From 77f8a1e392b64f51326df8aebdc77e97af07bfed Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 2 Nov 2015 17:27:38 +0100 Subject: [PATCH 001/102] Merge when build succeeds --- CHANGELOG | 1 + .../stylesheets/pages/merge_requests.scss | 4 +- .../projects/merge_requests_controller.rb | 35 ++++++++++++--- app/models/ci/commit.rb | 8 ++++ app/models/commit_status.rb | 33 ++++++++++++++ app/models/merge_request.rb | 13 ++++++ .../merge_when_build_succeeds_service.rb | 43 +++++++++++++++++++ .../merge_requests/refresh_service.rb | 8 +++- app/services/system_note_service.rb | 14 ++++++ .../cancel_merge_when_build_succeeds.js.haml | 2 + .../projects/merge_requests/merge.js.haml | 6 ++- .../merge_requests/widget/_open.html.haml | 2 + .../widget/open/_accept.html.haml | 18 +++++--- .../open/_merge_when_build_succeeds.html.haml | 27 ++++++++++++ config/routes.rb | 1 + ...ge_when_build_succeeds_to_merge_request.rb | 7 +++ db/schema.rb | 17 +++++--- 17 files changed, 219 insertions(+), 20 deletions(-) create mode 100644 app/services/merge_requests/merge_when_build_succeeds_service.rb create mode 100644 app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml create mode 100644 app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml create mode 100644 db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb diff --git a/CHANGELOG b/CHANGELOG index 0d89fca9fc1..195e53abd8b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.2.0 (unreleased) - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - Use issue editor as cross reference comment author when issue is edited with a new mention. + - Merge when build succeeds (Zeger-Jan van de Weg) v 8.1.1 - Fix cloning Wiki repositories via HTTP (Stan Hu) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f0b3667acca..b5a8c893678 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -19,18 +19,20 @@ .accept-merge-holder { .accept-action { display: inline-block; + float: left; } .accept-control { display: inline-block; + float: left; margin: 0; margin-left: 20px; padding: 5px; + padding-top: 12px; line-height: 20px; &.right { float: right; - padding-top: 12px; a { color: $gl-gray; } diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 16c42386623..2f9b8a25edf 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -2,7 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :merge, :merge_check, - :ci_status, :toggle_subscription + :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds ] before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits] before_action :validates_merge_request, only: [:show, :diffs, :commits] @@ -149,15 +149,34 @@ class Projects::MergeRequestsController < Projects::ApplicationController render partial: "projects/merge_requests/widget/show.html.haml", layout: false end + def cancel_merge_when_build_succeeds + return access_denied! unless @merge_request.can_be_merged_by?(current_user) + + if @merge_request.merge_when_build_succeeds? + @merge_request.reset_merge_when_build_succeeds + SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user) + end + end + def merge return access_denied! unless @merge_request.can_be_merged_by?(current_user) - if @merge_request.mergeable? - @merge_request.update(merge_error: nil) - MergeWorker.perform_async(@merge_request.id, current_user.id, params) - @status = true + unless @merge_request.mergeable? + @status = :failed + return + end + + @merge_request.update(merge_error: nil) + + if params[:merge_when_build_succeeds] && @merge_request.ci_commit.active? + MergeRequests::MergeWhenBuildSucceedsService.new(@project, + current_user, + merge_params: merge_params) + .execute(@merge_request) + @status = :merge_when_build_succeeds else - @status = false + MergeWorker.perform_async(@merge_request.id, current_user.id, params) + @status = :success end end @@ -282,6 +301,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ) end + def merge_params + params.permit(:should_remove_source_branch, :commit_message) + end + # Make sure merge requests created before 8.0 # have head file in refs/merge-requests/ def ensure_ref_fetched diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 13437b2483f..ebe4bace3b5 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -164,6 +164,14 @@ module Ci status == 'canceled' end + def active? + running? || pending? + end + + def complete? + canceled? || success? || failed? + end + def duration duration_array = latest_statuses.map(&:duration).compact duration_array.reduce(:+).to_i diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 0b73ab6d2eb..b1049fab788 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,3 +1,32 @@ +# == Schema Information +# +# project_id integer +# status string +# finished_at datetime +# trace text +# created_at datetime +# updated_at datetime +# started_at datetime +# runner_id integer +# coverage float +# commit_id integer +# commands text +# job_id integer +# name string +# deploy boolean default: false +# options text +# allow_failure boolean default: false, null: false +# stage string +# trigger_request_id integer +# stage_idx integer +# tag boolean +# ref string +# user_id integer +# type string +# target_url string +# description string +# + class CommitStatus < ActiveRecord::Base self.table_name = 'ci_builds' @@ -46,6 +75,10 @@ class CommitStatus < ActiveRecord::Base build.update_attributes finished_at: Time.now end + after_transition running: :success do |build, transition| + MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.gl_project, nil).trigger(build) + end + state :pending, value: 'pending' state :running, value: 'running' state :failed, value: 'failed' diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 85f37e49e62..c5f04dbcf4c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -34,9 +34,12 @@ class MergeRequest < ActiveRecord::Base belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" + belongs_to :merge_user, class_name: "User" has_one :merge_request_diff, dependent: :destroy + serialize :merge_params, Hash + after_create :create_merge_request_diff after_update :update_merge_request_diff @@ -385,6 +388,16 @@ class MergeRequest < ActiveRecord::Base message end + def reset_merge_when_build_succeeds + return unless merge_when_build_succeeds? + + self.merge_when_build_succeeds = false + self.merge_user = nil + self.merge_params = nil + + self.save + end + # Return array of possible target branches # depends on target project of MR def target_branches diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb new file mode 100644 index 00000000000..a4418360b8c --- /dev/null +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -0,0 +1,43 @@ +module MergeRequests + class MergeWhenBuildSucceedsService < MergeRequests::BaseService + def execute(merge_request) + merge_request.merge_params.merge!(params[:merge_params]) + + # The service is also called when the merge params are updated. + already_approved = merge_request.merge_when_build_succeeds? + + unless already_approved + merge_request.merge_when_build_succeeds = true + merge_request.merge_user = @current_user + end + + merge_request.save + + unless already_approved + SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user) + end + end + + def trigger(build) + merge_requests = merge_request_from(build) + + merge_requests.each do |merge_request| + next unless merge_request.merge_when_build_succeeds? + + ci_commit = merge_request.ci_commit + if ci_commit && ci_commit.success? && merge_request.mergeable? + MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) + end + end + end + + private + + def merge_request_from(build) + merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a + merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a + + merge_requests.uniq.select(&:source_project) + end + end +end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index d68bc79ecc0..335ef32abce 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -8,6 +8,7 @@ module MergeRequests find_new_commits reload_merge_requests + reset_merge_when_build_succeeds # Leave a system note if a branch was deleted/added if branch_added? || branch_removed? @@ -57,7 +58,6 @@ module MergeRequests merge_requests = filter_merge_requests(merge_requests) merge_requests.each do |merge_request| - if merge_request.source_branch == @branch_name || force_push? merge_request.reload_code merge_request.mark_as_unchecked @@ -76,6 +76,12 @@ module MergeRequests end end + def reset_merge_when_build_succeeds + merge_requests_for_source_branch.each do |merge_request| + merge_request.reset_merge_when_build_succeeds + end + end + def find_new_commits if branch_added? @commits = [] diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 708c2f00486..c9846e9f26f 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -130,6 +130,20 @@ class SystemNoteService create_note(noteable: noteable, project: project, author: author, note: body) end + # Called when 'merge when build succeeds' is executed + def self.merge_when_build_succeeds(noteable, project, author) + body = "Approved this request to be merged automatically when the build succeeds" + + create_note(noteable: noteable, project: project, author: author, note: body) + end + + # Called when 'merge when build succeeds' is canceled + def self.cancel_merge_when_build_succeeds(noteable, project, author) + body = "Canceled the automatic merge" + + create_note(noteable: noteable, project: project, author: author, note: body) + end + # Called when the title of a Noteable is changed # # noteable - Noteable object that responds to `title` diff --git a/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml new file mode 100644 index 00000000000..eab5be488b5 --- /dev/null +++ b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml @@ -0,0 +1,2 @@ +:plain + $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/accept'))}"); diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml index 33321651e32..89aae17a606 100644 --- a/app/views/projects/merge_requests/merge.js.haml +++ b/app/views/projects/merge_requests/merge.js.haml @@ -1,6 +1,10 @@ -- if @status +- case @status +- when :success :plain merge_request_widget.mergeInProgress(); +- when :merge_when_build_succeeds + :plain + $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}"); - else :plain $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}"); diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 0aad9bb3e88..e0013fb769a 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -13,6 +13,8 @@ = render 'projects/merge_requests/widget/open/conflicts' - elsif @merge_request.work_in_progress? = render 'projects/merge_requests/widget/open/wip' + - elsif @merge_request.merge_when_build_succeeds? + = render 'projects/merge_requests/widget/open/merge_when_build_succeeds' - elsif !@merge_request.can_be_merged_by?(current_user) = render 'projects/merge_requests/widget/open/not_allowed' - elsif @merge_request.can_be_merged? diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 613525437ab..2afc5f81251 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -2,8 +2,15 @@ = hidden_field_tag :authenticity_token, form_authenticity_token .accept-merge-holder.clearfix.js-toggle-container .accept-action - = f.button class: "btn btn-create accept_merge_request" do - Accept Merge Request + - ci_commit = @merge_request.ci_commit + - if ci_commit && ci_commit.active? + = f.button class: "btn btn-create btn-grouped merge_when_build_succeeds", name: "merge_when_build_succeeds" do + Merge when Build Succeeds + = f.button class: "btn btn-warning btn-grouped accept_merge_request" do + Accept Merge Request Now + - else + = f.button class: "btn btn-create btn-grouped accept_merge_request" do + Accept Merge Request - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? .accept-control.checkbox = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do @@ -19,7 +26,8 @@ rows: 14, hint: true :coffeescript - $('.accept-mr-form').on 'ajax:before', -> - btn = $('.accept_merge_request') - btn.disable() + $('.accept_merge_request').on 'click', -> + btn = $(this) btn.html(" Merge in progress") + $('.accept-mr-form').on 'ajax:sen', -> + $(".accept-mr-form :input").disable() diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml new file mode 100644 index 00000000000..7e5385cf8b9 --- /dev/null +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -0,0 +1,27 @@ +%h4 + Approved by #{link_to_member(@project, @merge_request.merge_user, avatar: true)} + to be merged automatically when + #{link_to "the build", ci_status_path(@merge_request.ci_commit)} succeeds +%div + - if @merge_request.merge_params["should_remove_source_branch"] + = succeed '.' do + The changes will be merged into + %span.label-branch= @merge_request.target_branch + The source branch will be removed. + - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) + - remove_source_branch_button = true + %p + = succeed '.' do + The changes will be merged into + %span.label-branch= @merge_request.target_branch + The source branch will not be removed. + +- if remove_source_branch_button || @merge_request.can_be_merged_by?(current_user) + .clearfix.prepend-top-10 + - if remove_source_branch_button + = link_to merge_namespace_project_merge_request_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do + = icon('times') + Remove Source Branch When Merged + - if @merge_request.can_be_merged_by?(current_user) + = link_to merge_namespace_project_merge_request_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request), remote: true, method: :delete, class: "btn btn-grouped btn-warning btn-sm" do + Cancel Automatic Merge diff --git a/config/routes.rb b/config/routes.rb index 0458f538eb6..917c3d3f1ed 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -556,6 +556,7 @@ Gitlab::Application.routes.draw do get :diffs get :commits post :merge + delete :merge, action: :cancel_merge_when_build_succeeds get :merge_check get :ci_status post :toggle_subscription diff --git a/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb b/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb new file mode 100644 index 00000000000..ceb52f0c222 --- /dev/null +++ b/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb @@ -0,0 +1,7 @@ +class AddMergeWhenBuildSucceedsToMergeRequest < ActiveRecord::Migration + def change + add_column :merge_requests, :merge_params, :text + add_column :merge_requests, :merge_when_build_succeeds, :boolean, default: false, null: false + add_column :merge_requests, :merge_user_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 4bde9f0b748..825d95565be 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: 20151026182941) do +ActiveRecord::Schema.define(version: 20151028152939) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -418,6 +418,7 @@ ActiveRecord::Schema.define(version: 20151026182941) do end add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree + add_index "labels", ["title"], name: "index_labels_on_title", using: :btree create_table "members", force: true do |t| t.integer "access_level", null: false @@ -453,9 +454,9 @@ ActiveRecord::Schema.define(version: 20151026182941) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree create_table "merge_requests", force: true do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" t.string "title" @@ -464,13 +465,16 @@ ActiveRecord::Schema.define(version: 20151026182941) do t.integer "milestone_id" t.string "state" t.string "merge_status" - t.integer "target_project_id", null: false + t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 + t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" t.string "merge_error" + t.text "merge_params" + t.boolean "merge_when_build_succeeds", default: false, null: false + t.integer "merge_user_id" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -499,6 +503,7 @@ ActiveRecord::Schema.define(version: 20151026182941) do add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree + add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree create_table "namespaces", force: true do |t| t.string "name", null: false From 63b234706d46f75c0c0f93bdfdc576e328981eb5 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 2 Nov 2015 20:02:51 +0100 Subject: [PATCH 002/102] MRs author can cancel automatic merge --- app/controllers/projects/merge_requests_controller.rb | 4 +++- .../widget/open/_merge_when_build_succeeds.html.haml | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2f9b8a25edf..d58dab2d666 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -150,7 +150,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def cancel_merge_when_build_succeeds - return access_denied! unless @merge_request.can_be_merged_by?(current_user) + unless @merge_request.can_be_merged_by?(current_user) || @merge_request.author == current_user + return access_denied! + end if @merge_request.merge_when_build_succeeds? @merge_request.reset_merge_when_build_succeeds diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index 7e5385cf8b9..f3894334968 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -14,7 +14,7 @@ = succeed '.' do The changes will be merged into %span.label-branch= @merge_request.target_branch - The source branch will not be removed. + The source branch won't be removed. - if remove_source_branch_button || @merge_request.can_be_merged_by?(current_user) .clearfix.prepend-top-10 @@ -22,6 +22,6 @@ = link_to merge_namespace_project_merge_request_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = icon('times') Remove Source Branch When Merged - - if @merge_request.can_be_merged_by?(current_user) + - if @merge_request.can_be_merged_by?(current_user) || @merge_request.author == current_user = link_to merge_namespace_project_merge_request_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request), remote: true, method: :delete, class: "btn btn-grouped btn-warning btn-sm" do Cancel Automatic Merge From 2f048df4a4a83ff009d2ef2d14ee04e5a2798618 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 18 Nov 2015 11:17:41 +0100 Subject: [PATCH 003/102] API support, incorporated feedback --- .../projects/merge_requests_controller.rb | 4 +- app/models/commit_status.rb | 2 +- app/models/merge_request.rb | 6 +- app/services/merge_requests/merge_service.rb | 16 +++-- .../merge_when_build_succeeds_service.rb | 2 +- .../merge_requests/refresh_service.rb | 4 +- app/services/system_note_service.rb | 2 +- .../widget/open/_accept.html.haml | 2 +- .../open/_merge_when_build_succeeds.html.haml | 8 +-- app/workers/merge_worker.rb | 13 +--- config/routes.rb | 3 +- doc/api/merge_requests.md | 60 ++++++++++++++-- lib/api/merge_requests.rb | 68 +++++++++++++------ spec/models/merge_request_spec.rb | 14 +++- spec/services/system_note_service_spec.rb | 16 +++++ 15 files changed, 157 insertions(+), 63 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d58dab2d666..931298df5d8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -171,9 +171,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds] && @merge_request.ci_commit.active? - MergeRequests::MergeWhenBuildSucceedsService.new(@project, - current_user, - merge_params: merge_params) + MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) .execute(@merge_request) @status = :merge_when_build_succeeds else diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index b1049fab788..acc86b1a8cd 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -75,7 +75,7 @@ class CommitStatus < ActiveRecord::Base build.update_attributes finished_at: Time.now end - after_transition running: :success do |build, transition| + after_transition [:pending, :running] => :success do |build, transition| MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.gl_project, nil).trigger(build) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c5f04dbcf4c..7b372399a3a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -253,6 +253,10 @@ class MergeRequest < ActiveRecord::Base end end + def mergeable_by_or_author(user) + self.can_be_merged_by?(user) || self.author == user + end + def mr_and_commit_notes # Fetch comments only from last 100 commits commits_for_notes_limit = 100 @@ -390,7 +394,7 @@ class MergeRequest < ActiveRecord::Base def reset_merge_when_build_succeeds return unless merge_when_build_succeeds? - + self.merge_when_build_succeeds = false self.merge_user = nil self.merge_params = nil diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 7963af127e1..db8d18a7d84 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -6,15 +6,12 @@ module MergeRequests # Executed when you do merge via GitLab UI # class MergeService < MergeRequests::BaseService - attr_reader :merge_request, :commit_message + attr_reader :merge_request - def execute(merge_request, commit_message) - @commit_message = commit_message + def execute(merge_request) @merge_request = merge_request - unless @merge_request.mergeable? - return error('Merge request is not mergeable') - end + return error('Merge request is not mergeable') unless @merge_request.mergeable? merge_request.in_locked_state do if commit @@ -32,7 +29,7 @@ module MergeRequests committer = repository.user_to_committer(current_user) options = { - message: commit_message, + message: params[:commit_message] || merge_request.merge_commit_message, author: committer, committer: committer } @@ -46,6 +43,11 @@ module MergeRequests def after_merge MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) + + if params[:should_remove_source_branch] + DeleteBranchService.new(@merge_request.source_project, current_user). + execute(merge_request.source_branch) + end end end end diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index a4418360b8c..d5cae2f98f7 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -1,7 +1,7 @@ module MergeRequests class MergeWhenBuildSucceedsService < MergeRequests::BaseService def execute(merge_request) - merge_request.merge_params.merge!(params[:merge_params]) + merge_request.merge_params.merge!(params) # The service is also called when the merge params are updated. already_approved = merge_request.merge_when_build_succeeds? diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 335ef32abce..e5024857201 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -77,9 +77,7 @@ module MergeRequests end def reset_merge_when_build_succeeds - merge_requests_for_source_branch.each do |merge_request| - merge_request.reset_merge_when_build_succeeds - end + merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds) end def find_new_commits diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index c9846e9f26f..5e8281a3fd0 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -132,7 +132,7 @@ class SystemNoteService # Called when 'merge when build succeeds' is executed def self.merge_when_build_succeeds(noteable, project, author) - body = "Approved this request to be merged automatically when the build succeeds" + body = "This merge request will be automatically merged when the build for #{noteable.ci_commit.short_sha} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 2afc5f81251..2a51241971f 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -29,5 +29,5 @@ $('.accept_merge_request').on 'click', -> btn = $(this) btn.html(" Merge in progress") - $('.accept-mr-form').on 'ajax:sen', -> + $('.accept-mr-form').on 'ajax:send', -> $(".accept-mr-form :input").disable() diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index f3894334968..ddd1a7bd63d 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -1,9 +1,9 @@ %h4 Approved by #{link_to_member(@project, @merge_request.merge_user, avatar: true)} to be merged automatically when - #{link_to "the build", ci_status_path(@merge_request.ci_commit)} succeeds + #{link_to "the build", ci_status_path(@merge_request.ci_commit)} succeeds. %div - - if @merge_request.merge_params["should_remove_source_branch"] + - if @merge_request.merge_params["should_remove_source_branch"].present? = succeed '.' do The changes will be merged into %span.label-branch= @merge_request.target_branch @@ -19,9 +19,9 @@ - if remove_source_branch_button || @merge_request.can_be_merged_by?(current_user) .clearfix.prepend-top-10 - if remove_source_branch_button - = link_to merge_namespace_project_merge_request_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do + = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = icon('times') Remove Source Branch When Merged - if @merge_request.can_be_merged_by?(current_user) || @merge_request.author == current_user - = link_to merge_namespace_project_merge_request_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request), remote: true, method: :delete, class: "btn btn-grouped btn-warning btn-sm" do + = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do Cancel Automatic Merge diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb index 5d1a8555b7d..c87c0a252b1 100644 --- a/app/workers/merge_worker.rb +++ b/app/workers/merge_worker.rb @@ -8,16 +8,7 @@ class MergeWorker current_user = User.find(current_user_id) merge_request = MergeRequest.find(merge_request_id) - result = MergeRequests::MergeService.new(merge_request.target_project, current_user). - execute(merge_request, params[:commit_message]) - - if result[:status] == :success && params[:should_remove_source_branch].present? - DeleteBranchService.new(merge_request.source_project, current_user). - execute(merge_request.source_branch) - - merge_request.source_project.repository.expire_branch_names - end - - result + MergeRequests::MergeService.new(merge_request.target_project, current_user, params). + execute(merge_request) end end diff --git a/config/routes.rb b/config/routes.rb index 917c3d3f1ed..993687665cc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -556,8 +556,7 @@ Gitlab::Application.routes.draw do get :diffs get :commits post :merge - delete :merge, action: :cancel_merge_when_build_succeeds - get :merge_check + post :cancel_merge_when_build_succeeds get :ci_status post :toggle_subscription end diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index ffa7f2cdf14..1b2ad1caf49 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -2,8 +2,8 @@ ## List merge requests -Get all merge requests for this project. -The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). +Get all merge requests for this project. +The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. ``` @@ -292,9 +292,59 @@ PUT /projects/:id/merge_request/:merge_request_id/merge Parameters: -- `id` (required) - The ID of a project -- `merge_request_id` (required) - ID of MR -- `merge_commit_message` (optional) - Custom merge commit message +- `id` (required) - The ID of a project +- `merge_request_id` (required) - ID of MR +- `merge_commit_message` (optional) - Custom merge commit message +- `should_remove_source_branch` (optional) - if `true` removes the source branch +- `merge_when_build_succeeds` (optional) - if `true` the MR is merge when the build succeeds + +```json +{ + "id": 1, + "target_branch": "master", + "source_branch": "test1", + "project_id": 3, + "title": "test1", + "state": "merged", + "upvotes": 0, + "downvotes": 0, + "author": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "name": "Administrator", + "state": "active", + "created_at": "2012-04-29T08:46:00Z" + }, + "assignee": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "name": "Administrator", + "state": "active", + "created_at": "2012-04-29T08:46:00Z" + } +} +``` + +## Cancel Merge When Build Succeeds + +Cancels the merge when build succeeds and reset the merge parameters + +If successfull you'll get `200 OK`. + +If you don't have permissions to accept this merge request - you'll get a 401 + +If the merge request is already merged or closed - you get 405 and error message 'Method Not Allowed' + +In case the merge request is not set to be merged when the build succeeds, you'll also get a 405 with the error message 'Method Not Allowed' +``` +PUT /projects/:id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds +``` +Parameters: + +- `id` (required) - The ID of a project +- `merge_request_id` (required) - ID of MR ```json { diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 6eb84baf9cb..f981432db36 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -179,9 +179,10 @@ module API # Merge MR # # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # merge_commit_message (optional) - Custom merge commit message + # id (required) - The ID of a project + # merge_request_id (required) - ID of MR + # merge_commit_message (optional) - Custom merge commit message + # merge_when_build_succeeds (optional) - truethy when this MR should be merged when the build is succesfull # Example: # PUT /projects/:id/merge_request/:merge_request_id/merge # @@ -191,34 +192,57 @@ module API allowed = ::Gitlab::GitAccess.new(current_user, user_project). can_push_to_branch?(merge_request.target_branch) - if allowed - if merge_request.unchecked? - merge_request.check_if_can_be_merged - end + # Merge request can not be merged + # because user dont have permissions to push into target branch + unauthorized! unless allowed - if merge_request.open? && !merge_request.work_in_progress? - if merge_request.can_be_merged? - commit_message = params[:merge_commit_message] || merge_request.merge_commit_message + not_allowed! unless merge_request.open? - ::MergeRequests::MergeService.new(merge_request.target_project, current_user). - execute(merge_request, commit_message) + merge_request.check_if_can_be_merged if merge_request.unchecked? - present merge_request, with: Entities::MergeRequest - else - render_api_error!('Branch cannot be merged', 405) - end + merge_params = { + commit_message: params[:merge_commit_message], + should_remove_source_branch: params[:should_remove_source_branch] + } + + if !merge_request.work_in_progress? + if parse_boolean(params[:merge_when_build_succeeds]) + ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). + execute(merge_request) else - # Merge request can not be merged - # because it is already closed/merged or marked as WIP - not_allowed! + ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). + execute(merge_request, merge_params) end else - # Merge request can not be merged - # because user dont have permissions to push into target branch - unauthorized! + render_api_error!('Branch cannot be merged', 405) end + + present merge_request, with: Entities::MergeRequest end + # Cancel Merge if Merge When build succeeds is enabled + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - ID of MR + # + post ":id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds" do + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + + allowed = ::Gitlab::GitAccess.new(current_user, user_project). + can_push_to_branch?(merge_request.target_branch) + + # Merge request can not be merged + # because user dont have permissions to push into target branch + unauthorized! unless allowed + + if merge_request.merged? || !merge_request.open? || !merge_request.merge_when_build_succeeds? + not_allowed! + end + + merge_request.reset_merge_when_build_succeeds + + present merge_request, with: Entities::MergeRequest + end # Get a merge request's comments # diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index eed2cbc5412..e42534a8491 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -30,7 +30,7 @@ describe MergeRequest do describe 'associations' do it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } - + it { is_expected.to belong_to(:merge_user).class_name("User") } it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } end @@ -53,6 +53,8 @@ describe MergeRequest do it { is_expected.to respond_to(:unchecked?) } it { is_expected.to respond_to(:can_be_merged?) } it { is_expected.to respond_to(:cannot_be_merged?) } + it { is_expected.to respond_to(:merge_params) } + it { is_expected.to respond_to(:merge_when_build_succeeds) } end describe '#to_reference' do @@ -171,6 +173,16 @@ describe MergeRequest do end end + describe "#reset_merge_when_build_succeeds" do + let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true } + it "sets the item to false" do + merge_if_green.reset_merge_when_build_succeeds + merge_if_green.reload + + expect(merge_if_green.merge_when_build_succeeds).to be_falsey + end + end + describe "#hook_attrs" do it "has all the required keys" do attrs = subject.hook_attrs diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index a45130bd473..35912ece644 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -207,6 +207,22 @@ describe SystemNoteService do end end + describe '.merge_when_build_succeeds' do + let(:ci_commit) { create :ci_commit, gl_project: project } + let(:merge_request) { create :merge_request, project: project } + + subject { described_class.merge_when_build_succeeds(merge_request, project, author) } + + it_behaves_like 'a system note' + + it "posts the Merge When Build Succeeds system note" do + allow(merge_request).to receive(:ci_commit).and_return(ci_commit) + allow(ci_commit).to receive(:short_sha).and_return('12345678') + + expect(subject.note).to eq "This merge request will be automatically merged when the build for 12345678 succeeds" + end + end + describe '.change_title' do subject { described_class.change_title(noteable, project, author, 'Old title') } From 8608c6325e19f529f7b43ff881c562d3a0114e1c Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 23 Nov 2015 09:42:20 +0100 Subject: [PATCH 004/102] Refactor MergeWhenBuildSucceedsService and incorporate feedback --- CHANGELOG | 5 +-- .../projects/merge_requests_controller.rb | 11 ++---- app/models/merge_request.rb | 11 ++++-- .../merge_when_build_succeeds_service.rb | 17 ++++++++- app/services/system_note_service.rb | 4 +-- .../widget/open/_accept.html.haml | 2 +- .../open/_merge_when_build_succeeds.html.haml | 9 ++--- config/routes.rb | 1 + db/schema.rb | 2 -- doc/api/merge_requests.md | 8 ++--- lib/api/merge_requests.rb | 36 +++++++------------ 11 files changed, 52 insertions(+), 54 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 08188324d74..18f5f270ec1 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.3.0 (unreleased) +- Merge when build succeeds (Zeger-Jan van de Weg) v 8.2.0 - Fix grouping of contributors by email in graph. @@ -28,10 +29,6 @@ v 8.2.0 - Allow to define cache in `.gitlab-ci.yml` - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - - Use issue editor as cross reference comment author when issue is edited with a new mention. - - Merge when build succeeds (Zeger-Jan van de Weg) - -v 8.1.1 - [API] Add ability to fetch the commit ID of the last commit that actually touched a file - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) - Add "New file" link to dropdown on project page diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9db6ed5022d..f2e9a34dd2e 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -151,14 +151,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def cancel_merge_when_build_succeeds - unless @merge_request.can_be_merged_by?(current_user) || @merge_request.author == current_user - return access_denied! - end + return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) - if @merge_request.merge_when_build_succeeds? - @merge_request.reset_merge_when_build_succeeds - SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user) - end + MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request) end def merge @@ -171,7 +166,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) - if params[:merge_when_build_succeeds] && @merge_request.ci_commit.active? + if params[:merge_when_build_succeeds] && @merge_request.ci_commit && @merge_request.ci_commit.active? MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) .execute(@merge_request) @status = :merge_when_build_succeeds diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 05c3bc074bb..89f9e8fa6a8 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -254,8 +254,15 @@ class MergeRequest < ActiveRecord::Base end end - def mergeable_by_or_author(user) - self.can_be_merged_by?(user) || self.author == user + def can_cancel_merge_when_build_succeeds?(user) + can_be_merged_by?(user) || self.author == user + end + + def can_remove_source_branch? + for_fork? && + !project.protected_branch(source_branch) && + !project.repository.root_ref(source_branch) && + can?(current_user, :push_code, project) end def mr_and_commit_notes diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index d5cae2f98f7..15dcace5dfb 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -1,5 +1,7 @@ module MergeRequests class MergeWhenBuildSucceedsService < MergeRequests::BaseService + # Marks the passed `merge_request` to be marked when the build succeeds or + # updates the params for the automatic merge def execute(merge_request) merge_request.merge_params.merge!(params) @@ -14,10 +16,11 @@ module MergeRequests merge_request.save unless already_approved - SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user) + SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.ci_commit) end end + # Triggers the automatic merge of merge_request once the build succeeds def trigger(build) merge_requests = merge_request_from(build) @@ -31,6 +34,18 @@ module MergeRequests end end + # Cancels the automatic merge + def cancel(merge_request) + if merge_request.merge_when_build_succeeds? && merge_request.open? && !merge_request.merged? + merge_request.reset_merge_when_build_succeeds + SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user) + + success + else + error("Can't cancel the automatic merge", 406) + end + end + private def merge_request_from(build) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 5e8281a3fd0..7de4221c4c4 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -131,8 +131,8 @@ class SystemNoteService end # Called when 'merge when build succeeds' is executed - def self.merge_when_build_succeeds(noteable, project, author) - body = "This merge request will be automatically merged when the build for #{noteable.ci_commit.short_sha} succeeds" + def self.merge_when_build_succeeds(noteable, project, author, ci_commit) + body = "Approved an automatic merge when the build for #{ci_commit.sha} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index d2189787d47..5ec623b472c 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -13,7 +13,7 @@ - else = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do Accept Merge Request - - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? + - if @merge_request.can_remove_source_branch? .accept-control.checkbox = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do = check_box_tag :should_remove_source_branch diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index ddd1a7bd63d..51e18f84424 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -8,20 +8,17 @@ The changes will be merged into %span.label-branch= @merge_request.target_branch The source branch will be removed. - - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) - - remove_source_branch_button = true %p = succeed '.' do The changes will be merged into %span.label-branch= @merge_request.target_branch - The source branch won't be removed. + The source branch will not be removed. -- if remove_source_branch_button || @merge_request.can_be_merged_by?(current_user) .clearfix.prepend-top-10 - - if remove_source_branch_button + - if @merge_request.can_remove_source_branch? && !@merge_request.merge_params["should_remove_source_branch"].present? = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = icon('times') Remove Source Branch When Merged - - if @merge_request.can_be_merged_by?(current_user) || @merge_request.author == current_user + - if @merge_request.merge_when_build_succeeds = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do Cancel Automatic Merge diff --git a/config/routes.rb b/config/routes.rb index 49543786686..81f568cfa74 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -570,6 +570,7 @@ Gitlab::Application.routes.draw do member do get :diffs get :commits + get :merge_check post :merge post :cancel_merge_when_build_succeeds get :ci_status diff --git a/db/schema.rb b/db/schema.rb index fa32617cb99..7d5ed559754 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -421,7 +421,6 @@ ActiveRecord::Schema.define(version: 20151116144118) do end add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree - add_index "labels", ["title"], name: "index_labels_on_title", using: :btree create_table "lfs_objects", force: true do |t| t.string "oid", null: false @@ -525,7 +524,6 @@ ActiveRecord::Schema.define(version: 20151116144118) do add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree - add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree create_table "namespaces", force: true do |t| t.string "name", null: false diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 1b2ad1caf49..19e6fbb7887 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -296,7 +296,7 @@ Parameters: - `merge_request_id` (required) - ID of MR - `merge_commit_message` (optional) - Custom merge commit message - `should_remove_source_branch` (optional) - if `true` removes the source branch -- `merge_when_build_succeeds` (optional) - if `true` the MR is merge when the build succeeds +- `merged_when_build_succeeds` (optional) - if `true` the MR is merge when the build succeeds ```json { @@ -329,15 +329,13 @@ Parameters: ## Cancel Merge When Build Succeeds -Cancels the merge when build succeeds and reset the merge parameters - -If successfull you'll get `200 OK`. +If successful you'll get `200 OK`. If you don't have permissions to accept this merge request - you'll get a 401 If the merge request is already merged or closed - you get 405 and error message 'Method Not Allowed' -In case the merge request is not set to be merged when the build succeeds, you'll also get a 405 with the error message 'Method Not Allowed' +In case the merge request is not set to be merged when the build succeeds, you'll also get a 406 error. ``` PUT /projects/:id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds ``` diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index f981432db36..b72f816932b 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -182,39 +182,35 @@ module API # id (required) - The ID of a project # merge_request_id (required) - ID of MR # merge_commit_message (optional) - Custom merge commit message - # merge_when_build_succeeds (optional) - truethy when this MR should be merged when the build is succesfull + # should_remove_source_branch - When true, the source branch will be deleted if possible + # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds # Example: # PUT /projects/:id/merge_request/:merge_request_id/merge # put ":id/merge_request/:merge_request_id/merge" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - allowed = ::Gitlab::GitAccess.new(current_user, user_project). - can_push_to_branch?(merge_request.target_branch) - # Merge request can not be merged # because user dont have permissions to push into target branch - unauthorized! unless allowed + unauthorized! unless merge_request.can_be_merged_by?(current_user) - not_allowed! unless merge_request.open? + not_allowed! unless merge_request.open? && !merge_request.work_in_progress? merge_request.check_if_can_be_merged if merge_request.unchecked? + render_api_error!('Branch cannot be merged', 406) if merge_request.can_be_merged? + merge_params = { commit_message: params[:merge_commit_message], should_remove_source_branch: params[:should_remove_source_branch] } - if !merge_request.work_in_progress? - if parse_boolean(params[:merge_when_build_succeeds]) - ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request) - else - ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request, merge_params) - end + if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active? + ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). + execute(merge_request) else - render_api_error!('Branch cannot be merged', 405) + ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). + execute(merge_request) end present merge_request, with: Entities::MergeRequest @@ -233,15 +229,9 @@ module API # Merge request can not be merged # because user dont have permissions to push into target branch - unauthorized! unless allowed + unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) - if merge_request.merged? || !merge_request.open? || !merge_request.merge_when_build_succeeds? - not_allowed! - end - - merge_request.reset_merge_when_build_succeeds - - present merge_request, with: Entities::MergeRequest + ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) end # Get a merge request's comments From 9ab7bdf7739935bf79c2e033212726a4be421a26 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 1 Dec 2015 19:45:58 -0200 Subject: [PATCH 005/102] Signed in admin should be able to add/remove himself to a group --- app/models/ability.rb | 6 ++---- features/admin/groups.feature | 16 ++++++++++++++++ features/steps/admin/groups.rb | 29 +++++++++++++++++++++++++++++ features/steps/shared/group.rb | 4 ++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 07f3a56ec7a..10c41306c55 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -346,12 +346,10 @@ class Ability unless group.last_owner?(target_user) can_manage = group_abilities(user, group).include?(:admin_group_member) - if can_manage && user != target_user + if can_manage rules << :update_group_member rules << :destroy_group_member - end - - if user == target_user + elsif user == target_user rules << :destroy_group_member end end diff --git a/features/admin/groups.feature b/features/admin/groups.feature index 973918086a3..2edb3964f70 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -33,3 +33,19 @@ Feature: Admin Groups When I visit admin group page When I select user "johndoe@gitlab.com" from user list as "Reporter" Then I should see "johndoe@gitlab.com" in team list in every project as "Reporter" + + @javascript + Scenario: Signed in admin should be able to add himself to a group + Given "John Doe" is owner of group "Owned" + When I visit group "Owned" members page + When I select current user as "Developer" + Then I should see current user as "Developer" + + @javascript + Scenario: Signed in admin should be able to remove himself from group + Given current user is developer of group "Owned" + When I visit group "Owned" members page + Then I should see current user as "Developer" + When I click on the "Remove User From Group" button for current user + When I visit group "Owned" members page + Then I should not see current user as "Developer" diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index d27634858a2..43fd91d0d4c 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -1,5 +1,6 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps include SharedAuthentication + include SharedGroup include SharedPaths include SharedUser include SharedActiveTab @@ -88,6 +89,34 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end end + step 'I select current user as "Developer"' do + page.within ".users-group-form" do + select2(current_user.id, from: "#user_ids", multiple: true) + select "Developer", from: "access_level" + end + + click_button "Add users to group" + end + + step 'I should see current user as "Developer"' do + page.within '.content-list' do + expect(page).to have_content(current_user.name) + expect(page).to have_content('Developer') + end + end + + step 'I click on the "Remove User From Group" button for current user' do + find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click + # poltergeist always confirms popups. + end + + step 'I should not see current user as "Developer"' do + page.within '.content-list' do + expect(page).not_to have_content(current_user.name) + expect(page).not_to have_content('Developer') + end + end + protected def current_group diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb index 58581653f28..fe6736dacd4 100644 --- a/features/steps/shared/group.rb +++ b/features/steps/shared/group.rb @@ -1,6 +1,10 @@ module SharedGroup include Spinach::DSL + step 'current user is developer of group "Owned"' do + is_member_of(current_user.name, "Owned", Gitlab::Access::DEVELOPER) + end + step '"John Doe" is owner of group "Owned"' do is_member_of("John Doe", "Owned", Gitlab::Access::OWNER) end From 67cc6b0642573fe443126042dd36b15f05bc539c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 1 Dec 2015 20:47:26 -0200 Subject: [PATCH 006/102] Signed in admin should be able to add/remove himself to a project --- app/models/ability.rb | 6 ++---- features/admin/projects.feature | 16 ++++++++++++++ features/steps/admin/projects.rb | 37 ++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 10c41306c55..cd5ae0fb0fd 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -365,12 +365,10 @@ class Ability unless target_user == project.owner can_manage = project_abilities(user, project).include?(:admin_project_member) - if can_manage && user != target_user + if can_manage rules << :update_project_member rules << :destroy_project_member - end - - if user == target_user + elsif user == target_user rules << :destroy_project_member end end diff --git a/features/admin/projects.feature b/features/admin/projects.feature index f7cec04eb75..c5ee80136c8 100644 --- a/features/admin/projects.feature +++ b/features/admin/projects.feature @@ -27,3 +27,19 @@ Feature: Admin Projects And I visit admin project page When I transfer project to group 'Web' Then I should see project transfered + + @javascript + Scenario: Signed in admin should be able to add himself to a project + Given "John Doe" owns private project "Enterprise" + When I visit project "Enterprise" members page + When I select current user as "Developer" + Then I should see current user as "Developer" + + @javascript + Scenario: Signed in admin should be able to remove himself from a project + Given "John Doe" owns private project "Enterprise" + And current user is developer of project "Enterprise" + When I visit project "Enterprise" members page + Then I should see current user as "Developer" + When I click on the "Remove User From Project" button for current user + Then I should not see current user as "Developer" diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb index 5a1cc9aa151..a7a28755a6c 100644 --- a/features/steps/admin/projects.rb +++ b/features/steps/admin/projects.rb @@ -3,6 +3,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps include SharedPaths include SharedAdmin include SharedProject + include SharedUser + include Select2Helper step 'I should see all non-archived projects' do Project.non_archived.each do |p| @@ -56,6 +58,41 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps expect(page).to have_content 'Namespace: Web' end + step 'I visit project "Enterprise" members page' do + project = Project.find_by!(name: "Enterprise") + visit namespace_project_project_members_path(project.namespace, project) + end + + step 'I select current user as "Developer"' do + page.within ".users-project-form" do + select2(current_user.id, from: "#user_ids", multiple: true) + select "Developer", from: "access_level" + end + + click_button "Add users to project" + end + + step 'I should see current user as "Developer"' do + page.within '.content-list' do + expect(page).to have_content(current_user.name) + expect(page).to have_content('Developer') + end + end + + step 'current user is developer of project "Enterprise"' do + project = Project.find_by!(name: "Enterprise") + project.team << [current_user, :developer] + end + + step 'I click on the "Remove User From Project" button for current user' do + find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click + # poltergeist always confirms popups. + end + + step 'I should not see current_user as "Developer"' do + expect(page).not_to have_selector(:css, '.content-list') + end + def project @project ||= Project.first end From 8034b61b52ed9e54366ee9d73d17a4ea1c00de44 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 1 Dec 2015 20:58:31 -0200 Subject: [PATCH 007/102] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index db812796b69..c088c89d861 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.2.2 - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) - Fix: Raw private snippets access workflow - Prevent "413 Request entity too large" errors when pushing large files with LFS + - Fix: As an admin, cannot add oneself as a member to a group/project v 8.2.1 - Forcefully update builds that didn't want to update with state machine From a7682f8775a4609ac8c70151ffe8f3ccf3b767b6 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 24 Nov 2015 14:59:02 +0100 Subject: [PATCH 008/102] Specs for 'Merge When Build Succeeds' --- .gitignore | 1 + app/models/merge_request.rb | 13 ++- .../merge_when_build_succeeds_service.rb | 6 +- app/services/system_note_service.rb | 2 +- .../merge_requests/widget/_merged.html.haml | 4 +- .../widget/open/_accept.html.haml | 4 +- .../open/_merge_when_build_succeeds.html.haml | 21 ++--- lib/api/entities.rb | 1 + lib/api/merge_requests.rb | 22 ++--- spec/factories/ci/commits.rb | 23 +++--- spec/factories/commit_statuses.rb | 2 +- .../merge_when_build_succeeds_spec.rb | 80 +++++++++++++++++++ spec/models/merge_request_spec.rb | 24 ++++++ spec/requests/api/merge_requests_spec.rb | 17 +++- .../merge_requests/merge_service_spec.rb | 8 +- .../merge_when_build_succeeds_service_spec.rb | 78 ++++++++++++++++++ .../merge_requests/refresh_service_spec.rb | 5 +- spec/services/system_note_service_spec.rb | 22 +++-- 18 files changed, 268 insertions(+), 65 deletions(-) create mode 100644 spec/features/merge_requests/merge_when_build_succeeds_spec.rb create mode 100644 spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb diff --git a/.gitignore b/.gitignore index f5b6427ca03..881b3fb81ac 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ public/assets/ public/uploads.* public/uploads/ shared/artifacts/ +TODO rails_best_practices_output.html /tags tmp/ diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 89f9e8fa6a8..131dfda6b5f 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -254,15 +254,14 @@ class MergeRequest < ActiveRecord::Base end end - def can_cancel_merge_when_build_succeeds?(user) - can_be_merged_by?(user) || self.author == user + def can_cancel_merge_when_build_succeeds?(current_user) + can_be_merged_by?(current_user) || self.author == current_user end - def can_remove_source_branch? - for_fork? && - !project.protected_branch(source_branch) && - !project.repository.root_ref(source_branch) && - can?(current_user, :push_code, project) + def can_remove_source_branch?(current_user) + !source_project.protected_branch?(source_branch) && + !source_project.root_ref?(source_branch) && + Ability.abilities.allowed?(current_user, :push_code, source_project) end def mr_and_commit_notes diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 15dcace5dfb..2f101e53a3f 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -11,13 +11,11 @@ module MergeRequests unless already_approved merge_request.merge_when_build_succeeds = true merge_request.merge_user = @current_user + + SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.ci_commit) end merge_request.save - - unless already_approved - SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.ci_commit) - end end # Triggers the automatic merge of merge_request once the build succeeds diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 7de4221c4c4..ed557fef814 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -132,7 +132,7 @@ class SystemNoteService # Called when 'merge when build succeeds' is executed def self.merge_when_build_succeeds(noteable, project, author, ci_commit) - body = "Approved an automatic merge when the build for #{ci_commit.sha} succeeds" + body = "Enabled an automatic merge when the build for #{ci_commit.sha} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml index a788fcea23f..52e34ee617e 100644 --- a/app/views/projects/merge_requests/widget/_merged.html.haml +++ b/app/views/projects/merge_requests/widget/_merged.html.haml @@ -13,7 +13,7 @@ %span.label-branch= @merge_request.target_branch The source branch has been removed. - - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) + - elsif @merge_request.can_remove_source_branch?(current_user) .remove_source_branch_widget %p = succeed '.' do @@ -48,5 +48,3 @@ $('.remove_source_branch_in_progress').hide(); $('.remove_source_branch_widget.failed').show(); }); - - diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 5ec623b472c..279e2ec91f8 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -7,13 +7,13 @@ - ci_commit = @merge_request.ci_commit - if ci_commit && ci_commit.active? = f.button class: "btn btn-create btn-grouped merge_when_build_succeeds", name: "merge_when_build_succeeds" do - Merge when Build Succeeds + Merge When Build Succeeds = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do Accept Merge Request Now - else = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do Accept Merge Request - - if @merge_request.can_remove_source_branch? + - if @merge_request.can_remove_source_branch?(current_user) .accept-control.checkbox = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do = check_box_tag :should_remove_source_branch diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index 51e18f84424..43ba49c5a5e 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -3,22 +3,25 @@ to be merged automatically when #{link_to "the build", ci_status_path(@merge_request.ci_commit)} succeeds. %div - - if @merge_request.merge_params["should_remove_source_branch"].present? + - source_branch_removed = @merge_request.merge_params["should_remove_source_branch"].present? + - if source_branch_removed = succeed '.' do The changes will be merged into %span.label-branch= @merge_request.target_branch The source branch will be removed. + - else %p = succeed '.' do The changes will be merged into %span.label-branch= @merge_request.target_branch The source branch will not be removed. - .clearfix.prepend-top-10 - - if @merge_request.can_remove_source_branch? && !@merge_request.merge_params["should_remove_source_branch"].present? - = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do - = icon('times') - Remove Source Branch When Merged - - if @merge_request.merge_when_build_succeeds - = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do - Cancel Automatic Merge + - if (@merge_request.can_remove_source_branch?(current_user) && !source_branch_removed) || @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + .clearfix.prepend-top-10 + - if @merge_request.can_remove_source_branch?(current_user) && !source_branch_removed + = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do + = icon('times') + Remove Source Branch When Merged + - if @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do + Cancel Automatic Merge diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d6aec03d7f5..6f9f71b0945 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -169,6 +169,7 @@ module API expose :description expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone + expose :merge_when_build_succeeds end class MergeRequestChanges < MergeRequest diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index b72f816932b..32cb1137ef7 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -179,11 +179,11 @@ module API # Merge MR # # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # merge_commit_message (optional) - Custom merge commit message - # should_remove_source_branch - When true, the source branch will be deleted if possible - # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds + # id (required) - The ID of a project + # merge_request_id (required) - ID of MR + # merge_commit_message (optional) - Custom merge commit message + # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible + # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds # Example: # PUT /projects/:id/merge_request/:merge_request_id/merge # @@ -193,12 +193,11 @@ module API # Merge request can not be merged # because user dont have permissions to push into target branch unauthorized! unless merge_request.can_be_merged_by?(current_user) - - not_allowed! unless merge_request.open? && !merge_request.work_in_progress? + not_allowed! if !merge_request.open? || merge_request.work_in_progress? merge_request.check_if_can_be_merged if merge_request.unchecked? - render_api_error!('Branch cannot be merged', 406) if merge_request.can_be_merged? + render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged? merge_params = { commit_message: params[:merge_commit_message], @@ -206,7 +205,7 @@ module API } if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active? - ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). + ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). execute(merge_request) else ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). @@ -224,11 +223,6 @@ module API post ":id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - allowed = ::Gitlab::GitAccess.new(current_user, user_project). - can_push_to_branch?(merge_request.target_branch) - - # Merge request can not be merged - # because user dont have permissions to push into target branch unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/commits.rb index 79e000b7ccb..70e3fa319c6 100644 --- a/spec/factories/ci/commits.rb +++ b/spec/factories/ci/commits.rb @@ -2,17 +2,18 @@ # # Table name: commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index 52de437052d..8898b71e2a3 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -5,7 +5,7 @@ FactoryGirl.define do name 'default' status 'success' description 'commit status' - commit factory: :ci_commit + commit factory: :ci_commit_with_one_job factory :generic_commit_status, class: GenericCommitStatus do name 'generic' diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb new file mode 100644 index 00000000000..b25a3f05e29 --- /dev/null +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +feature 'Merge When Build Succeeds', feature: true, js: true do + let(:user) { create(:user) } + + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + + before do + project.team << [user, :master] + project.enable_ci + end + + context "Active build for Merge Request" do + before do + ci_commit = create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) + ci_build = create(:ci_build, commit: ci_commit) + + login_as user + visit_merge_request(merge_request) + end + + it 'displays the Merge When Build Succeeds button' do + expect(page).to have_button "Merge When Build Succeeds" + end + + context "Merge When Build succeeds enabled" do + before do + click_button "Merge When Build Succeeds" + end + + it 'activates Merge When Build Succeeds feature' do + expect(page).to have_link "Cancel Automatic Merge" + + expect(page).to have_content "Approved by #{user.name} to be merged automatically when the build succeeds." + expect(page).to have_content "The source branch will not be removed." + end + end + end + + context 'When it is enabled' do + # No clue how, but push a new commit to the branch + let(:merge_request) { create(:merge_request_with_diffs, source_project: project, # source_branch: "mepmep", + author: user, title: "Bug NS-04", merge_when_build_succeeds: true) } + + before do + merge_request.source_project.team << [user, :master] + merge_request.source_branch = "feature" + merge_request.target_branch = "master" + merge_request.save! + + ci_commit = create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) + ci_build = create(:ci_build, commit: ci_commit) + + login_as user + visit_merge_request(merge_request) + end + + it 'cancels the automatic merge' do + click_link "Cancel Automatic Merge" + + expect(page).to have_button "Merge When Build Succeeds" + end + + it "allows the user to remove the source branch" do + expect(page).to have_link "Remove Source Branch When Merged" + end + end + + context 'Build is not active' do + it "should not allow for enabling" do + visit_merge_request(merge_request) + expect(page).not_to have_button "Merge When Build Succeeds" + end + end + + def visit_merge_request(merge_request) + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 1bd09a1b0fb..c7a9765825e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -174,6 +174,30 @@ describe MergeRequest do end end + describe '#can_remove_source_branch' do + let(:user) { build(:user)} + + before do + subject.source_project.team << [user, :master] + end + + it "cant be merged when its a a protected branch" do + subject.source_project.protected_branches = []; + + expect(subject.can_remove_source_branch?(user)).to be_falsey + end + + it "cant remove a root ref" do + subject.source_branch = "master"; + + expect(subject.can_remove_source_branch?(user)).to be_falsey + end + + it "is truthy in all other cases" do + expect(subject.can_remove_source_branch?(user)) + end + end + describe "#reset_merge_when_build_succeeds" do let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true } it "sets the item to false" do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index a68c7b1e461..91ae2059e95 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -303,19 +303,21 @@ describe API::API, api: true do end describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do + let (:ci_commit) { create(:ci_commit_without_jobs) } + it "should return merge_request in case of success" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) expect(response.status).to eq(200) end - it "should return 405 if branch can't be merged" do + it "should return 406 if branch can't be merged" do allow_any_instance_of(MergeRequest). to receive(:can_be_merged?).and_return(false) put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response.status).to eq(406) expect(json_response['message']).to eq('Branch cannot be merged') end @@ -340,6 +342,17 @@ describe API::API, api: true do expect(response.status).to eq(401) expect(json_response['message']).to eq('401 Unauthorized') end + + it "enables merge when build succeeds if the ci is active" do + allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit) + allow(ci_commit).to receive(:active?).and_return(true) + + put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user), merge_when_build_succeeds: true + + expect(response.status).to eq(200) + expect(json_response['title']).to eq('Test') + expect(json_response['merge_when_build_succeeds']).to eq(true) + end end describe "PUT /projects/:id/merge_request/:merge_request_id" do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 7483f51de03..242524286fa 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -13,12 +13,12 @@ describe MergeRequests::MergeService do describe :execute do context 'valid params' do - let(:service) { MergeRequests::MergeService.new(project, user, {}) } + let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } before do allow(service).to receive(:execute_hooks) - service.execute(merge_request, 'Awesome message') + service.execute(merge_request) end it { expect(merge_request).to be_valid } @@ -37,14 +37,14 @@ describe MergeRequests::MergeService do end context "error handling" do - let(:service) { MergeRequests::MergeService.new(project, user, {}) } + let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } it 'saves error if there is an exception' do allow(service).to receive(:repository).and_raise("error") allow(service).to receive(:execute_hooks) - service.execute(merge_request, 'Awesome message') + service.execute(merge_request) expect(merge_request.merge_error).to eq("Something went wrong during merge") end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb new file mode 100644 index 00000000000..8638539173b --- /dev/null +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +describe MergeRequests::MergeWhenBuildSucceedsService do + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:mr_merge_if_green_enabled) { create(:merge_request, merge_when_build_succeeds: true, + source_branch: "source_branch", target_branch: project.default_branch, + source_project: project, target_project: project, state: "opened") } + let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch) } + let(:project) { create(:project) } + let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') } + + before do + project.ci_commits = [ci_commit] + project.save! + end + describe "#execute" do + context 'first time enabling' do + before do + allow(merge_request).to receive(:ci_commit).and_return(ci_commit) + end + + it 'sets the params, merge_user, and flag' do + service.execute(merge_request) + + expect(merge_request).to be_valid + expect(merge_request.merge_when_build_succeeds).to be_truthy + expect(merge_request.merge_params).to eq commit_message: 'Awesome message' + expect(merge_request.merge_user).to be user + + note = merge_request.notes.last + expect(note.note).to include 'Enabled an automatic merge when the build for' + end + end + + context 'allready approved' do + let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, new_key: true) } + let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } + + before do + allow(mr_merge_if_green_enabled).to receive(:ci_commit).and_return(ci_commit) + allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true) + allow(ci_commit).to receive(:success?).and_return(true) + end + + it 'updates the merge params' do + expect(SystemNoteService).not_to receive(:merge_when_build_succeeds) + + service.execute(mr_merge_if_green_enabled) + expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) + end + end + end + + describe "#trigger" do + let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } + + it "merges all merge requests with merge when build succeeds enabled" do + allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit) + allow(ci_commit).to receive(:success?).and_return(true) + + expect(MergeWorker).to receive(:perform_async) + service.trigger(build) + end + end + + describe "#cancel" do + before do + service.cancel(mr_merge_if_green_enabled) + end + + it "resets all the merge_when_build_succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey + expect(mr_merge_if_green_enabled.merge_params).to eq({}) + expect(mr_merge_if_green_enabled.merge_user).to be nil + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7ee4488521d..18b2659c1f6 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -17,7 +17,8 @@ describe MergeRequests::RefreshService do source_project: @project, source_branch: 'master', target_branch: 'feature', - target_project: @project) + target_project: @project, + merge_when_build_succeeds: true) @fork_merge_request = create(:merge_request, source_project: @fork_project, @@ -46,6 +47,7 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request).to be_open } + it { expect(@merge_request.merge_when_build_succeeds).to be_falsey} it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } end @@ -146,6 +148,7 @@ describe MergeRequests::RefreshService do end end + def reload_mrs @merge_request.reload @fork_merge_request.reload diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 35912ece644..5d41a5cdc69 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -208,18 +208,28 @@ describe SystemNoteService do end describe '.merge_when_build_succeeds' do - let(:ci_commit) { create :ci_commit, gl_project: project } - let(:merge_request) { create :merge_request, project: project } + let(:ci_commit) { create :ci_commit_without_jobs } + let(:noteable) { create :merge_request } - subject { described_class.merge_when_build_succeeds(merge_request, project, author) } + subject { described_class.merge_when_build_succeeds(noteable, project, author, ci_commit) } it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - allow(merge_request).to receive(:ci_commit).and_return(ci_commit) - allow(ci_commit).to receive(:short_sha).and_return('12345678') + expect(subject.note).to eq "Enabled an automatic merge when the build for 97de212e80737a608d939f648d959671fb0a0142 succeeds" + end + end - expect(subject.note).to eq "This merge request will be automatically merged when the build for 12345678 succeeds" + describe '.cancel_merge_when_build_succeeds' do + let(:ci_commit) { create :ci_commit_without_jobs } + let(:noteable) { create :merge_request } + + subject { described_class.cancel_merge_when_build_succeeds(noteable, project, author) } + + it_behaves_like 'a system note' + + it "posts the Merge When Build Succeeds system note" do + expect(subject.note).to eq "Canceled the automatic merge" end end From 25907ebe476a24bfdd2c451f18227d4fcf314b07 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 2 Dec 2015 13:28:21 +0100 Subject: [PATCH 009/102] Doc feature Merge When Build Succeeds --- .../disable_merge_when_build_succeeds.png | Bin 0 -> 20551 bytes .../enable_merge_when_build_succeeds.png | Bin 0 -> 13150 bytes doc/workflow/merge_when_build_succeeds.md | 20 ++++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 doc/workflow/merge_requests/disable_merge_when_build_succeeds.png create mode 100644 doc/workflow/merge_requests/enable_merge_when_build_succeeds.png create mode 100644 doc/workflow/merge_when_build_succeeds.md diff --git a/doc/workflow/merge_requests/disable_merge_when_build_succeeds.png b/doc/workflow/merge_requests/disable_merge_when_build_succeeds.png new file mode 100644 index 0000000000000000000000000000000000000000..a45a4890b6249d6b8ad24408dece3f57873a24ff GIT binary patch literal 20551 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/merge_requests/enable_merge_when_build_succeeds.png b/doc/workflow/merge_requests/enable_merge_when_build_succeeds.png new file mode 100644 index 0000000000000000000000000000000000000000..62a46c9508b582f82c1e8f3cb5c469f7216f5d01 GIT binary patch literal 13150 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 Date: Thu, 3 Dec 2015 10:27:34 +0100 Subject: [PATCH 010/102] Incorporate feedback --- app/models/merge_request.rb | 46 ++++++++++--------- .../merge_when_build_succeeds_service.rb | 9 ++-- app/services/system_note_service.rb | 6 +-- .../merge_requests/_merge_request.html.haml | 5 +- .../merge_requests/widget/_heading.html.haml | 9 ++-- .../widget/open/_accept.html.haml | 3 +- .../open/_merge_when_build_succeeds.html.haml | 24 +++++----- spec/factories/merge_requests.rb | 5 ++ .../merge_when_build_succeeds_spec.rb | 21 +++++++-- spec/models/merge_request_spec.rb | 44 ++++++++++++++---- spec/requests/api/merge_requests_spec.rb | 2 +- .../merge_when_build_succeeds_service_spec.rb | 30 +++++++----- .../merge_requests/refresh_service_spec.rb | 3 +- spec/services/system_note_service_spec.rb | 10 ++-- 14 files changed, 134 insertions(+), 83 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 131dfda6b5f..1f81e23c7a3 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -2,25 +2,28 @@ # # Table name: merge_requests # -# id :integer not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null -# source_project_id :integer not null -# author_id :integer -# assignee_id :integer -# title :string(255) -# created_at :datetime -# updated_at :datetime -# milestone_id :integer -# state :string(255) -# merge_status :string(255) -# target_project_id :integer not null -# iid :integer -# description :text -# position :integer default(0) -# locked_at :datetime -# updated_by_id :integer -# merge_error :string(255) +# id :integer not null, primary key +# target_branch :string(255) not null +# source_branch :string(255) not null +# source_project_id :integer not null +# author_id :integer +# assignee_id :integer +# title :string(255) +# created_at :datetime +# updated_at :datetime +# milestone_id :integer +# state :string(255) +# merge_status :string(255) +# target_project_id :integer not null +# iid :integer +# description :text +# position :integer default(0) +# locked_at :datetime +# updated_by_id :integer +# merge_error :string(255) +# merge_params :text (serialized to hash) +# merge_when_build_succeeds :boolean default(false), not null +# merge_user_id :integer # require Rails.root.join("app/models/commit") @@ -124,6 +127,7 @@ class MergeRequest < ActiveRecord::Base validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true + validates :merge_user, presence: true, if: :merge_when_build_succeeds? validate :validate_branches validate :validate_fork @@ -496,8 +500,6 @@ class MergeRequest < ActiveRecord::Base end def ci_commit - if last_commit - source_project.ci_commit(last_commit.id) - end + @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit end end diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 2f101e53a3f..5cf7404a493 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -1,6 +1,6 @@ module MergeRequests class MergeWhenBuildSucceedsService < MergeRequests::BaseService - # Marks the passed `merge_request` to be marked when the build succeeds or + # Marks the passed `merge_request` to be merged when the build succeeds or # updates the params for the automatic merge def execute(merge_request) merge_request.merge_params.merge!(params) @@ -12,7 +12,7 @@ module MergeRequests merge_request.merge_when_build_succeeds = true merge_request.merge_user = @current_user - SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.ci_commit) + SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.last_commit) end merge_request.save @@ -25,8 +25,7 @@ module MergeRequests merge_requests.each do |merge_request| next unless merge_request.merge_when_build_succeeds? - ci_commit = merge_request.ci_commit - if ci_commit && ci_commit.success? && merge_request.mergeable? + if merge_request.ci_commit && merge_request.ci_commit.success? && merge_request.mergeable? MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) end end @@ -34,7 +33,7 @@ module MergeRequests # Cancels the automatic merge def cancel(merge_request) - if merge_request.merge_when_build_succeeds? && merge_request.open? && !merge_request.merged? + if merge_request.merge_when_build_succeeds? && merge_request.open? merge_request.reset_merge_when_build_succeeds SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index ed557fef814..f84e480ca9c 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -131,15 +131,15 @@ class SystemNoteService end # Called when 'merge when build succeeds' is executed - def self.merge_when_build_succeeds(noteable, project, author, ci_commit) - body = "Enabled an automatic merge when the build for #{ci_commit.sha} succeeds" + def self.merge_when_build_succeeds(noteable, project, author, last_commit) + body = "Enabled an automatic merge when the build for #{last_commit.to_reference} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when 'merge when build succeeds' is canceled def self.cancel_merge_when_build_succeeds(noteable, project, author) - body = "Canceled the automatic merge" + body = "Cancelled the automatic merge" create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index c5234c0618c..d10ccb30571 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,4 +1,3 @@ -- ci_commit = merge_request.ci_commit %li{ class: mr_css_classes(merge_request) } .merge-request-title %span.merge-request-title-text @@ -7,8 +6,8 @@ - merge_request.labels.each do |label| = link_to_label(label, project: merge_request.project) .pull-right.light - - if ci_commit - = render_ci_status(ci_commit) + - if merge_request.ci_commit + = render_ci_status(merge_request.ci_commit) - if merge_request.merged? %span %i.fa.fa-check diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index ba5ad22bca7..9513a18f105 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,13 +1,12 @@ -- ci_commit = @merge_request.ci_commit -- if ci_commit - - status = ci_commit.status +- if @merge_request.ci_commit + - status = @merge_request.ci_commit.status .mr-widget-heading .ci_widget{class: "ci-#{status}"} - = ci_status_icon(ci_commit) + = ci_status_icon(@merge_request.ci_commit) %span CI build #{status} for #{@merge_request.last_commit_short_sha}. %span.ci-coverage - = link_to "View build details", ci_status_path(ci_commit) + = link_to "View build details", ci_status_path(@merge_request.ci_commit) - elsif @merge_request.has_ci? - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 279e2ec91f8..f7d872aa455 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -4,8 +4,7 @@ = hidden_field_tag :authenticity_token, form_authenticity_token .accept-merge-holder.clearfix.js-toggle-container .accept-action - - ci_commit = @merge_request.ci_commit - - if ci_commit && ci_commit.active? + - if @merge_request.ci_commit && @merge_request.ci_commit.active? = f.button class: "btn btn-create btn-grouped merge_when_build_succeeds", name: "merge_when_build_succeeds" do Merge When Build Succeeds = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index 43ba49c5a5e..9788d68b54e 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -1,27 +1,27 @@ %h4 - Approved by #{link_to_member(@project, @merge_request.merge_user, avatar: true)} + Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)} to be merged automatically when #{link_to "the build", ci_status_path(@merge_request.ci_commit)} succeeds. %div - - source_branch_removed = @merge_request.merge_params["should_remove_source_branch"].present? - - if source_branch_removed + - should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present? + %p = succeed '.' do The changes will be merged into %span.label-branch= @merge_request.target_branch - The source branch will be removed. - - else - %p - = succeed '.' do - The changes will be merged into - %span.label-branch= @merge_request.target_branch + - if should_remove_source_branch + The source branch will be removed. + - else The source branch will not be removed. - - if (@merge_request.can_remove_source_branch?(current_user) && !source_branch_removed) || @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + - remove_source_branch_button = @merge_request.can_remove_source_branch?(current_user) && !should_remove_source_branch + - user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + - if remove_source_branch_button || user_can_cancel_automatic_merge .clearfix.prepend-top-10 - - if @merge_request.can_remove_source_branch?(current_user) && !source_branch_removed + - if remove_source_branch_button = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = icon('times') Remove Source Branch When Merged - - if @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + + - if user_can_cancel_automatic_merge = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do Cancel Automatic Merge diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 729a49c9f72..5b4d7f41bc4 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -65,6 +65,11 @@ FactoryGirl.define do target_branch "master" end + trait :merge_when_build_succeeds do + merge_when_build_succeeds true + merge_user author + end + factory :closed_merge_request, traits: [:closed] factory :reopened_merge_request, traits: [:reopened] factory :merge_request_with_diffs, traits: [:with_diffs] diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index b25a3f05e29..2e64e903d1e 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -1,4 +1,6 @@ require 'spec_helper' +# rubocop:disable Lint/UselessAssignment +# As rubocop doesn't see a need for both `ci_commit` and `ci_build` feature 'Merge When Build Succeeds', feature: true, js: true do let(:user) { create(:user) } @@ -32,16 +34,20 @@ feature 'Merge When Build Succeeds', feature: true, js: true do it 'activates Merge When Build Succeeds feature' do expect(page).to have_link "Cancel Automatic Merge" - expect(page).to have_content "Approved by #{user.name} to be merged automatically when the build succeeds." + expect(page).to have_content "Set by #{user.name} to be merged automatically when the build succeeds." expect(page).to have_content "The source branch will not be removed." + + visit_merge_request(merge_request) # Needed to refresh the page + expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i end end end context 'When it is enabled' do - # No clue how, but push a new commit to the branch - let(:merge_request) { create(:merge_request_with_diffs, source_project: project, # source_branch: "mepmep", - author: user, title: "Bug NS-04", merge_when_build_succeeds: true) } + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, author: user, + merge_user: user, title: "MepMep", merge_when_build_succeeds: true) + end before do merge_request.source_project.team << [user, :master] @@ -60,10 +66,16 @@ feature 'Merge When Build Succeeds', feature: true, js: true do click_link "Cancel Automatic Merge" expect(page).to have_button "Merge When Build Succeeds" + + visit_merge_request(merge_request) # Needed to refresh the page + expect(page).to have_content "Cancelled the automatic merge" end it "allows the user to remove the source branch" do expect(page).to have_link "Remove Source Branch When Merged" + + click_link "Remove Source Branch When Merged" + expect(page).to have_content "The source branch will be removed" end end @@ -78,3 +90,4 @@ feature 'Merge When Build Succeeds', feature: true, js: true do visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) end end +# rubocop:enable Lint/UselessAssignment diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c7a9765825e..63e1cd1fb92 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -48,6 +48,24 @@ describe MergeRequest do describe 'validation' do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } + + context "Validation of merge user with Merge When Build succeeds" do + it "allows user to be nil when the feature is disabled" do + expect(subject).to be_valid + end + + it "is invalid without merge user" do + subject.merge_when_build_succeeds = true + expect(subject).not_to be_valid + end + + it "is valid with merge user" do + subject.merge_when_build_succeeds = true + subject.merge_user = build(:user) + + expect(subject).to be_valid + end + end end describe 'respond to' do @@ -175,31 +193,41 @@ describe MergeRequest do end describe '#can_remove_source_branch' do - let(:user) { build(:user)} + let(:user) { create(:user) } + let(:user2) { create(:user) } before do subject.source_project.team << [user, :master] + + subject.source_branch = "feature" + subject.target_branch = "master" + subject.save! end - it "cant be merged when its a a protected branch" do - subject.source_project.protected_branches = []; - + it "can't be removed when its a protected branch" do + allow(subject.source_project).to receive(:protected_branch?).and_return(true) expect(subject.can_remove_source_branch?(user)).to be_falsey end it "cant remove a root ref" do - subject.source_branch = "master"; + subject.source_branch = "master" + subject.target_branch = "feature" expect(subject.can_remove_source_branch?(user)).to be_falsey end - it "is truthy in all other cases" do - expect(subject.can_remove_source_branch?(user)) + it "is unable to remove the source branch for a project the user cannot push to" do + expect(subject.can_remove_source_branch?(user2)).to be_falsey + end + + it "is can be removed in all other cases" do + expect(subject.can_remove_source_branch?(user)).to be_truthy end end describe "#reset_merge_when_build_succeeds" do - let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true } + let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user) } + it "sets the item to false" do merge_if_green.reset_merge_when_build_succeeds merge_if_green.reload diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 91ae2059e95..6b7a066ac40 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -303,7 +303,7 @@ describe API::API, api: true do end describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do - let (:ci_commit) { create(:ci_commit_without_jobs) } + let(:ci_commit) { create(:ci_commit_without_jobs) } it "should return merge_request in case of success" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 8638539173b..a62d88cde86 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -3,37 +3,38 @@ require 'spec_helper' describe MergeRequests::MergeWhenBuildSucceedsService do let(:user) { create(:user) } let(:merge_request) { create(:merge_request) } - let(:mr_merge_if_green_enabled) { create(:merge_request, merge_when_build_succeeds: true, - source_branch: "source_branch", target_branch: project.default_branch, - source_project: project, target_project: project, state: "opened") } - let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch) } + + let(:mr_merge_if_green_enabled) do + create(:merge_request, merge_when_build_succeeds: true, merge_user: user, + source_branch: "source_branch", target_branch: project.default_branch, + source_project: project, target_project: project, state: "opened") + end + let(:project) { create(:project) } + let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch, gl_project: project) } let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') } - before do - project.ci_commits = [ci_commit] - project.save! - end describe "#execute" do context 'first time enabling' do before do allow(merge_request).to receive(:ci_commit).and_return(ci_commit) + service.execute(merge_request) end it 'sets the params, merge_user, and flag' do - service.execute(merge_request) - expect(merge_request).to be_valid expect(merge_request.merge_when_build_succeeds).to be_truthy expect(merge_request.merge_params).to eq commit_message: 'Awesome message' expect(merge_request.merge_user).to be user + end + it 'creates a system note' do note = merge_request.notes.last - expect(note.note).to include 'Enabled an automatic merge when the build for' + expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/ end end - context 'allready approved' do + context 'already approved' do let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, new_key: true) } let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } @@ -74,5 +75,10 @@ describe MergeRequests::MergeWhenBuildSucceedsService do expect(mr_merge_if_green_enabled.merge_params).to eq({}) expect(mr_merge_if_green_enabled.merge_user).to be nil end + + it 'Posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'Cancelled the automatic merge' + end end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 18b2659c1f6..9a8174f95fd 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -18,7 +18,8 @@ describe MergeRequests::RefreshService do source_branch: 'master', target_branch: 'feature', target_project: @project, - merge_when_build_succeeds: true) + merge_when_build_succeeds: true, + merge_user: @user) @fork_merge_request = create(:merge_request, source_project: @fork_project, diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 5d41a5cdc69..333035f2d2c 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -208,20 +208,20 @@ describe SystemNoteService do end describe '.merge_when_build_succeeds' do - let(:ci_commit) { create :ci_commit_without_jobs } + let(:ci_commit) { build :ci_commit_without_jobs } let(:noteable) { create :merge_request } - subject { described_class.merge_when_build_succeeds(noteable, project, author, ci_commit) } + subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.last_commit) } it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to eq "Enabled an automatic merge when the build for 97de212e80737a608d939f648d959671fb0a0142 succeeds" + expect(subject.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-f]{40} succeeds/ end end describe '.cancel_merge_when_build_succeeds' do - let(:ci_commit) { create :ci_commit_without_jobs } + let(:ci_commit) { build :ci_commit_without_jobs } let(:noteable) { create :merge_request } subject { described_class.cancel_merge_when_build_succeeds(noteable, project, author) } @@ -229,7 +229,7 @@ describe SystemNoteService do it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to eq "Canceled the automatic merge" + expect(subject.note).to eq "Cancelled the automatic merge" end end From 88217029fc37e972fd84aec7eb9a77247a9532bf Mon Sep 17 00:00:00 2001 From: Eirik Lygre Date: Sun, 6 Dec 2015 20:48:04 +0100 Subject: [PATCH 011/102] When rendering the clone page, check user profile to decide default clone protocol. If the user has uploaded SSH-keys, use SSH; otherwise, use http(s). Close #3504. --- app/helpers/projects_helper.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 48729e5260e..22db8d860e5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -175,11 +175,21 @@ module ProjectsHelper end def default_url_to_repo(project = @project) - current_user ? project.url_to_repo : project.http_url_to_repo + if default_clone_protocol == "ssh" + project.ssh_url_to_repo + else + project.http_url_to_repo + end end def default_clone_protocol - current_user ? "ssh" : "http" + if !current_user + "http" + elsif current_user.require_ssh_key? + "http" + else + "ssh" + end end def project_last_activity(project) From 46278ec7ba7c618acaf7381ad466742ce84e33db Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Dec 2015 09:59:04 +0100 Subject: [PATCH 012/102] Button fix --- app/services/system_note_service.rb | 2 +- .../widget/open/_accept.html.haml | 33 ++++++++++++++++--- .../merge_when_build_succeeds_spec.rb | 2 +- spec/models/merge_request_spec.rb | 2 +- .../merge_when_build_succeeds_service_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 2 +- 6 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index f84e480ca9c..6d15a49145d 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -139,7 +139,7 @@ class SystemNoteService # Called when 'merge when build succeeds' is canceled def self.cancel_merge_when_build_succeeds(noteable, project, author) - body = "Cancelled the automatic merge" + body = "Canceled the automatic merge" create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index f7d872aa455..c2badf342db 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -5,10 +5,22 @@ .accept-merge-holder.clearfix.js-toggle-container .accept-action - if @merge_request.ci_commit && @merge_request.ci_commit.active? - = f.button class: "btn btn-create btn-grouped merge_when_build_succeeds", name: "merge_when_build_succeeds" do - Merge When Build Succeeds - = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do - Accept Merge Request Now + %span.btn-group + = link_to "#", class: "btn btn-create merge_when_build_succeeds" do + Merge When Build Succeeds + %a.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %span.caret + %span.sr-only + Select Merge Moment + %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } + %li + = link_to "#", class: "merge_when_build_succeeds" do + = icon('check fw') + Merge When Build Succeeds + %li + = link_to "#", class: "accept_merge_request" do + = icon('warning fw') + Accept Merge Request Now - else = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do Accept Merge Request @@ -26,6 +38,8 @@ text: @merge_request.merge_commit_message, rows: 14, hint: true + = hidden_field_tag :merge_when_build_succeeds, "" + :javascript $('.accept_merge_request').on('click', function() { $(this).html(" Merge in progress"); @@ -34,3 +48,14 @@ $('.accept-mr-form').on('ajax:send', function() { $(".accept-mr-form :input").disable(); }); + + $('a.accept_merge_request').on('click', function(e) { + e.preventDefault(); + $(this).closest("form").submit(); + }); + + $('a.merge_when_build_succeeds').on('click', function(e) { + e.preventDefault(); + $("#merge_when_build_succeeds").val("1"); + $(this).closest("form").submit(); + }); diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 2e64e903d1e..b5f319f2040 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -68,7 +68,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).to have_button "Merge When Build Succeeds" visit_merge_request(merge_request) # Needed to refresh the page - expect(page).to have_content "Cancelled the automatic merge" + expect(page).to have_content "Canceled the automatic merge" end it "allows the user to remove the source branch" do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 63e1cd1fb92..33acfa37fea 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -192,7 +192,7 @@ describe MergeRequest do end end - describe '#can_remove_source_branch' do + describe '#can_remove_source_branch?' do let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index a62d88cde86..188fda6211f 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -78,7 +78,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do it 'Posts a system note' do note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'Cancelled the automatic merge' + expect(note.note).to include 'Canceled the automatic merge' end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 333035f2d2c..15173cee0a2 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -229,7 +229,7 @@ describe SystemNoteService do it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to eq "Cancelled the automatic merge" + expect(subject.note).to eq "Canceled the automatic merge" end end From cfc95b828724b0d7cb07254091e675d6d8093dba Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Dec 2015 10:14:37 +0100 Subject: [PATCH 013/102] Fix schema --- db/schema.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index fb59e187625..94b87040d88 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -476,9 +476,9 @@ ActiveRecord::Schema.define(version: 20151203162133) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" t.string "title" @@ -487,13 +487,16 @@ ActiveRecord::Schema.define(version: 20151203162133) do t.integer "milestone_id" t.string "state" t.string "merge_status" - t.integer "target_project_id", null: false + t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 + t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" t.string "merge_error" + t.text "merge_params" + t.boolean "merge_when_build_succeeds", default: false, null: false + t.integer "merge_user_id" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree From 900d3a09a73b953e3271a233aa6fc4937ac9d59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20St=C3=B6ckler?= Date: Mon, 7 Dec 2015 10:28:47 +0100 Subject: [PATCH 014/102] Fix typos in integration docs --- doc/integration/bitbucket.md | 2 +- doc/integration/crowd.md | 2 +- doc/integration/github.md | 2 +- doc/integration/gitlab.md | 2 +- doc/integration/google.md | 2 +- doc/integration/saml.md | 2 +- doc/integration/twitter.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 6a0fa4ce015..63432b04432 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -30,7 +30,7 @@ Bitbucket will generate an application ID and secret key for you to use. sudo editor /etc/gitlab/gitlab.rb ``` - For instalations from source: + For installations from source: ```sh cd /home/git/gitlab diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md index 2ecc8795ac1..40d93aef2a9 100644 --- a/doc/integration/crowd.md +++ b/doc/integration/crowd.md @@ -10,7 +10,7 @@ To enable the Crowd OmniAuth provider you must register your application with Cr sudo editor /etc/gitlab/gitlab.rb ``` - For instalations from source: + For installations from source: ```sh cd /home/git/gitlab diff --git a/doc/integration/github.md b/doc/integration/github.md index b64501c2aaa..a789d2c814f 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -32,7 +32,7 @@ GitHub will generate an application ID and secret key for you to use. sudo editor /etc/gitlab/gitlab.rb ``` - For instalations from source: + For installations from source: ```sh cd /home/git/gitlab diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md index 216f1f11a9b..80e3c0142a0 100644 --- a/doc/integration/gitlab.md +++ b/doc/integration/gitlab.md @@ -38,7 +38,7 @@ GitLab.com will generate an application ID and secret key for you to use. sudo editor /etc/gitlab/gitlab.rb ``` - For instalations from source: + For installations from source: ```sh cd /home/git/gitlab diff --git a/doc/integration/google.md b/doc/integration/google.md index e1c14c7c948..91e9b2495cc 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -35,7 +35,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application sudo editor /etc/gitlab/gitlab.rb ``` - For instalations from source: + For installations from source: ```sh cd /home/git/gitlab diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 4aa6dbe758a..1b8c28dd0f4 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -14,7 +14,7 @@ First configure SAML 2.0 support in GitLab, then register the GitLab application sudo editor /etc/gitlab/gitlab.rb ``` - For instalations from source: + For installations from source: ```sh cd /home/git/gitlab diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md index 1350c8f693c..52ed4a22339 100644 --- a/doc/integration/twitter.md +++ b/doc/integration/twitter.md @@ -37,7 +37,7 @@ To enable the Twitter OmniAuth provider you must register your application with sudo editor /etc/gitlab/gitlab.rb ``` - For instalations from source: + For installations from source: ```sh cd /home/git/gitlab From 6eb75ed6ad2710126b577f8093e78faff64bae1b Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Dec 2015 11:29:23 +0100 Subject: [PATCH 015/102] Fix specs --- .../merge_requests/merge_when_build_succeeds_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index b5f319f2040..3a8f0a344e8 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -23,12 +23,12 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end it 'displays the Merge When Build Succeeds button' do - expect(page).to have_button "Merge When Build Succeeds" + expect(page).to have_link "Merge When Build Succeeds" end context "Merge When Build succeeds enabled" do before do - click_button "Merge When Build Succeeds" + click_link "Merge When Build Succeeds" end it 'activates Merge When Build Succeeds feature' do @@ -65,7 +65,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do it 'cancels the automatic merge' do click_link "Cancel Automatic Merge" - expect(page).to have_button "Merge When Build Succeeds" + expect(page).to have_link "Merge When Build Succeeds" visit_merge_request(merge_request) # Needed to refresh the page expect(page).to have_content "Canceled the automatic merge" @@ -82,7 +82,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do context 'Build is not active' do it "should not allow for enabling" do visit_merge_request(merge_request) - expect(page).not_to have_button "Merge When Build Succeeds" + expect(page).not_to have_link "Merge When Build Succeeds" end end From 96520227ae6fc5b02b0725bc515511960bcfefed Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Dec 2015 11:29:48 +0100 Subject: [PATCH 016/102] Prevent Firefox from remembering hidden field value --- app/views/projects/merge_requests/widget/open/_accept.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index c2badf342db..0fe3af97b70 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -38,7 +38,7 @@ text: @merge_request.merge_commit_message, rows: 14, hint: true - = hidden_field_tag :merge_when_build_succeeds, "" + = hidden_field_tag :merge_when_build_succeeds, "", autocomplete: "off" :javascript $('.accept_merge_request').on('click', function() { From 4c8666e69fc64200bdd2f5069bbc8c9b22fe12ab Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Dec 2015 11:30:09 +0100 Subject: [PATCH 017/102] Fix commit message textarea position --- .../widget/open/_accept.html.haml | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 0fe3af97b70..6b4395fe4dc 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -3,37 +3,38 @@ = form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f| = hidden_field_tag :authenticity_token, form_authenticity_token .accept-merge-holder.clearfix.js-toggle-container - .accept-action - - if @merge_request.ci_commit && @merge_request.ci_commit.active? - %span.btn-group - = link_to "#", class: "btn btn-create merge_when_build_succeeds" do - Merge When Build Succeeds - %a.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' } - %span.caret - %span.sr-only - Select Merge Moment - %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } - %li - = link_to "#", class: "merge_when_build_succeeds" do - = icon('check fw') - Merge When Build Succeeds - %li - = link_to "#", class: "accept_merge_request" do - = icon('warning fw') - Accept Merge Request Now - - else - = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do - Accept Merge Request - - if @merge_request.can_remove_source_branch?(current_user) - .accept-control.checkbox - = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do - = check_box_tag :should_remove_source_branch - Remove source branch - .accept-control.right - = link_to "#", class: "modify-merge-commit-link js-toggle-button" do - = icon('edit') - Modify commit message - .js-toggle-content.hide.prepend-top-20 + .clearfix + .accept-action + - if @merge_request.ci_commit && @merge_request.ci_commit.active? + %span.btn-group + = link_to "#", class: "btn btn-create merge_when_build_succeeds" do + Merge When Build Succeeds + %a.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %span.caret + %span.sr-only + Select Merge Moment + %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } + %li + = link_to "#", class: "merge_when_build_succeeds" do + = icon('check fw') + Merge When Build Succeeds + %li + = link_to "#", class: "accept_merge_request" do + = icon('warning fw') + Merge Immediately + - else + = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do + Accept Merge Request + - if @merge_request.can_remove_source_branch?(current_user) + .accept-control.checkbox + = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do + = check_box_tag :should_remove_source_branch + Remove source branch + .accept-control.right + = link_to "#", class: "modify-merge-commit-link js-toggle-button" do + = icon('edit') + Modify commit message + .js-toggle-content.hide.prepend-top-default = render 'shared/commit_message_container', params: params, text: @merge_request.merge_commit_message, rows: 14, hint: true From e7969d6f6c743ead94b3b430b9184ad21f647337 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Dec 2015 11:54:07 +0100 Subject: [PATCH 018/102] Satisfy Douwe Maan --- CHANGELOG | 2 +- app/services/system_note_service.rb | 2 +- .../merge_when_build_succeeds_spec.rb | 14 ++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6c2fa75899c..163a0ea54e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) -- Merge when build succeeds (Zeger-Jan van de Weg) + - Merge when build succeeds (Zeger-Jan van de Weg) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 7cb9417b13e..6975b2ee55b 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -132,7 +132,7 @@ class SystemNoteService # Called when 'merge when build succeeds' is executed def self.merge_when_build_succeeds(noteable, project, author, last_commit) - body = "Enabled an automatic merge when the build for #{last_commit.to_reference} succeeds" + body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 3a8f0a344e8..ee2fb25e9e5 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -1,6 +1,4 @@ require 'spec_helper' -# rubocop:disable Lint/UselessAssignment -# As rubocop doesn't see a need for both `ci_commit` and `ci_build` feature 'Merge When Build Succeeds', feature: true, js: true do let(:user) { create(:user) } @@ -14,10 +12,10 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end context "Active build for Merge Request" do - before do - ci_commit = create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) - ci_build = create(:ci_build, commit: ci_commit) + let!(:ci_commit) { create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } + let!(:ci_build) { create(:ci_build, commit: ci_commit) } + before do login_as user visit_merge_request(merge_request) end @@ -49,14 +47,15 @@ feature 'Merge When Build Succeeds', feature: true, js: true do merge_user: user, title: "MepMep", merge_when_build_succeeds: true) end + let!(:ci_commit) { create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } + let!(:ci_build) { create(:ci_build, commit: ci_commit) } + before do merge_request.source_project.team << [user, :master] merge_request.source_branch = "feature" merge_request.target_branch = "master" merge_request.save! - ci_commit = create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) - ci_build = create(:ci_build, commit: ci_commit) login_as user visit_merge_request(merge_request) @@ -90,4 +89,3 @@ feature 'Merge When Build Succeeds', feature: true, js: true do visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) end end -# rubocop:enable Lint/UselessAssignment From a426fb1596978221510c8c78a17703658ad7d161 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 7 Dec 2015 14:11:13 +0100 Subject: [PATCH 019/102] Update documentation about automatic issue closing --- config/gitlab.yml.example | 2 +- doc/customization/issue_closing.md | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1da42ab38f3..db378118f85 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -76,7 +76,7 @@ production: &base # This happens when the commit is pushed or merged into the default branch of a project. # When not specified the default issue_closing_pattern as specified below will be used. # Tip: you can test your closing pattern at http://rubular.com. - # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' + # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' ## Default project features settings default_projects_features: diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md index 64f128f5a63..00edfc97ed9 100644 --- a/doc/customization/issue_closing.md +++ b/doc/customization/issue_closing.md @@ -1,38 +1,39 @@ # Issue closing pattern -Here's how to close multiple issues in one commit message: +When a commit or merge request resolves one or more issues, it is possible to automatically have these issues closed when the commit or merge request lands in the project's default branch. -If a commit message matches the regular expression below, all issues referenced from -the matched text will be closed. This happens when the commit is pushed or merged -into the default branch of a project. +If a commit message or merge request description contains a sentence matching the regular expression below, all issues referenced from +the matched text will be closed. This happens when the commit is pushed to a project's default branch, or when a commit or merge request is merged into there. -When not specified, the default issue_closing_pattern as shown below will be used: +When not specified, the default `issue_closing_pattern` as shown below will be used: ```bash -((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+) +((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+) ``` +Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that matches a reference to a local issue (`#123`), cross-project issue (`group/project#123`) or a link to an issue (`https://gitlab.example.com/group/project/issues/123`). + For example: ``` -git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes #22). This commit is also related to #17 and fixes #18, #19 and #23." +git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23." ``` -will close `#20`, `#21`, `#22`, `#18`, `#19` and `#23`, but `#17` won't be closed -as it does not match the pattern. It also works with multiline commit messages. +will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages. Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site to test your own patterns. +Because Rubular doesn't understand `%{issue_ref}`, you can replace this by `#\d+` in testing, which matches only local issue references like `#123`. ## Change the pattern For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`: ``` -issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' +issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' ``` -For manual installs you can customize the pattern in [gitlab.yml][0]. +For manual installs you can customize the pattern in [gitlab.yml][0] using the `issue_closing_pattern` key. -[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/40c3675372320febf5264061c9bcd63db2dfd13c/config/gitlab.yml.example#L65 -[1]: http://rubular.com/r/Xmbexed1OJ \ No newline at end of file +[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example +[1]: http://rubular.com/r/Xmbexed1OJ From 5cc2315ee8beeed3af251e67108d86ae65d35109 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 7 Dec 2015 19:06:28 +0100 Subject: [PATCH 020/102] Fix migrations for postgres on test environment Signed-off-by: Dmitriy Zaporozhets --- db/migrate/20121220064453_init_schema.rb | 74 +++++++++---------- ...140122112253_create_merge_request_diffs.rb | 9 ++- .../20140903115954_migrate_to_new_shell.rb | 2 + 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/db/migrate/20121220064453_init_schema.rb b/db/migrate/20121220064453_init_schema.rb index 90f5eb08e8c..d7644b6847a 100644 --- a/db/migrate/20121220064453_init_schema.rb +++ b/db/migrate/20121220064453_init_schema.rb @@ -1,6 +1,6 @@ class InitSchema < ActiveRecord::Migration def up - + create_table "events", force: true do |t| t.string "target_type" t.integer "target_id" @@ -12,14 +12,14 @@ class InitSchema < ActiveRecord::Migration t.integer "action" t.integer "author_id" end - + add_index "events", ["action"], name: "index_events_on_action", using: :btree add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree - + create_table "issues", force: true do |t| t.string "title" t.integer "assignee_id" @@ -33,7 +33,7 @@ class InitSchema < ActiveRecord::Migration t.text "description" t.integer "milestone_id" end - + add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["closed"], name: "index_issues_on_closed", using: :btree @@ -41,7 +41,7 @@ class InitSchema < ActiveRecord::Migration add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree add_index "issues", ["title"], name: "index_issues_on_title", using: :btree - + create_table "keys", force: true do |t| t.integer "user_id" t.datetime "created_at" @@ -51,11 +51,11 @@ class InitSchema < ActiveRecord::Migration t.string "identifier" t.integer "project_id" end - + add_index "keys", ["identifier"], name: "index_keys_on_identifier", using: :btree add_index "keys", ["project_id"], name: "index_keys_on_project_id", using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree - + create_table "merge_requests", force: true do |t| t.string "target_branch", null: false t.string "source_branch", null: false @@ -66,13 +66,13 @@ class InitSchema < ActiveRecord::Migration t.boolean "closed", default: false, null: false t.datetime "created_at" t.datetime "updated_at" - t.text "st_commits", limit: 2147483647 - t.text "st_diffs", limit: 2147483647 + t.text "st_commits" + t.text "st_diffs" t.boolean "merged", default: false, null: false t.integer "state", default: 1, null: false t.integer "milestone_id" end - + add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree add_index "merge_requests", ["closed"], name: "index_merge_requests_on_closed", using: :btree @@ -82,7 +82,7 @@ class InitSchema < ActiveRecord::Migration add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree - + create_table "milestones", force: true do |t| t.string "title", null: false t.integer "project_id", null: false @@ -92,10 +92,10 @@ class InitSchema < ActiveRecord::Migration t.datetime "created_at" t.datetime "updated_at" end - + add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree - + create_table "namespaces", force: true do |t| t.string "name", null: false t.string "path", null: false @@ -104,12 +104,12 @@ class InitSchema < ActiveRecord::Migration t.datetime "updated_at" t.string "type" end - + add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree - + create_table "notes", force: true do |t| t.text "note" t.string "noteable_type" @@ -122,13 +122,13 @@ class InitSchema < ActiveRecord::Migration t.string "commit_id" t.integer "noteable_id" end - + add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree - + create_table "projects", force: true do |t| t.string "name" t.string "path" @@ -144,17 +144,17 @@ class InitSchema < ActiveRecord::Migration t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" end - + add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["owner_id"], name: "index_projects_on_owner_id", using: :btree - + create_table "protected_branches", force: true do |t| t.integer "project_id", null: false t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" end - + create_table "services", force: true do |t| t.string "type" t.string "title" @@ -165,9 +165,9 @@ class InitSchema < ActiveRecord::Migration t.boolean "active", default: false, null: false t.string "project_url" end - + add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree - + create_table "snippets", force: true do |t| t.string "title" t.text "content" @@ -178,11 +178,11 @@ class InitSchema < ActiveRecord::Migration t.string "file_name" t.datetime "expires_at" end - + add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree - + create_table "taggings", force: true do |t| t.integer "tag_id" t.integer "taggable_id" @@ -192,14 +192,14 @@ class InitSchema < ActiveRecord::Migration t.string "context" t.datetime "created_at" end - + add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree - + create_table "tags", force: true do |t| t.string "name" end - + create_table "user_team_project_relationships", force: true do |t| t.integer "project_id" t.integer "user_team_id" @@ -207,7 +207,7 @@ class InitSchema < ActiveRecord::Migration t.datetime "created_at" t.datetime "updated_at" end - + create_table "user_team_user_relationships", force: true do |t| t.integer "user_id" t.integer "user_team_id" @@ -216,7 +216,7 @@ class InitSchema < ActiveRecord::Migration t.datetime "created_at" t.datetime "updated_at" end - + create_table "user_teams", force: true do |t| t.string "name" t.string "path" @@ -224,7 +224,7 @@ class InitSchema < ActiveRecord::Migration t.datetime "created_at" t.datetime "updated_at" end - + create_table "users", force: true do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -255,7 +255,7 @@ class InitSchema < ActiveRecord::Migration t.string "provider" t.string "username" end - + add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["blocked"], name: "index_users_on_blocked", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree @@ -263,7 +263,7 @@ class InitSchema < ActiveRecord::Migration add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree - + create_table "users_projects", force: true do |t| t.integer "user_id", null: false t.integer "project_id", null: false @@ -271,11 +271,11 @@ class InitSchema < ActiveRecord::Migration t.datetime "updated_at" t.integer "project_access", default: 0, null: false end - + add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree - + create_table "web_hooks", force: true do |t| t.string "url" t.integer "project_id" @@ -284,7 +284,7 @@ class InitSchema < ActiveRecord::Migration t.string "type", default: "ProjectHook" t.integer "service_id" end - + create_table "wikis", force: true do |t| t.string "title" t.text "content" @@ -294,10 +294,10 @@ class InitSchema < ActiveRecord::Migration t.string "slug" t.integer "user_id" end - + add_index "wikis", ["project_id"], name: "index_wikis_on_project_id", using: :btree add_index "wikis", ["slug"], name: "index_wikis_on_slug", using: :btree - + end def down diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb index ef592305a23..47bee269f9c 100644 --- a/db/migrate/20140122112253_create_merge_request_diffs.rb +++ b/db/migrate/20140122112253_create_merge_request_diffs.rb @@ -2,11 +2,16 @@ class CreateMergeRequestDiffs < ActiveRecord::Migration def change create_table :merge_request_diffs do |t| t.string :state, null: false, default: 'collected' - t.text :st_commits, null: true, limit: 2147483647 - t.text :st_diffs, null: true, limit: 2147483647 + t.text :st_commits, null: true + t.text :st_diffs, null: true t.integer :merge_request_id, null: false t.timestamps end + + if ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/ + change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647 + change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647 + end end end diff --git a/db/migrate/20140903115954_migrate_to_new_shell.rb b/db/migrate/20140903115954_migrate_to_new_shell.rb index 2d832109513..54cbe48960a 100644 --- a/db/migrate/20140903115954_migrate_to_new_shell.rb +++ b/db/migrate/20140903115954_migrate_to_new_shell.rb @@ -1,5 +1,7 @@ class MigrateToNewShell < ActiveRecord::Migration def change + return if Rails.env.test? + gitlab_shell_path = Gitlab.config.gitlab_shell.path if system("#{gitlab_shell_path}/bin/create-hooks") puts 'Repositories updated with new hooks' From 419d6fa6b19f2e2ee6429f7db453573155ca2d21 Mon Sep 17 00:00:00 2001 From: Eirik Lygre Date: Mon, 7 Dec 2015 19:24:51 +0100 Subject: [PATCH 021/102] Simplify expression per feedback from @rspeicher --- app/helpers/projects_helper.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 22db8d860e5..d061136b7b8 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -183,9 +183,7 @@ module ProjectsHelper end def default_clone_protocol - if !current_user - "http" - elsif current_user.require_ssh_key? + if !current_user || current_user.require_ssh_key? "http" else "ssh" From a17ba43bfd05cd49bab18d6c7f80226004870bc2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 13:03:28 +0100 Subject: [PATCH 022/102] Move commit builds to partial --- app/controllers/projects/commit_controller.rb | 8 +-- app/views/projects/commit/_builds.html.haml | 67 ++++++++++++++++++ app/views/projects/commit/_ci_menu.html.haml | 2 +- app/views/projects/commit/builds.html.haml | 68 +------------------ 4 files changed, 73 insertions(+), 72 deletions(-) create mode 100644 app/views/projects/commit/_builds.html.haml diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 3f137440e28..e8af205b788 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -37,7 +37,7 @@ class Projects::CommitController < Projects::ApplicationController def cancel_builds ci_commit.builds.running_or_pending.each(&:cancel) - redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha) + redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha) end def retry_builds @@ -47,7 +47,7 @@ class Projects::CommitController < Projects::ApplicationController end end - redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha) + redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha) end def branches @@ -74,8 +74,8 @@ class Projects::CommitController < Projects::ApplicationController end @notes_count = commit.notes.count - - @builds = ci_commit.builds if ci_commit + + @statuses = ci_commit.statuses if ci_commit end def authorize_manage_builds! diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml new file mode 100644 index 00000000000..e4d81182c1a --- /dev/null +++ b/app/views/projects/commit/_builds.html.haml @@ -0,0 +1,67 @@ +.gray-content-block.middle-block + .pull-right + - if @ci_project && can?(current_user, :manage_builds, @ci_commit.gl_project) + - if @ci_commit.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post + + - if @ci_commit.builds.running_or_pending.any? + = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + + .oneline + = pluralize @statuses.count(:id), "build" + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), class: "monospace" + - if @ci_commit.duration > 0 + in + = time_interval_in_words @ci_commit.duration + +- if @ci_commit.yaml_errors.present? + .bs-callout.bs-callout-danger + %h4 Found errors in your .gitlab-ci.yml: + %ul + - @ci_commit.yaml_errors.split(",").each do |error| + %li= error + +- if @ci_commit.gl_project.builds_enabled? && !@ci_commit.ci_yaml_file + .bs-callout.bs-callout-warning + \.gitlab-ci.yml not found in this commit + +.table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Ref + %th Stage + %th Name + %th Duration + %th Finished at + - if @ci_project && @ci_project.coverage_enabled? + %th Coverage + %th + - @ci_commit.refs.each do |ref| + = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, + locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true, allow_retry: true } + +- if @ci_commit.retried.any? + .gray-content-block.second-block + Retried builds + + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Ref + %th Stage + %th Name + %th Duration + %th Finished at + - if @ci_project && @ci_project.coverage_enabled? + %th Coverage + %th + = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, + locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true } diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index 76dc87a8824..f74f8b427ec 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -6,4 +6,4 @@ = nav_link(path: 'commit#builds') do = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do Builds - %span.badge= @builds.count(:id) + %span.badge= @statuses.count diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml index 00cf9c76102..99d62503a94 100644 --- a/app/views/projects/commit/builds.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -3,70 +3,4 @@ = render "commit_box" = render "ci_menu" - -- if @ci_commit.yaml_errors.present? - .bs-callout.bs-callout-danger - %h4 Found errors in your .gitlab-ci.yml: - %ul - - @ci_commit.yaml_errors.split(",").each do |error| - %li= error - -- unless @ci_commit.ci_yaml_file - .bs-callout.bs-callout-warning - \.gitlab-ci.yml not found in this commit - -.gray-content-block.second-block - Latest builds - - .pull-right - - if @ci_commit.duration > 0 - %i.fa.fa-time - #{time_interval_in_words @ci_commit.duration} - -   - - - if @ci_project && current_user && can?(current_user, :manage_builds, @project) - - if @ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-primary', method: :post - - - if @ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger', method: :post - -.table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_project && @ci_project.coverage_enabled? - %th Coverage - %th - - @ci_commit.refs.each do |ref| - = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, - locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true, allow_retry: true } - -- if @ci_commit.retried.any? - .gray-content-block.second-block - Retried builds - - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_project && @ci_project.coverage_enabled? - %th Coverage - %th - = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, - locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true } += render "builds" From 1567572e79bbeace4a68f00c01e64ed0dad9106a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 13:04:24 +0100 Subject: [PATCH 023/102] Add Builds tab to MR detail page --- .../javascripts/merge_request_tabs.js.coffee | 22 +++++++++- .../projects/merge_requests_controller.rb | 41 +++++++++++++------ .../merge_requests/_new_submit.html.haml | 8 ++++ .../projects/merge_requests/_show.html.haml | 10 ++++- .../merge_requests/show/_builds.html.haml | 1 + .../merge_requests/widget/_heading.html.haml | 2 +- config/routes.rb | 1 + 7 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 app/views/projects/merge_requests/show/_builds.html.haml diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 593a8f42130..69a12fdd045 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -43,6 +43,7 @@ # class @MergeRequestTabs diffsLoaded: false + buildsLoaded: false commitsLoaded: false constructor: (@opts = {}) -> @@ -54,6 +55,12 @@ class @MergeRequestTabs bindEvents: -> $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown + $(document).on 'click', '.js-show-tab', @showTab + + showTab: (event) => + event.preventDefault() + + @activateTab $(event.target).data('action') tabShown: (event) => $target = $(event.target) @@ -61,6 +68,8 @@ class @MergeRequestTabs if action == 'commits' @loadCommits($target.attr('href')) + else if action == 'builds' + @loadBuilds($target.attr('href')) else if action == 'diffs' @loadDiff($target.attr('href')) @@ -101,7 +110,7 @@ class @MergeRequestTabs action = 'notes' if action == 'show' # Remove a trailing '/commits' or '/diffs' - new_state = @_location.pathname.replace(/\/(commits|diffs)(\.html)?\/?$/, '') + new_state = @_location.pathname.replace(/\/(commits|builds|diffs)(\.html)?\/?$/, '') # Append the new action if we're on a tab other than 'notes' unless action == 'notes' @@ -129,6 +138,17 @@ class @MergeRequestTabs @commitsLoaded = true @scrollToElement("#commits") + loadBuilds: (source) -> + return if @buildsLoaded + + @_get + url: "#{source}.json" + success: (data) => + document.getElementById('builds').innerHTML = data.html + $('.js-timeago').timeago() + @buildsLoaded = true + @scrollToElement("#builds") + loadDiff: (source) -> return if @diffsLoaded diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3f47f2ddb2c..04642294cd3 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -1,13 +1,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ - :edit, :update, :show, :diffs, :commits, :merge, :merge_check, + :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, :ci_status, :toggle_subscription ] - before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits] - before_action :validates_merge_request, only: [:show, :diffs, :commits] - before_action :define_show_vars, only: [:show, :diffs, :commits] - before_action :ensure_ref_fetched, only: [:show, :commits, :diffs] + before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds] + before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds] + before_action :define_show_vars, only: [:show, :diffs, :commits, :builds] + before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds] # Allow read any merge_request before_action :authorize_read_merge_request! @@ -79,6 +79,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def builds + @ci_project = @merge_request.source_project.gitlab_ci_project + + respond_to do |format| + format.html { render 'show' } + format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } } + end + end + def new params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute @@ -91,20 +100,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController @target_project = merge_request.target_project @source_project = merge_request.source_project - @commits = @merge_request.compare_commits + @commits = @merge_request.compare_commits.reverse @commit = @merge_request.last_commit @first_commit = @merge_request.first_commit @diffs = @merge_request.compare_diffs + + @ci_project = @source_project.gitlab_ci_project + @ci_commit = @merge_request.ci_commit + @statuses = @ci_commit.statuses if @ci_commit + @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count end - def edit - @source_project = @merge_request.source_project - @target_project = @merge_request.target_project - @target_branches = @merge_request.target_project.repository.branch_names - end - def create @target_branches ||= [] @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute @@ -118,6 +126,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def edit + @source_project = @merge_request.source_project + @target_project = @merge_request.target_project + @target_branches = @merge_request.target_project.repository.branch_names + end + def update @merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request) @@ -264,6 +278,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request_diff = @merge_request.merge_request_diff + @ci_commit = @merge_request.ci_commit + @statuses = @ci_commit.statuses if @ci_commit + if @merge_request.locked_long_ago? @merge_request.unlock_mr @merge_request.close diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 156922cea41..f0a821d2d9f 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -23,6 +23,11 @@ = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do Commits %span.badge= @commits.size + - if @ci_commit + %li.builds-tab.active + = link_to url_for(params), data: {target: '#builds', action: 'builds', toggle: 'tab'} do + Builds + %span.badge= @statuses.size %li.diffs-tab.active = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do Changes @@ -31,6 +36,9 @@ .tab-content #commits.commits.tab-pane = render "projects/merge_requests/show/commits" + - if @ci_commit + #builds.builds.tab-pane + = render "projects/merge_requests/show/builds" #diffs.diffs.tab-pane.active - if @diffs.present? = render "projects/diffs/diffs", diffs: @diffs, project: @project diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index f5aff0877e7..79d71a784c1 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -26,8 +26,7 @@ %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.label-branch= source_branch_with_namespace(@merge_request) %span into = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do = @merge_request.target_branch @@ -51,6 +50,11 @@ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do Commits %span.badge= @commits.size + - if @ci_commit + %li.builds-tab + = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do + Builds + %span.badge= @statuses.size %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do Changes @@ -61,6 +65,8 @@ = render "projects/merge_requests/discussion" #commits.commits.tab-pane - # This tab is always loaded via AJAX + #builds.builds.tab-pane + - # This tab is always loaded via AJAX #diffs.diffs.tab-pane - # This tab is always loaded via AJAX diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml new file mode 100644 index 00000000000..307a75d02ca --- /dev/null +++ b/app/views/projects/merge_requests/show/_builds.html.haml @@ -0,0 +1 @@ += render "projects/commit/builds", link_to_commit: true diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index ba5ad22bca7..f277014f840 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -7,7 +7,7 @@ %span CI build #{status} for #{@merge_request.last_commit_short_sha}. %span.ci-coverage - = link_to "View build details", ci_status_path(ci_commit) + = link_to "View build details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'} - elsif @merge_request.has_ci? - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX diff --git a/config/routes.rb b/config/routes.rb index 3b151891a6b..38f0b16a412 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -570,6 +570,7 @@ Rails.application.routes.draw do resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do member do get :diffs + get :builds get :commits post :merge get :merge_check From c767d0e6bd95e73217a92040d568b34e14b65949 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 13:04:47 +0100 Subject: [PATCH 024/102] Change text of MR widget heading --- app/assets/stylesheets/pages/merge_requests.scss | 4 ++++ .../merge_requests/widget/_heading.html.haml | 15 ++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f21ad694d06..ab7df978768 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -81,6 +81,10 @@ &.ci-error { color: $gl-danger; } + + a.monospace { + color: inherit; + } } .mr-widget-body, diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index f277014f840..e94b07eaeaa 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -4,10 +4,12 @@ .mr-widget-heading .ci_widget{class: "ci-#{status}"} = ci_status_icon(ci_commit) - %span CI build #{status} - for #{@merge_request.last_commit_short_sha}. + %span Build #{status} + for + = succeed "." do + = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace" %span.ci-coverage - = link_to "View build details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'} + = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'} - elsif @merge_request.has_ci? - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX @@ -21,10 +23,13 @@ - else = icon("circle") %span CI build #{status} - for #{@merge_request.last_commit_short_sha}. + for + - commit = @merge_request.last_commit + = succeed "." do + = link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace" %span.ci-coverage - if ci_build_details_path(@merge_request) - = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" + = link_to "View details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" .ci_widget = icon("spinner spin") From af6b5437421106caf34719e37d359808b88eb45c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 13:04:59 +0100 Subject: [PATCH 025/102] Make commit and MR ref filters aware of /builds path --- lib/gitlab/markdown/commit_reference_filter.rb | 11 +++++++++++ lib/gitlab/markdown/merge_request_reference_filter.rb | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index b4036578e60..e3066a89b04 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -47,6 +47,17 @@ module Gitlab def object_link_title(commit) commit.link_title end + + def object_link_text_extras(object, matches) + extras = super + + path = matches[:path] if matches.names.include?("path") + if path == '/builds' + extras.unshift "builds" + end + + extras + end end end end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index de71fc76a9b..79d67870b14 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -24,8 +24,14 @@ module Gitlab def object_link_text_extras(object, matches) extras = super - if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs' + path = matches[:path] if matches.names.include?("path") + case path + when '/diffs' extras.unshift "diffs" + when '/builds' + extras.unshift "builds" + when '/commits' + extras.unshift "commits" end extras From b2b548de9d74b01816baca822d39f9dd543bbbf7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 14:02:34 +0100 Subject: [PATCH 026/102] Rewrite docs --- doc/workflow/README.md | 1 + .../disable_merge_when_build_succeeds.png | Bin 20551 -> 0 bytes .../enable_merge_when_build_succeeds.png | Bin 13150 -> 0 bytes doc/workflow/merge_when_build_succeeds.md | 19 +++++++----------- .../merge_when_build_succeeds/enable.png | Bin 0 -> 151112 bytes .../merge_when_build_succeeds/status.png | Bin 0 -> 180318 bytes 6 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 doc/workflow/merge_requests/disable_merge_when_build_succeeds.png delete mode 100644 doc/workflow/merge_requests/enable_merge_when_build_succeeds.png create mode 100644 doc/workflow/merge_when_build_succeeds/enable.png create mode 100644 doc/workflow/merge_when_build_succeeds/status.png diff --git a/doc/workflow/README.md b/doc/workflow/README.md index a6b4d951188..d2642495c9a 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -17,4 +17,5 @@ - [Milestones](milestones.md) - [Merge Requests](merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) +- [Merge When Build Succeeds](merge_when_build_succeeds.md) - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md) diff --git a/doc/workflow/merge_requests/disable_merge_when_build_succeeds.png b/doc/workflow/merge_requests/disable_merge_when_build_succeeds.png deleted file mode 100644 index a45a4890b6249d6b8ad24408dece3f57873a24ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20551 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 diff --git a/doc/workflow/merge_requests/enable_merge_when_build_succeeds.png b/doc/workflow/merge_requests/enable_merge_when_build_succeeds.png deleted file mode 100644 index 62a46c9508b582f82c1e8f3cb5c469f7216f5d01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13150 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/merge_when_build_succeeds/status.png b/doc/workflow/merge_when_build_succeeds/status.png new file mode 100644 index 0000000000000000000000000000000000000000..c856c7d14dc6ba2f0c89f3e6d7c0e3de39e0dcfe GIT binary patch literal 180318 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

M$^a^8jm41-_{C#PI0dj6faq1`ClUL&ToH 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 DsM0JO literal 0 HcmV?d00001 From 5beacba038c097e513b46f24ee26d5065ad419c9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 14:13:57 +0100 Subject: [PATCH 027/102] Fix spec --- features/steps/project/commits/commits.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index e5b3f27135d..0d6a9a8fc66 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -118,6 +118,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps step 'I see builds list' do expect(page).to have_content "build: pending" - expect(page).to have_content "Latest builds" + expect(page).to have_content "1 build" end end From d00d3444a45f81a5e79a1161acb44fca2df3c1c7 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 8 Dec 2015 18:23:30 +0300 Subject: [PATCH 028/102] Remove the prepended v on GitLab Workhorse upgrade doc --- doc/update/patch_versions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 957354decb7..c19ee49f9e0 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -47,7 +47,7 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch -sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` +sudo -u git -H git checkout `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` ``` ### 5. Install libs, migrations, etc. From df6750d3d6b562c8a6a0a57c12dfd694da38a0e8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 16:42:10 +0100 Subject: [PATCH 029/102] Default target branch to patch-n when editing file in protected branch --- app/controllers/projects/blob_controller.rb | 12 ++++++++++-- app/helpers/branches_helper.rb | 2 +- app/helpers/tree_helper.rb | 13 ++++++++++--- app/models/repository.rb | 11 +++++++++++ app/views/shared/_new_commit_form.html.haml | 2 +- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 31a33bfd237..62163682936 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -162,12 +162,20 @@ class Projects::BlobController < Projects::ApplicationController end def sanitized_new_branch_name - @new_branch ||= sanitize(strip_tags(params[:new_branch])) + sanitize(strip_tags(params[:new_branch])) end def editor_variables @current_branch = @ref - @new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref + + @new_branch = + if params[:new_branch].present? + sanitized_new_branch_name + elsif ::Gitlab::GitAccess.new(current_user, @project).can_push_to_branch?(@ref) + @ref + else + @repository.next_patch_branch + end @file_path = if action_name.to_s == 'create' diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index d6eaa7d57bc..e39548e17e1 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -11,7 +11,7 @@ module BranchesHelper def can_push_branch?(project, branch_name) return false unless project.repository.branch_names.include?(branch_name) - + ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) end end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 6afa1aacc5b..ac75c9a14b7 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -48,10 +48,17 @@ module TreeHelper def allowed_tree_edit?(project = nil, ref = nil) project ||= @project - ref ||= @ref - return false unless project.repository.branch_names.include?(ref) + can?(current_user, :push_code, project) + end - ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) + def tree_edit_branch(project = @project, ref = @ref) + if allowed_tree_edit? + if can_push_branch?(project, ref) + ref + else + project.repository.next_patch_branch + end + end end def can_delete_or_replace?(blob) diff --git a/app/models/repository.rb b/app/models/repository.rb index 1d43307e1e7..1edec52c09e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -329,6 +329,17 @@ class Repository commit(sha) end + def next_patch_branch + patch_branch_ids = self.branch_names.map do |n| + result = n.match(/\Apatch-([0-9]+)\z/) + result[1].to_i if result + end.compact + + highest_patch_branch_id = patch_branch_ids.max || 0 + + "patch-#{highest_patch_branch_id + 1}" + end + # Remove archives older than 2 hours def branches_sorted_by(value) case value diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml index 31b02ed93d0..074080e44c9 100644 --- a/app/views/shared/_new_commit_form.html.haml +++ b/app/views/shared/_new_commit_form.html.haml @@ -4,7 +4,7 @@ .form-group.branch = label_tag 'new_branch', 'Target branch', class: 'control-label' .col-sm-10 - = text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch" + = text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch" .form-group.js-create-merge-request-form-group .col-sm-offset-2.col-sm-10 From 82ddd738b672afd4ed28f12fb58f512fa117d2be Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 17:10:07 +0100 Subject: [PATCH 030/102] Show tooltip when trying to edit file when not on branch --- app/helpers/blob_helper.rb | 38 +++++++++---------- app/helpers/tree_helper.rb | 13 ++++--- app/services/files/base_service.rb | 2 +- app/views/projects/blob/_actions.html.haml | 14 ++++--- app/views/projects/blob/show.html.haml | 2 +- .../projects/tree/_tree_header.html.haml | 4 ++ 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 4a3d971f7c6..65bd543bf1a 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -30,26 +30,24 @@ module BlobHelper nil end - if blob_viewable?(blob) - text = 'Edit' - after = options[:after] || '' - from_mr = options[:from_merge_request_id] - link_opts = {} - link_opts[:from_merge_request_id] = from_mr if from_mr - cls = 'btn btn-small' - if allowed_tree_edit?(project, ref) - link_to(text, - namespace_project_edit_blob_path(project.namespace, project, - tree_join(ref, path), - link_opts), - class: cls - ) - else - content_tag :span, text, class: cls + ' disabled' - end + after.html_safe - else - '' - end + return unless blob && blob_editable?(blob) + + text = 'Edit' + after = options[:after] || '' + from_mr = options[:from_merge_request_id] + link_opts = {} + link_opts[:from_merge_request_id] = from_mr if from_mr + cls = 'btn btn-small' + link_to(text, + namespace_project_edit_blob_path(project.namespace, project, + tree_join(ref, path), + link_opts), + class: cls + ) + after.html_safe + end + + def blob_editable?(blob, project = @project, ref = @ref) + allowed_tree_edit?(project, ref) && !blob.lfs_pointer? end def leave_edit_message diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index ac75c9a14b7..886a1e734b5 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -46,13 +46,20 @@ module TreeHelper File.join(*args) end + def on_top_of_branch?(project = @project, ref = @ref) + project.repository.branch_names.include?(ref) + end + def allowed_tree_edit?(project = nil, ref = nil) project ||= @project + ref ||= @ref + return false unless on_top_of_branch?(project, ref) + can?(current_user, :push_code, project) end def tree_edit_branch(project = @project, ref = @ref) - if allowed_tree_edit? + if allowed_tree_edit?(project, ref) if can_push_branch?(project, ref) ref else @@ -61,10 +68,6 @@ module TreeHelper end end - def can_delete_or_replace?(blob) - allowed_tree_edit? && !blob.lfs_pointer? - end - def tree_breadcrumbs(tree, max_links = 2) if @path.present? part_path = "" diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index f50aaf2eb52..309abcd6d0b 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -53,7 +53,7 @@ module Files unless project.empty_repo? unless repository.branch_names.include?(@current_branch) - raise_error("You can only create files if you are on top of a branch") + raise_error("You can only create or edit files when you are on top of a branch") end if @current_branch != @target_branch diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 0e54e59e953..41d8765623e 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -1,5 +1,4 @@ .btn-group.tree-btn-group - = edit_blob_link(@project, @ref, @path) = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), class: 'btn btn-sm', target: '_blank' -# only show normal/blame view links for text files @@ -12,11 +11,16 @@ class: 'btn btn-sm' unless @blob.empty? = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'btn btn-sm' - - if @ref != @commit.sha - = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, - tree_join(@commit.sha, @path)), class: 'btn btn-sm' + = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, + tree_join(@commit.sha, @path)), class: 'btn btn-sm' -- if can_delete_or_replace?(@blob) +- if blob_editable?(@blob) .btn-group{ role: "group" } + = edit_blob_link(@project, @ref, @path) %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete +- elsif !on_top_of_branch? + .btn-group{ role: "group" } + %button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on top of a branch.", data: {container: 'body'}} Edit + %button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on top of a branch.", data: {container: 'body'}} Replace + %button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on top of a branch.", data: {container: 'body'}} Delete diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 09d6fc18e3e..3f8d11ed8c8 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -6,7 +6,7 @@ %div#tree-holder.tree-holder = render 'blob', blob: @blob -- if can_delete_or_replace?(@blob) +- if blob_editable?(@blob) = render 'projects/blob/remove' - title = "Replace #{@blob.name}" diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 1115ca6b4ca..4767f824ac1 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -30,3 +30,7 @@ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do = icon('folder fw') New directory + - elsif !on_top_of_branch? + %li + %span.btn.add-to-tree.disabled.has_tooltip{href: '#', title: "You can only add files when you are on top of a branch.", data: {container: 'body'}} + = icon('plus') From 7aeb7077cb906933c59e5b890fd4027005e34d3d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 17:11:23 +0100 Subject: [PATCH 031/102] Only show Edit button for text blobs. --- app/helpers/blob_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 65bd543bf1a..68e5d5be600 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -30,7 +30,7 @@ module BlobHelper nil end - return unless blob && blob_editable?(blob) + return unless blob && blob.text? && blob_editable?(blob) text = 'Edit' after = options[:after] || '' @@ -47,7 +47,7 @@ module BlobHelper end def blob_editable?(blob, project = @project, ref = @ref) - allowed_tree_edit?(project, ref) && !blob.lfs_pointer? + !blob.lfs_pointer? && allowed_tree_edit?(project, ref) end def leave_edit_message From 4f074aaa14faa8a866f18a80f58b66cd023a141f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 25 Nov 2015 14:41:14 +0100 Subject: [PATCH 032/102] Introduce CI documentation for services and languages --- doc/README.md | 12 ++ doc/ci/README.md | 12 ++ doc/ci/docker/using_docker_images.md | 115 +++++++---------- doc/ci/languages/README.md | 3 + doc/ci/languages/php.md | 178 +++++++++++++++++++++++++++ doc/ci/services/README.md | 6 + doc/ci/services/docker-services.md | 5 + doc/ci/services/mysql.md | 72 +++++++++++ doc/ci/services/postgres.md | 70 +++++++++++ doc/ci/services/redis.md | 40 ++++++ doc/ci/ssh_keys/README.md | 114 +++++++++++++++++ 11 files changed, 559 insertions(+), 68 deletions(-) create mode 100644 doc/ci/languages/README.md create mode 100644 doc/ci/languages/php.md create mode 100644 doc/ci/services/README.md create mode 100644 doc/ci/services/docker-services.md create mode 100644 doc/ci/services/mysql.md create mode 100644 doc/ci/services/postgres.md create mode 100644 doc/ci/services/redis.md create mode 100644 doc/ci/ssh_keys/README.md diff --git a/doc/README.md b/doc/README.md index 58ab5dd08e0..a7025f7af10 100644 --- a/doc/README.md +++ b/doc/README.md @@ -24,9 +24,21 @@ - [Using Docker Images](ci/docker/using_docker_images.md) - [Using Docker Build](ci/docker/using_docker_build.md) - [Using Variables](ci/variables/README.md) +- [Using SSH keys](ci/ssh_keys/README.md) - [User permissions](ci/permissions/README.md) - [API](ci/api/README.md) +### CI Languages + ++ [Testing PHP](ci/languages/php.md) + +## CI Services + ++ [Using MySQL](ci/services/mysql.md) ++ [Using PostgreSQL](ci/services/postgres.md) ++ [Using Redis](ci/services/redis.md) ++ [Using Other Services](ci/docker/using_docker_images.html#how-to-use-other-images-as-services) + ### CI Examples - [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md) diff --git a/doc/ci/README.md b/doc/ci/README.md index 97325069ceb..ae921b6f988 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -9,6 +9,18 @@ + [Using Docker Images](docker/using_docker_images.md) + [Using Docker Build](docker/using_docker_build.md) + [Using Variables](variables/README.md) ++ [Using SSH keys](ssh_keys/README.md) + +### Languages + ++ [Testing PHP](languages/php.md) + +### Services + ++ [Using MySQL](services/mysql.md) ++ [Using PostgreSQL](services/postgres.md) ++ [Using Redis](services/redis.md) ++ [Using Other Services](docker/using_docker_images.html#how-to-use-other-images-as-services) ### Examples diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 1feae62b1c7..2f0ca19cd0f 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -42,7 +42,44 @@ So, **to access your database service you have to connect to host: `mysql` inste ### How to use other images as services? You are not limited to have only database services. -You can hand modify `config.toml` to add any image as service found at [Docker Hub](https://registry.hub.docker.com/). +You can add the services to `.gitlab-ci.yml` or hand modify the `config.toml`. +You can use any image as service found at [Docker Hub](https://registry.hub.docker.com/). + +### Define image and services from `.gitlab-ci.yml` +You can simply define image or list services that you want to use for the build time. +``` +image: ruby:2.2 +services: + - postgres:9.3 +before_install: + - bundle install + +test: + script: + - bundle exec rake spec +``` + +It's possible to define image and service per-job: +``` +before_install: + - bundle install + +test:2.1: + image: ruby:2.1 + services: + - postgres:9.3 + script: + - bundle exec rake spec + +test:2.2: + image: ruby:2.2 + services: + - postgres:9.4 + script: + - bundle exec rake spec +``` + +### Define image and services in `config.toml` Look for `[runners.docker]` section: ``` [runners.docker] @@ -50,13 +87,16 @@ Look for `[runners.docker]` section: services = ["mysql:latest", "postgres:latest"] ``` +The image and services defined these way will be added to all builds run by that runner. + +### Accessing the services For example you need `wordpress` instance to test some API integration with `Wordpress`. -You can for example use this image: [tutum/wordpress](https://registry.hub.docker.com/u/tutum/wordpress/). -This is image that have fully preconfigured `wordpress` and have `MySQL` server built-in: +You can for example use this image: [tutum/wordpress](https://registry.hub.docker.com/u/tutum/wordpress/). + ``` -[runners.docker] - image = "ruby:2.1" - services = ["mysql:latest", "postgres:latest", "tutum/wordpress:latest"] +# .gitlab-ci.yml +services: +- tutum/wordpress:latest ``` Next time when you run your application the `tutum/wordpress` will be started @@ -64,7 +104,7 @@ and you will have access to it from your build container under hostname: `tutum_ Alias hostname for the service is made from the image name: 1. Everything after `:` is stripped, -2. '/' is replaced with `__`. +2. '/' is replaced to `__`. ### Configuring services Many services accept environment variables, which allow you to easily change database names or set account names depending on the environment. @@ -99,67 +139,6 @@ or README page for any other Docker image. **Note: All variables will passed to all service containers. It's not designed to distinguish which variable should go where.** -### Overwrite image and services -It's possible to overwrite `docker-image` and specify services from `.gitlab-ci.yml`. -If you add to your YAML the `image` and the `services` these parameters -be used instead of the ones that were specified during runner's registration. -``` -image: ruby:2.2 -services: - - postgres:9.3 -before_install: - - bundle install - -test: - script: - - bundle exec rake spec -``` - -It's possible to define image and service per-job: -``` -before_install: - - bundle install - -test:2.1: - image: ruby:2.1 - services: - - postgres:9.3 - script: - - bundle exec rake spec - -test:2.2: - image: ruby:2.2 - services: - - postgres:9.4 - script: - - bundle exec rake spec -``` - -#### How to enable overwriting? -To enable overwriting you have to **enable it first** (it's disabled by default for security reasons). -You can do that by hand modifying runner configuration: `config.toml`. -Please go to section where is `[runners.docker]` definition for your runner. -Add `allowed_images` and `allowed_services` to specify what images are allowed to be picked from `.gitlab-ci.yml`: -``` -[runners.docker] - image = "ruby:2.1" - allowed_images = ["ruby:*", "python:*"] - allowed_services = ["mysql:*", "redis:*"] -``` -This enables you to use in your `.gitlab-ci.yml` any image that matches above wildcards. -You will be able to pick only `ruby` and `python` images. -The same rule can be applied to limit services. - -If you are courageous enough, you can make it fully open and accept everything: -``` -[runners.docker] - image = "ruby:2.1" - allowed_images = ["*", "*/*"] - allowed_services = ["*", "*/*"] -``` - -**It the feature is not enabled, or image isn't allowed the error message will be put into the build log.** - ### How Docker integration works 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`. 1. Create cache container to store all volumes as defined in `config.toml` and `Dockerfile` of build image (`ruby:2.1` as in above example). diff --git a/doc/ci/languages/README.md b/doc/ci/languages/README.md new file mode 100644 index 00000000000..375adf58d18 --- /dev/null +++ b/doc/ci/languages/README.md @@ -0,0 +1,3 @@ +### Languages + ++ [Testing PHP](php.md) diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md new file mode 100644 index 00000000000..e0589182003 --- /dev/null +++ b/doc/ci/languages/php.md @@ -0,0 +1,178 @@ +## Testing PHP projects + +This guide covers basic of building PHP projects. + +Is it possible to test PHP apps on any system. +However, it will require manual configuration. +The simplest is to use Docker executor as described below. + +### PHP projects on Docker executor +It's possible to official [PHP](https://hub.docker.com/_/php/) repositories on Docker Hub. +They allow to test PHP projects against different versions of the runtime. +However, they require additional configuration. + +To build PHP project you need to create valid `.gitlab-ci.yml` describing the build environment: +1. First you need to specify PHP image as described here: http://doc.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-image. To your `.gitlab-ci.yml` add: + + image: php:5.6 + +2. The official images are great, but they are lacking a few useful tools for testing. We need to install them first in build environment. Create `ci/docker_install.sh` file with following content: + + #!/bin/bash + + # We need to install dependencies only for Docker + [[ ! -e /.dockerinit ]] && exit 0 + + set -xe + + # Install git (the php image doesn't have it) which is required by composer + apt-get update -yqq + apt-get install git -yqq + + # Install phpunit, the tool that we will use for testing + curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar + chmod +x /usr/local/bin/phpunit + + # Install mysql driver + # Here you can install any other extension that you need + docker-php-ext-install pdo_mysql + +3. From your `.gitlab-ci.yml` run the created script: + + before_script: + - bash ci/docker_install.sh > /dev/null + +4. Now you can run your tests. Usually it will be `phpunit` with arguments: + + test:app: + script: + - phpunit --configuration phpunit_myapp.xml --coverage-text + +5. Commit your files, and push them to GitLab to see if it works. With GitLab Runner 1.0 you can also test the changes locally. From your terminal execute: + + # Check using docker executor + gitlab-runner exec docker test:app + + # Check using shell executor + gitlab-runner exec shell test:app + +The final `.gitlab-ci.yml` should look similar to this: + + # Select image from https://hub.docker.com/_/php/ + image: php:5.6 + + before_script: + # Install dependencies + - ci/docker_install.sh > /dev/null + + test:app: + script: + - phpunit --configuration phpunit_myapp.xml --coverage-text + +#### Test against different PHP versions in Docker builds + +You can also test against multiple version of PHP runtime: + + before_script: + # Install dependencies + - ci/docker_install.sh > /dev/null + + # We test PHP5.6 + test:5.6: + image: php:5.6 + script: + - phpunit --configuration phpunit_myapp.xml --coverage-text + + # We test PHP7.0 + test:7.0: + image: php:7.0 + script: + - phpunit --configuration phpunit_myapp.xml --coverage-text + +#### Custom PHP configuration in Docker builds + +You can customise your PHP environment by putting your .ini file into `/usr/local/etc/php/conf.d/`: + + before_script: + - cp my_php.ini /usr/local/etc/php/conf.d/test.ini + +### Test PHP projects using Shell + +Shell executor runs your builds in terminal session of your server. Thus in order to test your projects you need to have all dependencies installed as root. + +1. Install PHP dependencies: + + sudo apt-get update -qy + sudo apt-get install phpunit php5-mysql -y + + This will install the PHP version available for your distribution. + +2. Now you can run your tests. Usually it will be `phpunit` with arguments: + + test:app: + script: + - phpunit --configuration phpunit_myapp.xml --coverage-text + +#### Test against different PHP versions in Shell builds + +The [phpenv](https://github.com/phpenv/phpenv) allows you to easily manage different PHP with they own configs. +This is specially usefull when testing PHP project with Shell executor. + +Login as `gitlab-runner` user and follow [the installation guide](https://github.com/phpenv/phpenv#installation). + +Using phpenv also allows to easily configure PHP environment with: `phpenv config-add my_config.ini`. + +#### Install custom extensions + +Since we have pretty bare installation of our PHP environment you may need some extensions that are not present on your installation. + +To install additional extensions simply execute.: + + pecl install + + It's not advised to add this to the `.gitlab-ci.yml`. + You should execute this command once, only to setup the build environment. + +### Extend your tests + +#### Using atoum + +Instead of PHPUnit, you can use any other tool to run unit tests. For example [atoum](https://github.com/atoum/atoum): + + before_script: + - wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar + + test:atoum: + script: + - php mageekguy.atoum.phar + +#### Using Composer + +Majority of the PHP projects use Composer for managing the packages. +It's very simple to execute the Composer before running your tests. +To your `.gitlab-ci.yml` add: + + # The composer stores all downloaded packages in vendor/ + # Remove it if you committed the vendor/ directory + cache: + paths: + - vendor/ + + before_script: + # Install composer dependencies + - curl -sS https://getcomposer.org/installer | php + - php composer.phar install + +### Access private packages / dependencies + +You need to configure [the SSH keys](../ssh_keys/README.md) in order to checkout the repositories. + +### Use databases or other services + +Please checkout the docs about configuring [the CI services](../services/README.md). + +### Example project + +You maybe interested in our [Example Project](https://gitlab.com/gitlab-examples/php) that runs on [GitLab.com](https://gitlab.com) using our publically available shared runners. + +Want to hack it? Simply fork it, commit and push changes. Within a few moments the changes will be picked and rebuilt by public runners. diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md new file mode 100644 index 00000000000..0550e9435a3 --- /dev/null +++ b/doc/ci/services/README.md @@ -0,0 +1,6 @@ +## GitLab CI Services + ++ [Using MySQL](mysql.md) ++ [Using PostgreSQL](postgres.md) ++ [Using Redis](redis.md) ++ [Using Other Services](../docker/using_docker_images.html#how-to-use-other-images-as-services) diff --git a/doc/ci/services/docker-services.md b/doc/ci/services/docker-services.md new file mode 100644 index 00000000000..df36ebaf7d4 --- /dev/null +++ b/doc/ci/services/docker-services.md @@ -0,0 +1,5 @@ +## GitLab CI Services + ++ [Using MySQL](mysql.md) ++ [Using PostgreSQL](postgres.md) ++ [Using Redis](redis.md) diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md new file mode 100644 index 00000000000..3155af6b3e1 --- /dev/null +++ b/doc/ci/services/mysql.md @@ -0,0 +1,72 @@ +## Using MySQL + +It's possible to use MySQL database test your apps during builds. + +### Use MySQL with Docker executor + +If you are using our Docker integration you basically have everything already. + +1. Add this to your `.gitlab-ci.yml`: + + services: + - mysql + + variables: + # Configure mysql service (https://hub.docker.com/_/mysql/) + MYSQL_DATABASE: hello_world_test + MYSQL_ROOT_PASSWORD: mysql + +2. Configure your application to use the database: + + Host: mysql + User: root + Password: mysql + Database: hello_world_test + +3. You can also use any other available on [DockerHub](https://hub.docker.com/_/mysql/). For example: `mysql:5.5`. + +Example: https://gitlab.com/gitlab-examples/mysql/blob/master/.gitlab-ci.yml + +### Use MySQL with Shell executor + +It's possible to use MySQL on manually configured servers that are using GitLab Runner with Shell executor. + +1. First install the MySQL server: + + sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev + + # Pick a MySQL root password (can be anything), type it and press enter + # Retype the MySQL root password and press enter + +2. Create an user: + + mysql -u root -p + + # Create a user which will be used by your apps + # do not type the 'mysql>', this is part of the prompt + # change $password in the command below to a real password you pick + mysql> CREATE USER 'runner'@'localhost' IDENTIFIED BY '$password'; + + # Ensure you can use the InnoDB engine which is necessary to support long indexes + # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off" + mysql> SET storage_engine=INNODB; + + # Create the database + mysql> CREATE DATABASE IF NOT EXISTS `hello_world_test` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; + + # Grant necessary permissions on the database + mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `hello_world_test`.* TO 'runner'@'localhost'; + + # Quit the database session + mysql> \q + +3. Try to connect to database: + + sudo -u gitlab-runner -H mysql -u runner -p -D hello_world_test + +4. Configure your application to use the database: + + Host: localhost + User: runner + Password: $password + Database: hello_world_test diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md new file mode 100644 index 00000000000..e57f8c5944a --- /dev/null +++ b/doc/ci/services/postgres.md @@ -0,0 +1,70 @@ +## Using PostgreSQL + +It's possible to use PostgreSQL database test your apps during builds. + +### Use PostgreSQL with Docker executor + +If you are using our Docker integration you basically have everything already. + +1. Add this to your `.gitlab-ci.yml`: + + services: + - postgres + + variables: + # Configure postgres service (https://hub.docker.com/_/postgres/) + POSTGRES_DB: hello_world_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: "" + +2. Configure your application to use the database: + + Host: postgres + User: postgres + Password: postgres + Database: hello_world_test + +3. You can also use any other available on [DockerHub](https://hub.docker.com/_/postgres/). For example: `postgres:9.3`. + +Example: https://gitlab.com/gitlab-examples/postgres/blob/master/.gitlab-ci.yml + +### Use PostgreSQL with Shell executor + +It's possible to use PostgreSQL on manually configured servers that are using GitLab Runner with Shell executor. + +1. First install the PostgreSQL server: + + sudo apt-get install -y postgresql postgresql-client libpq-dev + +2. Create an user: + + # Install the database packages + sudo apt-get install -y postgresql postgresql-client libpq-dev + + # Login to PostgreSQL + sudo -u postgres psql -d template1 + + # Create a user for runner + # Do not type the 'template1=#', this is part of the prompt + template1=# CREATE USER runner CREATEDB; + + # Create the database & grant all privileges on database + template1=# CREATE DATABASE hello_world_test OWNER runner; + + # Quit the database session + template1=# \q + +3. Try to connect to database: + + # Try connecting to the new database with the new user + sudo -u gitlab-runner -H psql -d hello_world_test + + # Quit the database session + hello_world_test> \q + +4. Configure your application to use the database: + + Host: localhost + User: runner + Password: + Database: hello_world_test diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md new file mode 100644 index 00000000000..523634a457e --- /dev/null +++ b/doc/ci/services/redis.md @@ -0,0 +1,40 @@ +## Using Redis + +It's possible to use Redis database test your apps during builds. + +### Use Redis with Docker executor + +If you are using our Docker integration you basically have everything already. + +1. Add this to your `.gitlab-ci.yml`: + + services: + - redis + +2. Configure your application to use the database: + + Host: redis + +3. You can also use any other available on [DockerHub](https://hub.docker.com/_/redis/). For example: `redis:2.6`. + +Example: https://gitlab.com/gitlab-examples/redis/blob/master/.gitlab-ci.yml + +### Use Redis with Shell executor + +It's possible to use Redis on manually configured servers that are using GitLab Runner with Shell executor. + +1. First install the Redis server: + + sudo apt-get install redis-server + +2. Try to connect to the server: + + # Try connecting the the Redis server + sudo -u gitlab-runner -H redis-cli + + # Quit the session + 127.0.0.1:6379> quit + +4. Configure your application to use the database: + + Host: localhost diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md new file mode 100644 index 00000000000..515194e5f5e --- /dev/null +++ b/doc/ci/ssh_keys/README.md @@ -0,0 +1,114 @@ +# Using SSH keys + +GitLab currently doesn't have built-in support for SSH keys in build environment. + +The SSH keys can be useful when: +1. You want to checkout internal submodules, +2. You want to download private packages using your package manager (ie. bundler), +3. You want to deploy your app (ex. to Heroku or own server), +4. You want to execute ssh commands from build environment on remote server, +5. You want to rsync files from your build to remote server. + +If anyone of the above holds true, then you most likely need SSH key. + +There are two possibilities to add SSH keys to build environment. + +## Inject keys in your build environment +The most widely supported is to inject SSH key into your build environment by extending your .gitlab-ci.yml. +This is the universal solution which works with any type of executor (docker, shell, etc.). + +### How it works? +1. We create a new SSH private key with [ssh-keygen](http://linux.die.net/man/1/ssh-keygen). +2. We add the private key as the Secure Variable to project. +3. We run the [ssh-agent](http://linux.die.net/man/1/ssh-agent) during build to load the private key. + +The example [.gitlab-ci.yml](https://gitlab.com/gitlab-examples/ssh-private-key/blob/master/.gitlab-ci.yml) looks like this. + +### Make it work? +1. First, go to terminal and generate a new SSH key: +```bash +$ ssh-keygen -t rsa -f my_key + +Generating public/private rsa key pair. +Enter passphrase (empty for no passphrase): +Enter same passphrase again: +Your identification has been saved in my_key. +Your public key has been saved in my_key.pub. +The key fingerprint is: +SHA256:tBJEfyJUGTMNmPCiPg4UHywHs67MxlM2iEBAlI/W+TY fingeprint +The key's randomart image is: ++---[RSA 2048]----+ +|=*. .o++*= | +|..= +o..o. | +|.+++o + + . | +|+o*=.. + + | +|o+.=. . S | +|*.o .E . | +|o*o . . | +|.o.. | +| . | ++----[SHA256]-----+ +``` + +2. Create a new **Secure Variable** in your project settings on GitLab and name it: `SSH_PRIVATE_KEY`. + +3. Copy the content of `my_key` and paste it as a **Value** of **SSH_PRIVATE_KEY**. + +4. Next you need to modify your `.gitlab-ci.yml` and at the top of the file add: +``` +before_script: +# install ssh-agent (it is required for Docker, change apt-get to yum if you use CentOS-based image) +- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + +# run ssh-agent (in build environment) +- eval $(ssh-agent -s) + +# add ssh key stored in SSH_PRIVATE_KEY variable to the agent store +- ssh-add <(echo "$SSH_PRIVATE_KEY") + +# for Docker builds disable host key checking, by adding that you are suspectible to man-in-the-middle attack +- mkdir -p ~/.ssh +- '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config` +``` + +5. Add the public key from `my_key.pub` to services that you want to have an access from build. + +6. If your builds are run using `shell` executor, you may need to login to server and execute the `ssh ` to store the fingerprint of remote server. + +## SSH keys when using Shell executor +If use `shell`, not `docker` it can be easier to have the SSH key. + +We can generate the SSH key for the machine that holds `gitlab-runner` and use that key for all projects that are run on this machine. + +1. First, login to server that runs your builds. + +2. From terminal login as `gitlab-runner` user and generate the SSH private key: +```bash +$ ssh-keygen -t rsa +Generating public/private rsa key pair. +Enter passphrase (empty for no passphrase): +Enter same passphrase again: +Your identification has been saved in ~/.ssh/id_rsa. +Your public key has been saved in ~/.ssh/id_rsa.pub. +The key fingerprint is: +SHA256:tBJEfyJUGTMNmPCiPg4UHywHs67MxlM2iEBAlI/W+TY fingeprint +The key's randomart image is: ++---[RSA 2048]----+ +|=*. .o++*= | +|..= +o..o. | +|.+++o + + . | +|+o*=.. + + | +|o+.=. . S | +|*.o .E . | +|o*o . . | +|.o.. | +| . | ++----[SHA256]-----+ +``` + +3. Add the public key from `~/.ssh/id_rsa.pub` to services that you want to have an access from build. + +4. Try to login for the first time and accept fingerprint: +```bash +ssh Date: Wed, 2 Dec 2015 17:47:18 +0200 Subject: [PATCH 033/102] Fix heading --- doc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index a7025f7af10..f85a464a4f0 100644 --- a/doc/README.md +++ b/doc/README.md @@ -32,7 +32,7 @@ + [Testing PHP](ci/languages/php.md) -## CI Services +### CI Services + [Using MySQL](ci/services/mysql.md) + [Using PostgreSQL](ci/services/postgres.md) From dcdc49fe429c0e8a762f1a411e1d5c0a7294c91f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 2 Dec 2015 19:24:15 +0200 Subject: [PATCH 034/102] Use .md instead of .html --- doc/README.md | 2 +- doc/ci/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/README.md b/doc/README.md index f85a464a4f0..a3098094210 100644 --- a/doc/README.md +++ b/doc/README.md @@ -37,7 +37,7 @@ + [Using MySQL](ci/services/mysql.md) + [Using PostgreSQL](ci/services/postgres.md) + [Using Redis](ci/services/redis.md) -+ [Using Other Services](ci/docker/using_docker_images.html#how-to-use-other-images-as-services) ++ [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services) ### CI Examples diff --git a/doc/ci/README.md b/doc/ci/README.md index ae921b6f988..5d9d7a81db3 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -20,7 +20,7 @@ + [Using MySQL](services/mysql.md) + [Using PostgreSQL](services/postgres.md) + [Using Redis](services/redis.md) -+ [Using Other Services](docker/using_docker_images.html#how-to-use-other-images-as-services) ++ [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services) ### Examples From 05267b64d224c19f1b6843a715f666aea2bd106a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 3 Dec 2015 00:36:45 +0200 Subject: [PATCH 035/102] Clean up using_docker_images.md --- doc/ci/docker/using_docker_images.md | 276 ++++++++++++++++++--------- 1 file changed, 189 insertions(+), 87 deletions(-) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 2f0ca19cd0f..6551d47b697 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -1,16 +1,25 @@ # Using Docker Images -GitLab CI can use [Docker Engine](https://www.docker.com/) to build projects. -Docker is an open-source project that allows to use predefined images to run applications -in independent "containers" that are run within a single Linux instance. -[Docker Hub](https://registry.hub.docker.com/) have rich database of built images that can be used to build applications. +GitLab CI can use [Docker Engine](https://www.docker.com/) to build projects. -Docker when used with GitLab CI runs each build in separate and isolated container using predefined image and always from scratch. -It makes it easier to have simple and reproducible build environment that can also be run on your workstation. -This allows you to test all commands from your shell, rather than having to test them on a CI server. +Docker is an open-source project that allows to use predefined images to run +applications in independent "containers" that are run within a single Linux +instance. [Docker Hub][hub] has a rich database of prebuilt images that can be +used to test and build your applications. -### Register Docker runner -To use GitLab Runner with Docker you need to register new runner to use `docker` executor: +Docker, when used with GitLab CI, runs each build in a separate and isolated +container using the predefined image that is set up in `.gitlab-ci.yml`. The +container is always euphemeral which means you get only one container per build. + +This makes it easier to have a simple and reproducible build environment that +can also run on your workstation. The added benefit is that you can test all +the commands that we will explore later from your shell, rather than having to +test them on a CI server. + +### Register docker runner + +To use GitLab Runner with docker you need to register a new runner to use the +`docker` executor: ```bash gitlab-ci-multi-runner register \ @@ -23,45 +32,70 @@ gitlab-ci-multi-runner register \ --docker-mysql latest ``` -**The registered runner will use `ruby:2.1` image and will run two services (`postgres:latest` and `mysql:latest`) that will be accessible for time of the build.** +The registered runner will use the `ruby:2.1` docker image and will run two +services, `postgres:latest` and `mysql:latest`, both of which will be +accessible during the build process. -### What is image? -The image is the name of any repository that is present in local Docker Engine or any repository that can be found at [Docker Hub](https://registry.hub.docker.com/). -For more information about the image and Docker Hub please read the [Docker Fundamentals](https://docs.docker.com/introduction/understanding-docker/). +### What is image -### What is service? -Service is just another image that is run for time of your build and is linked to your build. This allows you to access the service image during build time. -The service image can run any application, but most common use case is to run some database container, ie.: `mysql`. -It's easier and faster to use existing image, run it as additional container than install `mysql` every time project is built. +The `image` keyword is the name of the docker image that is present in the +local Docker Engine (list all images with `docker images`) or any image that +can be found at [Docker Hub][hub]. For more information about images and Docker +Hub please read the [Docker Fundamentals][] documentation. -#### How is service linked to the build? -There's good document that describes how Docker linking works: [Linking containers together](https://docs.docker.com/userguide/dockerlinks/). -To summarize: if you add `mysql` as service to your application, the image will be used to create container that is linked to build container. -The service container for MySQL will be accessible under hostname `mysql`. -So, **to access your database service you have to connect to host: `mysql` instead of socket or `localhost`**. +In short, with `image` we refer to the docker image, which will be used to +create a container on which your build will run. -### How to use other images as services? -You are not limited to have only database services. -You can add the services to `.gitlab-ci.yml` or hand modify the `config.toml`. -You can use any image as service found at [Docker Hub](https://registry.hub.docker.com/). +### What is service + +The `service` keyword defines just another docker image that is run during +your build and is linked to the docker image that the `image` keyword defines. +This allows you to access the service image during build time. + +The service image can run any application, but the most common use case is to +run a database container, eg. `mysql`. It's easier and faster to use an +existing image and run it as an additional container than install `mysql` every +time the project is built. + +#### How is service linked to the build + +To better undestand how the container linking works, read +[Linking containers together](https://docs.docker.com/userguide/dockerlinks/). + +To summarize, if you add `mysql` as service to your application, the image will +then be used to create a container that is linked to the build container. + +The service container for MySQL will be accessible under the hostname `mysql`. +So, in order to access your database service you have to connect to the host +named `mysql` instead of a socket or `localhost`. + +### How to use other images as services + +You are not limited to have only database services. You can add as many +services you need to `.gitlab-ci.yml` or manually modify `config.toml`. +Any image found at [Docker Hub][hub] can be used as a service. ### Define image and services from `.gitlab-ci.yml` -You can simply define image or list services that you want to use for the build time. -``` + +You can simply define an image that will be used for all jobs and a list of +services that you want to use during build time. + +```yaml image: ruby:2.2 services: - postgres:9.3 -before_install: +before_script: - bundle install - + test: script: - bundle exec rake spec ``` -It's possible to define image and service per-job: -``` -before_install: +It is also possible to define different images and services per job: + +```yaml +before_script: - bundle install test:2.1: @@ -80,68 +114,106 @@ test:2.2: ``` ### Define image and services in `config.toml` -Look for `[runners.docker]` section: + +Look for the `[runners.docker]` section: + ``` +... + [runners.docker] image = "ruby:2.1" services = ["mysql:latest", "postgres:latest"] + +... ``` -The image and services defined these way will be added to all builds run by that runner. +The image and services defined this way will be added to all builds run by +that runner. ### Accessing the services -For example you need `wordpress` instance to test some API integration with `Wordpress`. -You can for example use this image: [tutum/wordpress](https://registry.hub.docker.com/u/tutum/wordpress/). -``` -# .gitlab-ci.yml +Let's say that you need a Wordpress instance to test some API integration with +your application. + +You can then use for example the [tutum/wordpress][] image in your +`.gitlab-ci.yml`: + +```yaml +... + services: - tutum/wordpress:latest + +... ``` -Next time when you run your application the `tutum/wordpress` will be started -and you will have access to it from your build container under hostname: `tutum__wordpress`. +When the build is run, `tutum/wordpress` will be started and you will have +access to it from your build container under the hostname `tutum_wordpress`. -Alias hostname for the service is made from the image name: -1. Everything after `:` is stripped, -2. '/' is replaced to `__`. +The alias hostname for the service is made from the image name following these +rules: + +1. Everything after `:` is stripped +2. Backslash (`/`) is replaced with double underscores (`__`) ### Configuring services -Many services accept environment variables, which allow you to easily change database names or set account names depending on the environment. -GitLab Runner 0.5.0 and up passes all YAML-defined variables to created service containers. +Many services accept environment variables which allow you to easily change +database names or set account names depending on the environment. -1. To configure database name for [postgres](https://registry.hub.docker.com/u/library/postgres/) service, -you need to set POSTGRES_DB. +GitLab Runner 0.5.0 and up passes all YAML-defined variables to the created +service containers. - ```yaml - services: - - postgres - - variables: - POSTGRES_DB: gitlab - ``` +For all possible configuration variables check the documentation of each image +provided in their corresponding Docker hub page. -1. To use [mysql](https://registry.hub.docker.com/u/library/mysql/) service with empty password for time of build, -you need to set MYSQL_ALLOW_EMPTY_PASSWORD. +*Note: All variables will be passed to all service containers. It's not designed + to distinguish which variable should go where.* - ```yaml - services: - - mysql - - variables: - MYSQL_ALLOW_EMPTY_PASSWORD: "yes" - ``` +#### PostgreSQL service example -For other possible configuration variables check the -https://registry.hub.docker.com/u/library/mysql/ or https://registry.hub.docker.com/u/library/postgres/ -or README page for any other Docker image. +To configure the database name for [postgres][postgres-hub] service, you need +to set the `POSTGRES_DB` variable: -**Note: All variables will passed to all service containers. It's not designed to distinguish which variable should go where.** +```yaml +... + +services: +- postgres + +variables: + POSTGRES_DB: gitlab + +... +``` + +For a real example visit . + +#### MySQL service example + +To use the [mysql][mysql-hub] service with an empty password during the time of +build, you need to set the `MYSQL_ALLOW_EMPTY_PASSWORD` variable: + +```yaml +... + +services: +- mysql + +variables: + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + +... +``` ### How Docker integration works + +Below is a high level overview of the steps performed by docker during build +time. + 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`. -1. Create cache container to store all volumes as defined in `config.toml` and `Dockerfile` of build image (`ruby:2.1` as in above example). +1. Create cache container to store all volumes as defined in `config.toml` and + `Dockerfile` of build image (`ruby:2.1` as in above example). 1. Create build container and link any service container to build container. 1. Start build container and send build script to the container. 1. Run build script. @@ -151,32 +223,62 @@ or README page for any other Docker image. 1. Remove build container and all created service containers. ### How to debug a build locally -1. Create a file with build script: + +*Note: The following commands are run without root privileges. You should be +able to run docker with your regular user account.* + +First start with creating a file named `build script`: + ```bash -$ cat < build_script +cat < build_script git clone https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git /builds/gitlab-org/gitlab-ci-multi-runner cd /builds/gitlab-org/gitlab-ci-multi-runner -make <- or any other build step +make EOF ``` -1. Create service containers: -``` -$ docker run -d -n service-mysql mysql:latest -$ docker run -d -n service-postgres postgres:latest -``` -This will create two service containers (MySQL and PostgreSQL). +Here we use as an example the GitLab Runner repository which contains a +Makefile, so running `make` will execute the commands defined in the Makefile. +Your mileage may vary, so instead of `make` you could run the command which +is specific to your project. -1. Create a build container and execute script in its context: -``` -$ docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script -``` -This will create build container that has two service containers linked. -The build_script is piped using STDIN to bash interpreter which executes the build script in container. +Then create some service containers: + +``` +docker run -d -n service-mysql mysql:latest +docker run -d -n service-postgres postgres:latest +``` + +This will create two service containers, named `service-mysql` and +`service-postgres` which use the latest MySQL and PostgreSQL images +respectively. They will both run in the background (`-d`). + +Finally, create a build container by executing the `build_script` file we +created earlier: + +``` +docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script +``` + +The above command will create a container named `build` that is spawned from +the `ruby:2.1` image and has two services linked to it. The `build_script` is +piped using STDIN to the bash interpreter which in turn executes the +`build_script` in the `build` container. + +When you finish testing and no longer need the containers, you can remove them +with: -1. At the end remove all containers: ``` docker rm -f -v build service-mysql service-postgres ``` -This will forcefully (the `-f` switch) remove build container and service containers -and all volumes (the `-v` switch) that were created with the container creation. + +This will forcefully (`-f`) remove the `build` container, the two service +containers as well as all volumes (`-v`) that were created with the container +creation. + +[Docker Fundamentals]: https://docs.docker.com/engine/introduction/understanding-docker/ +[hub]: https://hub.docker.com/ +[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/ +[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/ +[postgres-hub]: https://registry.hub.docker.com/u/library/postgres/ +[mysql-hub]: https://registry.hub.docker.com/u/library/mysql/ From 223a02757909b406c1d5ebe42ca4bc3340318a2e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 3 Dec 2015 15:43:06 +0200 Subject: [PATCH 036/102] Bring back removed heading and point to other section --- doc/ci/docker/using_docker_images.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 6551d47b697..63cfa436333 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -69,6 +69,10 @@ The service container for MySQL will be accessible under the hostname `mysql`. So, in order to access your database service you have to connect to the host named `mysql` instead of a socket or `localhost`. +### Overwrite image and services + +See the section below. + ### How to use other images as services You are not limited to have only database services. You can add as many From 71519d650d0317785f86ed935508d1dd5966cd6e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 3 Dec 2015 15:43:29 +0200 Subject: [PATCH 037/102] Fix wrong example --- doc/ci/docker/using_docker_images.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 63cfa436333..488479418b1 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -152,7 +152,7 @@ services: ``` When the build is run, `tutum/wordpress` will be started and you will have -access to it from your build container under the hostname `tutum_wordpress`. +access to it from your build container under the hostname `tutum__wordpress`. The alias hostname for the service is made from the image name following these rules: From bb75dfe38b481607952234212a15d29cd17b7cef Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 3 Dec 2015 15:46:27 +0200 Subject: [PATCH 038/102] Add intro about languages --- doc/ci/languages/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/ci/languages/README.md b/doc/ci/languages/README.md index 375adf58d18..54b2343e08b 100644 --- a/doc/ci/languages/README.md +++ b/doc/ci/languages/README.md @@ -1,3 +1,7 @@ ### Languages +This is a list of languages you can test with GitLab CI. Each section has +comprehensive documentation and comes with a test repository hosted on +GitLab.com + + [Testing PHP](php.md) From a08cc70232b1c5071f199106a582c77ba9541a83 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 3 Dec 2015 18:13:19 +0200 Subject: [PATCH 039/102] Clean up PHP CI example [ci skip] --- doc/ci/languages/php.md | 325 ++++++++++++++++++++++++++-------------- 1 file changed, 211 insertions(+), 114 deletions(-) diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md index e0589182003..c71ae40786e 100644 --- a/doc/ci/languages/php.md +++ b/doc/ci/languages/php.md @@ -1,178 +1,275 @@ -## Testing PHP projects +# Testing PHP projects -This guide covers basic of building PHP projects. +This guide covers basic building instructions for PHP projects. -Is it possible to test PHP apps on any system. -However, it will require manual configuration. -The simplest is to use Docker executor as described below. +There are covered two cases: testing using the Docker executor and testing +using the Shell executor. -### PHP projects on Docker executor -It's possible to official [PHP](https://hub.docker.com/_/php/) repositories on Docker Hub. -They allow to test PHP projects against different versions of the runtime. -However, they require additional configuration. +## Test PHP projects using the Docker executor -To build PHP project you need to create valid `.gitlab-ci.yml` describing the build environment: -1. First you need to specify PHP image as described here: http://doc.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-image. To your `.gitlab-ci.yml` add: +While it is possible to test PHP apps on any system, this would require manual +configuration from the developer. To overcome this we will be using the +official [PHP docker image][php-hub] that can be found in Docker Hub. - image: php:5.6 +This will allow us to test PHP projects against different versions of PHP. +However, not everything is plug 'n' play, you still need to onfigure some +things manually. -2. The official images are great, but they are lacking a few useful tools for testing. We need to install them first in build environment. Create `ci/docker_install.sh` file with following content: +As with every build, you need to create a valid `.gitlab-ci.yml` describing the +build environment. - #!/bin/bash +Let's first specify the PHP image that will be used for the build process +(you can read more about what an image means in the Runner's lingo reading +about [Using Docker images](../docker/using_docker_images.md#what-is-image)). - # We need to install dependencies only for Docker - [[ ! -e /.dockerinit ]] && exit 0 +Start by adding the image to your `.gitlab-ci.yml`: - set -xe +```yaml +image: php:5.6 +``` - # Install git (the php image doesn't have it) which is required by composer - apt-get update -yqq - apt-get install git -yqq +The official images are great, but they lack a few useful tools for testing. +We need to first prepare the build environment. A way to overcome this is to +create a script which installs all prerequisites prior the actual testing is +done. - # Install phpunit, the tool that we will use for testing - curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar - chmod +x /usr/local/bin/phpunit +Let's create a `ci/docker_install.sh` file in the root directory of our +repository with the following content: - # Install mysql driver - # Here you can install any other extension that you need - docker-php-ext-install pdo_mysql +```bash +#!/bin/bash -3. From your `.gitlab-ci.yml` run the created script: +# We need to install dependencies only for Docker +[[ ! -e /.dockerinit ]] && exit 0 - before_script: - - bash ci/docker_install.sh > /dev/null +set -xe -4. Now you can run your tests. Usually it will be `phpunit` with arguments: +# Install git (the php image doesn't have it) which is required by composer +apt-get update -yqq +apt-get install git -yqq - test:app: - script: - - phpunit --configuration phpunit_myapp.xml --coverage-text +# Install phpunit, the tool that we will use for testing +curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar +chmod +x /usr/local/bin/phpunit -5. Commit your files, and push them to GitLab to see if it works. With GitLab Runner 1.0 you can also test the changes locally. From your terminal execute: +# Install mysql driver +# Here you can install any other extension that you need +docker-php-ext-install pdo_mysql +``` - # Check using docker executor - gitlab-runner exec docker test:app +You might wonder what `docker-php-ext-install` is. In short, it is a script +provided by the official php docker image that you can use to easilly install +extensions. For more information read the the documentation at +. - # Check using shell executor - gitlab-runner exec shell test:app +Now that we created the script that contains all prerequisites for our build +environment, let's add it in `.gitlab-ci.yml`: + +```yaml +... + +before_script: +- bash ci/docker_install.sh > /dev/null + +... +``` + +Last step, run the actual tests using `phpunit`: + +```yaml +... + +test:app: + script: + - phpunit --configuration phpunit_myapp.xml + +... +``` + +Finally, commit your files and push them to GitLab to see your build succeeding +(or failing). The final `.gitlab-ci.yml` should look similar to this: - # Select image from https://hub.docker.com/_/php/ - image: php:5.6 +```yaml +# Select image from https://hub.docker.com/_/php/ +image: php:5.6 - before_script: - # Install dependencies - - ci/docker_install.sh > /dev/null +before_script: +# Install dependencies +- ci/docker_install.sh > /dev/null - test:app: - script: - - phpunit --configuration phpunit_myapp.xml --coverage-text +test:app: + script: + - phpunit --configuration phpunit_myapp.xml +``` -#### Test against different PHP versions in Docker builds +### Test against different PHP versions in Docker builds -You can also test against multiple version of PHP runtime: +Testing against multiple versions of PHP is super easy. Just add another job +with a different docker image version and the runner will do the rest: - before_script: - # Install dependencies - - ci/docker_install.sh > /dev/null +```yaml +before_script: +# Install dependencies +- ci/docker_install.sh > /dev/null - # We test PHP5.6 - test:5.6: - image: php:5.6 - script: - - phpunit --configuration phpunit_myapp.xml --coverage-text +# We test PHP5.6 +test:5.6: + image: php:5.6 + script: + - phpunit --configuration phpunit_myapp.xml - # We test PHP7.0 - test:7.0: - image: php:7.0 - script: - - phpunit --configuration phpunit_myapp.xml --coverage-text +# We test PHP7.0 (good luck with that) +test:7.0: + image: php:7.0 + script: + - phpunit --configuration phpunit_myapp.xml +``` -#### Custom PHP configuration in Docker builds +### Custom PHP configuration in Docker builds -You can customise your PHP environment by putting your .ini file into `/usr/local/etc/php/conf.d/`: +There are times where you will need to customise your PHP environment by +putting your `.ini` file into `/usr/local/etc/php/conf.d/`. For that purpose +add a `before_script` action: - before_script: - - cp my_php.ini /usr/local/etc/php/conf.d/test.ini +```yaml +before_script: +- cp my_php.ini /usr/local/etc/php/conf.d/test.ini +``` -### Test PHP projects using Shell +Of course, `my_php.ini` must be present in the root directory of your repository. -Shell executor runs your builds in terminal session of your server. Thus in order to test your projects you need to have all dependencies installed as root. +## Test PHP projects using the Shell executor -1. Install PHP dependencies: +The shell executor runs your builds in a terminal session on your server. +Thus, in order to test your projects you first need to make sure that all +dependencies are installed. - sudo apt-get update -qy - sudo apt-get install phpunit php5-mysql -y +For example, in a VM running Debian 8 we first update the cache, then we +install `phpunit` and `php5-mysql`: - This will install the PHP version available for your distribution. +```bash +sudo apt-get update -y +sudo apt-get install -y phpunit php5-mysql +``` -2. Now you can run your tests. Usually it will be `phpunit` with arguments: +Next, add the following snippet to your `.gitlab-ci.yml`: - test:app: - script: - - phpunit --configuration phpunit_myapp.xml --coverage-text +```yaml +test:app: + script: + - phpunit --configuration phpunit_myapp.xml +``` -#### Test against different PHP versions in Shell builds +Finally, push to GitLab and let the tests begin! -The [phpenv](https://github.com/phpenv/phpenv) allows you to easily manage different PHP with they own configs. -This is specially usefull when testing PHP project with Shell executor. +### Test against different PHP versions in Shell builds -Login as `gitlab-runner` user and follow [the installation guide](https://github.com/phpenv/phpenv#installation). +The [phpenv][] project allows you to easily manage different versions of PHP +each with its own config. This is specially usefull when testing PHP projects +with the Shell executor. -Using phpenv also allows to easily configure PHP environment with: `phpenv config-add my_config.ini`. +You will have to install it on your build machine under the `gitlab-runner` +user following [the upstream installation guide][phpenv-installation]. -#### Install custom extensions +Using phpenv also allows to easily configure the PHP environment with: -Since we have pretty bare installation of our PHP environment you may need some extensions that are not present on your installation. +``` +phpenv config-add my_config.ini +``` -To install additional extensions simply execute.: +### Install custom extensions - pecl install +Since this is a pretty bare installation of the PHP environment, you may need +some extensions that are not currently present on the build machine. - It's not advised to add this to the `.gitlab-ci.yml`. - You should execute this command once, only to setup the build environment. +To install additional extensions simply execute: -### Extend your tests +```bash +pecl install +``` -#### Using atoum +It's not advised to add this to `.gitlab-ci.yml`. You should execute this +command once, only to setup the build environment. -Instead of PHPUnit, you can use any other tool to run unit tests. For example [atoum](https://github.com/atoum/atoum): +## Extend your tests - before_script: - - wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar +### Using atoum - test:atoum: - script: - - php mageekguy.atoum.phar +Instead of PHPUnit, you can use any other tool to run unit tests. For example +you can use [atoum](https://github.com/atoum/atoum): -#### Using Composer +```yaml +before_script: +- wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar -Majority of the PHP projects use Composer for managing the packages. -It's very simple to execute the Composer before running your tests. -To your `.gitlab-ci.yml` add: +test:atoum: + script: + - php mageekguy.atoum.phar +``` - # The composer stores all downloaded packages in vendor/ - # Remove it if you committed the vendor/ directory - cache: - paths: - - vendor/ +### Using Composer - before_script: - # Install composer dependencies - - curl -sS https://getcomposer.org/installer | php - - php composer.phar install +The majority of the PHP projects use Composer for managing their PHP packages. +In order to execute Composer before running your tests, simply add the +following in your `.gitlab-ci.yml`: -### Access private packages / dependencies +```yaml +... -You need to configure [the SSH keys](../ssh_keys/README.md) in order to checkout the repositories. +# Composer stores all downloaded packages in the vendor/ directory. +# Do not use the following if the vendor/ directory is commited to +# your git repository. +cache: + paths: + - vendor/ -### Use databases or other services +before_script: +# Install composer dependencies +- curl -sS https://getcomposer.org/installer | php +- php composer.phar install -Please checkout the docs about configuring [the CI services](../services/README.md). +... +``` -### Example project +## Access private packages / dependencies -You maybe interested in our [Example Project](https://gitlab.com/gitlab-examples/php) that runs on [GitLab.com](https://gitlab.com) using our publically available shared runners. +If your test suite needs to access a private repository, you need to configure +[the SSH keys](../ssh_keys/README.md) in order to be able to clone it. -Want to hack it? Simply fork it, commit and push changes. Within a few moments the changes will be picked and rebuilt by public runners. +## Use databases or other services + +Most of the time you will need a running database in order for your tests to +run. If you are using the Docker executor you can leverage Docker's ability to +connect to other containers. In GitLab Runner lingo, this can be achieved by +defining a `service`. + +This functionality is covered in [the CI services](../services/README.md) +documentation. + +## Testing things locally + +With GitLab Runner 1.0 you can also test any changes locally. From your +terminal execute: + +```bash +# Check using docker executor +gitlab-runner exec docker test:app + +# Check using shell executor +gitlab-runner exec shell test:app +``` + +## Example project + +We have set up an [Example PHP Project](https://gitlab.com/gitlab-examples/php) +for your convenience that runs on [GitLab.com](https://gitlab.com) using our +publicly available [shared runners](../runners/README.md). + +Want to hack it? Simply fork it, commit and push your changes. Within a few +moments the changes will be picked by a public runner and the build will begin. + +[php-hub]: https://hub.docker.com/_/php/ +[phpenv]: https://github.com/phpenv/phpenv +[phpenv-installation]: https://github.com/phpenv/phpenv#installation From 9b8babb601ac6bdb3b7301b017a8ce20c5dc4814 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 3 Dec 2015 21:18:34 +0200 Subject: [PATCH 040/102] Use link instead of connect to be more Docker friendly --- doc/ci/languages/php.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md index c71ae40786e..589253cfc4c 100644 --- a/doc/ci/languages/php.md +++ b/doc/ci/languages/php.md @@ -242,7 +242,7 @@ If your test suite needs to access a private repository, you need to configure Most of the time you will need a running database in order for your tests to run. If you are using the Docker executor you can leverage Docker's ability to -connect to other containers. In GitLab Runner lingo, this can be achieved by +link to other containers. In GitLab Runner lingo, this can be achieved by defining a `service`. This functionality is covered in [the CI services](../services/README.md) From d13d43aca9c486365fbe6eab8d30cb1e005c3f61 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Dec 2015 11:46:08 +0200 Subject: [PATCH 041/102] Clean up CI ssh keys docs [ci skip] --- doc/ci/ssh_keys/README.md | 163 ++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 84 deletions(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 515194e5f5e..210f9c3e849 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -1,114 +1,109 @@ # Using SSH keys -GitLab currently doesn't have built-in support for SSH keys in build environment. +GitLab currently doesn't have built-in support for managing SSH keys in a build +environment. The SSH keys can be useful when: -1. You want to checkout internal submodules, -2. You want to download private packages using your package manager (ie. bundler), -3. You want to deploy your app (ex. to Heroku or own server), -4. You want to execute ssh commands from build environment on remote server, -5. You want to rsync files from your build to remote server. -If anyone of the above holds true, then you most likely need SSH key. +1. You want to checkout internal submodules +2. You want to download private packages using your package manager (eg. bundler) +3. You want to deploy your application to eg. Heroku or your own server +4. You want to execute SSH commands from the build server to the remote server +5. You want to rsync files from your build server to the remote server -There are two possibilities to add SSH keys to build environment. +If anything of the above rings a bell, then you most likely need an SSH key. -## Inject keys in your build environment -The most widely supported is to inject SSH key into your build environment by extending your .gitlab-ci.yml. -This is the universal solution which works with any type of executor (docker, shell, etc.). +## Inject keys in your build server -### How it works? -1. We create a new SSH private key with [ssh-keygen](http://linux.die.net/man/1/ssh-keygen). -2. We add the private key as the Secure Variable to project. -3. We run the [ssh-agent](http://linux.die.net/man/1/ssh-agent) during build to load the private key. +The most widely supported method is to inject an SSH key into your build +environment by extending your `.gitlab-ci.yml`. -The example [.gitlab-ci.yml](https://gitlab.com/gitlab-examples/ssh-private-key/blob/master/.gitlab-ci.yml) looks like this. +This is the universal solution which works with any type of executor +(docker, shell, etc.). -### Make it work? -1. First, go to terminal and generate a new SSH key: -```bash -$ ssh-keygen -t rsa -f my_key +### How it works -Generating public/private rsa key pair. -Enter passphrase (empty for no passphrase): -Enter same passphrase again: -Your identification has been saved in my_key. -Your public key has been saved in my_key.pub. -The key fingerprint is: -SHA256:tBJEfyJUGTMNmPCiPg4UHywHs67MxlM2iEBAlI/W+TY fingeprint -The key's randomart image is: -+---[RSA 2048]----+ -|=*. .o++*= | -|..= +o..o. | -|.+++o + + . | -|+o*=.. + + | -|o+.=. . S | -|*.o .E . | -|o*o . . | -|.o.. | -| . | -+----[SHA256]-----+ -``` +1. Create a new SSH key pair with [ssh-keygen][] +2. Add the private key as a **Secret Variable** to the project +3. Run the [ssh-agent][] during build to load the private key. -2. Create a new **Secure Variable** in your project settings on GitLab and name it: `SSH_PRIVATE_KEY`. +## SSH keys when using the Docker executor -3. Copy the content of `my_key` and paste it as a **Value** of **SSH_PRIVATE_KEY**. +You will first need to create an SSH key pair. For more information, follow the +instructions to [generate an SSH key](../ssh/README.md). + +Then, create a new **Secret Variable** in your project settings on GitLab +following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` +and in the **Value** field paste the content of your _private_ key that you +created earlier. + +Next you need to modify your `.gitlab-ci.yml` with a `before_script` action. +Add it to the top: -4. Next you need to modify your `.gitlab-ci.yml` and at the top of the file add: ``` before_script: -# install ssh-agent (it is required for Docker, change apt-get to yum if you use CentOS-based image) -- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + # Install ssh-agent if not already installed, it is required by Docker. + # (change apt-get to yum if you use a CentOS-based image) + - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' -# run ssh-agent (in build environment) -- eval $(ssh-agent -s) + # Run ssh-agent (inside the build environment) + - eval $(ssh-agent -s) -# add ssh key stored in SSH_PRIVATE_KEY variable to the agent store -- ssh-add <(echo "$SSH_PRIVATE_KEY") + # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store + - ssh-add <(echo "$SSH_PRIVATE_KEY") -# for Docker builds disable host key checking, by adding that you are suspectible to man-in-the-middle attack -- mkdir -p ~/.ssh -- '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config` + # For Docker builds disable host key checking. Be aware that by adding that + # you are suspectible to man-in-the-middle attacks. + # WARNING: Use this only with the Docker executor, if you use it with shell + # you will overwrite your user's SSH config. + - mkdir -p ~/.ssh + - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config` ``` -5. Add the public key from `my_key.pub` to services that you want to have an access from build. +As a final step, add the _public_ key from the one you created earlier to the +services that you want to have an access to from within the build environment. +If you are accessing a private GitLab repository you need to add it as a +[deploy key](../ssh/README.md#deploy-keys). -6. If your builds are run using `shell` executor, you may need to login to server and execute the `ssh ` to store the fingerprint of remote server. +That's it! You can now have access to private servers or repositories in your +build environment. -## SSH keys when using Shell executor -If use `shell`, not `docker` it can be easier to have the SSH key. +## SSH keys when using the Shell executor -We can generate the SSH key for the machine that holds `gitlab-runner` and use that key for all projects that are run on this machine. +If you are using the Shell executor and not Docker, it is easier to set up an +SSH key. -1. First, login to server that runs your builds. +You can generate the SSH key from the machine that GitLab Runner is installed +on, and use that key for all projects that are run on this machine. + +First, you need to login to the server that runs your builds. + +Then from the terminal login as the `gitlab-runner` user and generate the SSH +key pair as described in the [SSH keys documentation](../ssh/README.md). + +As a final step, add the _public_ key from the one you created earlier to the +services that you want to have an access to from within the build environment. +If you are accessing a private GitLab repository you need to add it as a +[deploy key](../ssh/README.md#deploy-keys). + +Once done, try to login to the remote server in order to accept the fingerprint: -2. From terminal login as `gitlab-runner` user and generate the SSH private key: ```bash -$ ssh-keygen -t rsa -Generating public/private rsa key pair. -Enter passphrase (empty for no passphrase): -Enter same passphrase again: -Your identification has been saved in ~/.ssh/id_rsa. -Your public key has been saved in ~/.ssh/id_rsa.pub. -The key fingerprint is: -SHA256:tBJEfyJUGTMNmPCiPg4UHywHs67MxlM2iEBAlI/W+TY fingeprint -The key's randomart image is: -+---[RSA 2048]----+ -|=*. .o++*= | -|..= +o..o. | -|.+++o + + . | -|+o*=.. + + | -|o+.=. . S | -|*.o .E . | -|o*o . . | -|.o.. | -| . | -+----[SHA256]-----+ +ssh ``` -3. Add the public key from `~/.ssh/id_rsa.pub` to services that you want to have an access from build. +For accessing repositories on GitLab.com, the `` would be +`git@gitlab.com`. -4. Try to login for the first time and accept fingerprint: -```bash -ssh Date: Fri, 4 Dec 2015 11:47:02 +0200 Subject: [PATCH 042/102] Move markdown link to the bottom [ci skip] --- doc/ci/languages/php.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md index 589253cfc4c..60079c090d3 100644 --- a/doc/ci/languages/php.md +++ b/doc/ci/languages/php.md @@ -263,13 +263,14 @@ gitlab-runner exec shell test:app ## Example project -We have set up an [Example PHP Project](https://gitlab.com/gitlab-examples/php) -for your convenience that runs on [GitLab.com](https://gitlab.com) using our -publicly available [shared runners](../runners/README.md). +We have set up an [Example PHP Project][php-example-repo] for your convenience +that runs on [GitLab.com](https://gitlab.com) using our publicly available +[shared runners](../runners/README.md). -Want to hack it? Simply fork it, commit and push your changes. Within a few +Want to hack on it? Simply fork it, commit and push your changes. Within a few moments the changes will be picked by a public runner and the build will begin. [php-hub]: https://hub.docker.com/_/php/ [phpenv]: https://github.com/phpenv/phpenv [phpenv-installation]: https://github.com/phpenv/phpenv#installation +[php-example-repo]: https://gitlab.com/gitlab-examples/php From 4fc9e6944e363e3167fede07c0d6898ff6c7e19f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Dec 2015 11:47:50 +0200 Subject: [PATCH 043/102] Add an intro to CI services documentation [ci skip] --- doc/ci/services/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md index 0550e9435a3..0856b7679c2 100644 --- a/doc/ci/services/README.md +++ b/doc/ci/services/README.md @@ -1,6 +1,9 @@ ## GitLab CI Services +GitLab CI uses the `service` keyword to define what docker containers should be +linked with your base image. Below is a list of examples you may use. + + [Using MySQL](mysql.md) + [Using PostgreSQL](postgres.md) + [Using Redis](redis.md) -+ [Using Other Services](../docker/using_docker_images.html#how-to-use-other-images-as-services) ++ [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services) From 4236861df03ff86fc1b2bb8a6a0835b0f3a03244 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Dec 2015 09:39:14 +0200 Subject: [PATCH 044/102] Clean up Redis CI service example [ci skip] --- doc/ci/services/redis.md | 72 ++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md index 523634a457e..b0d04f95408 100644 --- a/doc/ci/services/redis.md +++ b/doc/ci/services/redis.md @@ -1,40 +1,68 @@ -## Using Redis +# Using Redis -It's possible to use Redis database test your apps during builds. +As many applications depend on Redis as their key-value store, you will +eventually need it in order for your tests to run. Below you are guided how to +do this with the Docker and Shell executors of GitLab Runner. -### Use Redis with Docker executor +## Use Redis with Docker executor -If you are using our Docker integration you basically have everything already. +If you are using our Docker integration you basically have everything set up +already. -1. Add this to your `.gitlab-ci.yml`: +First, in your `.gitlab-ci.yml` add: - services: - - redis +```yaml +services: + - redis:latest +``` -2. Configure your application to use the database: +Then you need to configure your application to use the Redis database, for +example: - Host: redis +```bash +Host: redis +``` -3. You can also use any other available on [DockerHub](https://hub.docker.com/_/redis/). For example: `redis:2.6`. +And that's it. Redis will now be available to be used within your testing +framework. -Example: https://gitlab.com/gitlab-examples/redis/blob/master/.gitlab-ci.yml +If you want to use any other version of Redis, check the available versions +on [Docker Hub](https://hub.docker.com/_/redis/). -### Use Redis with Shell executor +## Use Redis with Shell executor -It's possible to use Redis on manually configured servers that are using GitLab Runner with Shell executor. +Redis can also be used on manually configured servers that are using GitLab +Runner with the Shell executor. -1. First install the Redis server: +In your build machine install the Redis server: - sudo apt-get install redis-server +```bash +sudo apt-get install redis-server +``` -2. Try to connect to the server: +Verify that you can connect to the server with the `gitlab-runner` user: - # Try connecting the the Redis server - sudo -u gitlab-runner -H redis-cli +```bash +# Try connecting the the Redis server +sudo -u gitlab-runner -H redis-cli - # Quit the session - 127.0.0.1:6379> quit +# Quit the session +127.0.0.1:6379> quit +``` -4. Configure your application to use the database: +Finally, configure your application to use the database, for example: - Host: localhost +```bash +Host: localhost +``` + +## Example project + +We have set up an [Example Redis Project][redis-example-repo] for your convenience +that runs on [GitLab.com](https://gitlab.com) using our publicly available +[shared runners](../runners/README.md). + +Want to hack on it? Simply fork it, commit and push your changes. Within a few +moments the changes will be picked by a public runner and the build will begin. + +[redis-example-repo]: https://gitlab.com/gitlab-examples/redis From 149c934a1d38662358b48cdf844430de7845d1c4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 15:59:41 +0200 Subject: [PATCH 045/102] More cleanup on Redis example --- doc/ci/services/redis.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md index b0d04f95408..e6add57ca76 100644 --- a/doc/ci/services/redis.md +++ b/doc/ci/services/redis.md @@ -4,10 +4,10 @@ As many applications depend on Redis as their key-value store, you will eventually need it in order for your tests to run. Below you are guided how to do this with the Docker and Shell executors of GitLab Runner. -## Use Redis with Docker executor +## Use Redis with the Docker executor -If you are using our Docker integration you basically have everything set up -already. +If you are using GitLab's Runner Docker integration you basically have +everything set up already. First, in your `.gitlab-ci.yml` add: @@ -29,7 +29,7 @@ framework. If you want to use any other version of Redis, check the available versions on [Docker Hub](https://hub.docker.com/_/redis/). -## Use Redis with Shell executor +## Use Redis with the Shell executor Redis can also be used on manually configured servers that are using GitLab Runner with the Shell executor. From 06b86de996232b956129f2bb5dde4a7647f94f69 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 18:22:45 +0200 Subject: [PATCH 046/102] Clean up postgres CI example [ci skip] --- doc/ci/services/postgres.md | 116 ++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 45 deletions(-) diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md index e57f8c5944a..f82a828756c 100644 --- a/doc/ci/services/postgres.md +++ b/doc/ci/services/postgres.md @@ -1,70 +1,96 @@ -## Using PostgreSQL +# Using PostgreSQL -It's possible to use PostgreSQL database test your apps during builds. +As many applications depend on PostgreSQL as their database, you will +eventually need it in order for your tests to run. Below you are guided how to +do this with the Docker and Shell executors of GitLab Runner. -### Use PostgreSQL with Docker executor +## Use PostgreSQL with the Docker executor -If you are using our Docker integration you basically have everything already. +If you are using GitLab's Runner with the Docker executor you basically have +everything set up already. -1. Add this to your `.gitlab-ci.yml`: +First, in your `.gitlab-ci.yml` add: - services: - - postgres +```yaml +services: + - postgres - variables: - # Configure postgres service (https://hub.docker.com/_/postgres/) - POSTGRES_DB: hello_world_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: "" +variables: + POSTGRES_DB: nice_marmot + POSTGRES_USER: gitlab_runner + POSTGRES_PASSWORD: "" +``` -2. Configure your application to use the database: +And then configure your application to use PostgreSQL, for example: - Host: postgres - User: postgres - Password: postgres - Database: hello_world_test +```yaml +Host: localhost +User: gitlab_runner +Password: +Database: nice_marmot +``` -3. You can also use any other available on [DockerHub](https://hub.docker.com/_/postgres/). For example: `postgres:9.3`. +You can also use any other docker image available on [Docker Hub][hub-pg]. +For example, to use PostgreSQL 9.3 the service becomes `postgres:9.3`. -Example: https://gitlab.com/gitlab-examples/postgres/blob/master/.gitlab-ci.yml +The `postgres` image can accept some environment variables. For more details +check the documentation on [Docker Hub][hub-pg]. -### Use PostgreSQL with Shell executor +## Use PostgreSQL with the Shell executor -It's possible to use PostgreSQL on manually configured servers that are using GitLab Runner with Shell executor. +You can also use PostgreSQL on manually configured servers that are using +GitLab Runner with the Shell executor. -1. First install the PostgreSQL server: +First install the PostgreSQL server: - sudo apt-get install -y postgresql postgresql-client libpq-dev +```bash +sudo apt-get install -y postgresql postgresql-client libpq-dev +``` -2. Create an user: +Then create a user: - # Install the database packages - sudo apt-get install -y postgresql postgresql-client libpq-dev +```bash +# Login to PostgreSQL +sudo -u postgres psql -d template1 - # Login to PostgreSQL - sudo -u postgres psql -d template1 +# Create a user for GitLab Runner that can create databases +# Do not type the 'template1=#', this is part of the prompt +template1=# CREATE USER gitlab_runner CREATEDB; - # Create a user for runner - # Do not type the 'template1=#', this is part of the prompt - template1=# CREATE USER runner CREATEDB; +# Create the database & grant all privileges on database +template1=# CREATE DATABASE nice_marmot OWNER gitlab_runner; - # Create the database & grant all privileges on database - template1=# CREATE DATABASE hello_world_test OWNER runner; +# Quit the database session +template1=# \q +``` - # Quit the database session - template1=# \q +Try to connect to database: -3. Try to connect to database: +```bash +# Try connecting to the new database with the new user +sudo -u gitlab_runner -H psql -d nice_marmot - # Try connecting to the new database with the new user - sudo -u gitlab-runner -H psql -d hello_world_test +# Quit the database session +nice_marmot> \q +``` - # Quit the database session - hello_world_test> \q +Finally, configure your application to use the database: -4. Configure your application to use the database: +```bash +Host: localhost +User: gitlab_runner +Password: +Database: nice_marmot +``` - Host: localhost - User: runner - Password: - Database: hello_world_test +## Example project + +We have set up an [Example PostgreSQL Project][postgres-example-repo] for your +convenience that runs on [GitLab.com](https://gitlab.com) using our publicly +available [shared runners](../runners/README.md). + +Want to hack on it? Simply fork it, commit and push your changes. Within a few +moments the changes will be picked by a public runner and the build will begin. + +[hub-pg]: https://hub.docker.com/_/postgres/ +[postgres-example-repo]: https://gitlab.com/gitlab-examples/postgres From 47e81da8c2746196ad0506d30720c775a538ebdb Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 21:11:11 +0200 Subject: [PATCH 047/102] Clean up mysql CI example --- doc/ci/services/mysql.md | 137 +++++++++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 47 deletions(-) diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md index 3155af6b3e1..7f50b4d76c8 100644 --- a/doc/ci/services/mysql.md +++ b/doc/ci/services/mysql.md @@ -1,72 +1,115 @@ -## Using MySQL +# Using MySQL -It's possible to use MySQL database test your apps during builds. +As many applications depend on MySQL as their database, you will eventually +need it in order for your tests to run. Below you are guided how to do this +with the Docker and Shell executors of GitLab Runner. -### Use MySQL with Docker executor +## Use MySQL with the Docker executor -If you are using our Docker integration you basically have everything already. +If you are using [GitLab Runner](../runners/README.md) with the Docker executor +you basically have everything set up already. -1. Add this to your `.gitlab-ci.yml`: +First, in your `.gitlab-ci.yml` add: - services: - - mysql +```yaml +services: + - mysql - variables: - # Configure mysql service (https://hub.docker.com/_/mysql/) - MYSQL_DATABASE: hello_world_test - MYSQL_ROOT_PASSWORD: mysql +variables: + # Configure mysql environment variables (https://hub.docker.com/_/mysql/) + MYSQL_DATABASE: el_duderino + MYSQL_ROOT_PASSWORD: mysql_strong_password +``` -2. Configure your application to use the database: +And then configure your application to use the database, for example: - Host: mysql - User: root - Password: mysql - Database: hello_world_test +```yaml +Host: localhost +User: root +Password: mysql_strong_password +Database: el_duderino +``` -3. You can also use any other available on [DockerHub](https://hub.docker.com/_/mysql/). For example: `mysql:5.5`. +You can also use any other docker image available on [Docker Hub][hub-mysql]. +For example, to use MySQL 5.5 the service becomes `mysql:5.5`. -Example: https://gitlab.com/gitlab-examples/mysql/blob/master/.gitlab-ci.yml +The `mysql` image can accept some environment variables. For more details +check the documentation on [Docker Hub][hub-mysql]. -### Use MySQL with Shell executor +## Use MySQL with the Shell executor -It's possible to use MySQL on manually configured servers that are using GitLab Runner with Shell executor. +You can also use MySQL on manually configured servers that are using +GitLab Runner with the Shell executor. -1. First install the MySQL server: - - sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev +First install the MySQL server: - # Pick a MySQL root password (can be anything), type it and press enter - # Retype the MySQL root password and press enter +```bash +sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev +``` -2. Create an user: +Pick a MySQL root password (can be anything), and type it twice when asked. - mysql -u root -p +*Note: As a security measure you can run `mysql_secure_installation` to +remove anonymous users, drop the test database and disable remote logins with +the root user.* - # Create a user which will be used by your apps - # do not type the 'mysql>', this is part of the prompt - # change $password in the command below to a real password you pick - mysql> CREATE USER 'runner'@'localhost' IDENTIFIED BY '$password'; +The next step is to create a user, so login to MySQL as root: - # Ensure you can use the InnoDB engine which is necessary to support long indexes - # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off" - mysql> SET storage_engine=INNODB; +```bash +mysql -u root -p +``` - # Create the database - mysql> CREATE DATABASE IF NOT EXISTS `hello_world_test` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; +Then create a user (in our case `runner`) which will be used by your +application. Change `$password` in the command below to a real strong password. - # Grant necessary permissions on the database - mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `hello_world_test`.* TO 'runner'@'localhost'; +*Note: Do not type `mysql>`, this is part of the MySQL prompt.* - # Quit the database session - mysql> \q +```bash +mysql> CREATE USER 'runner'@'localhost' IDENTIFIED BY '$password'; +``` -3. Try to connect to database: +Create the database: - sudo -u gitlab-runner -H mysql -u runner -p -D hello_world_test +```bash +mysql> CREATE DATABASE IF NOT EXISTS `el_duderino` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; +``` -4. Configure your application to use the database: +Grant the necessary permissions on the database: - Host: localhost - User: runner - Password: $password - Database: hello_world_test +```bash +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `el_duderino`.* TO 'runner'@'localhost'; +``` + +If all went well you can now quit the database session: + +```bash +mysql> \q +``` + +Now, try to connect to the newly created database to check that everything is +in place: + +```bash +mysql -u runner -p -D el_duderino +``` + +As a final step, configure your application to use the database, for example: + +```bash +Host: localhost +User: runner +Password: $password +Database: el_duderino +``` + +## Example project + +We have set up an [Example MySQL Project][mysql-example-repo] for your +convenience that runs on [GitLab.com](https://gitlab.com) using our publicly +available [shared runners](../runners/README.md). + +Want to hack on it? Simply fork it, commit and push your changes. Within a few +moments the changes will be picked by a public runner and the build will begin. + +[hub-mysql]: https://hub.docker.com/_/mysql/ +[mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql From 9dc91f46df377d6220928d6292dac73bc6bae295 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 21:11:27 +0200 Subject: [PATCH 048/102] Add link to runners doc [ci skip] --- doc/ci/services/postgres.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md index f82a828756c..b8436f4f7ca 100644 --- a/doc/ci/services/postgres.md +++ b/doc/ci/services/postgres.md @@ -6,8 +6,8 @@ do this with the Docker and Shell executors of GitLab Runner. ## Use PostgreSQL with the Docker executor -If you are using GitLab's Runner with the Docker executor you basically have -everything set up already. +If you are using [GitLab Runner](../runners/README.md) with the Docker executor +you basically have everything set up already. First, in your `.gitlab-ci.yml` add: @@ -21,7 +21,7 @@ variables: POSTGRES_PASSWORD: "" ``` -And then configure your application to use PostgreSQL, for example: +And then configure your application to use the database, for example: ```yaml Host: localhost From cf44dc510786775a1303384d858eb0a7bf33c34f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 21:35:08 +0200 Subject: [PATCH 049/102] Add note about the various phpenv tools --- doc/ci/languages/php.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md index 60079c090d3..dacb67fa3ff 100644 --- a/doc/ci/languages/php.md +++ b/doc/ci/languages/php.md @@ -179,6 +179,14 @@ Using phpenv also allows to easily configure the PHP environment with: phpenv config-add my_config.ini ``` +*__Important note:__ It seems `phpenv/phpenv` + [is abandoned](https://github.com/phpenv/phpenv/issues/57). There is a fork + at [madumlao/phpenv](https://github.com/madumlao/phpenv) that tries to bring + the project back to life. [CHH/phpenv](https://github.com/CHH/phpenv) also + seems like a good alternative. Picking any of the mentioned tools will work + with the basic phpenv commands. Guiding you to choose the right phpenv is out + of the scope of this tutorial.* + ### Install custom extensions Since this is a pretty bare installation of the PHP environment, you may need From 4ea0f06470620064c5d23e28c778f83296aeb551 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 21:35:25 +0200 Subject: [PATCH 050/102] Fix wrong naming of services keyword [ci skip] --- doc/ci/services/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md index 0856b7679c2..1ebb0a4a250 100644 --- a/doc/ci/services/README.md +++ b/doc/ci/services/README.md @@ -1,6 +1,6 @@ ## GitLab CI Services -GitLab CI uses the `service` keyword to define what docker containers should be +GitLab CI uses the `services` keyword to define what docker containers should be linked with your base image. Below is a list of examples you may use. + [Using MySQL](mysql.md) From 0c22635aa2036ab3394c7e7bd99fd61ddd99c43c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 21:41:56 +0200 Subject: [PATCH 051/102] More redis CI example clean up --- doc/ci/services/redis.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md index e6add57ca76..b281e8f9f60 100644 --- a/doc/ci/services/redis.md +++ b/doc/ci/services/redis.md @@ -6,8 +6,8 @@ do this with the Docker and Shell executors of GitLab Runner. ## Use Redis with the Docker executor -If you are using GitLab's Runner Docker integration you basically have -everything set up already. +If you are using [GitLab Runner](../runners/README.md) with the Docker executor +you basically have everything set up already. First, in your `.gitlab-ci.yml` add: @@ -19,15 +19,15 @@ services: Then you need to configure your application to use the Redis database, for example: -```bash +```yaml Host: redis ``` And that's it. Redis will now be available to be used within your testing framework. -If you want to use any other version of Redis, check the available versions -on [Docker Hub](https://hub.docker.com/_/redis/). +You can also use any other docker image available on [Docker Hub][hub-redis]. +For example, to use Redis 2.8 the service becomes `redis:2.8`. ## Use Redis with the Shell executor @@ -52,7 +52,7 @@ sudo -u gitlab-runner -H redis-cli Finally, configure your application to use the database, for example: -```bash +```yaml Host: localhost ``` @@ -65,4 +65,5 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available Want to hack on it? Simply fork it, commit and push your changes. Within a few moments the changes will be picked by a public runner and the build will begin. +[hub-redis]: https://hub.docker.com/_/redis/ [redis-example-repo]: https://gitlab.com/gitlab-examples/redis From ebee5077f2437d0828784ff23b19ed470bed534d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 8 Dec 2015 14:47:28 -0500 Subject: [PATCH 052/102] Make tab target selectors less naive Prior, any of the specified IDs could have been hijacked by a table of contents header, breaking the tab functionality. For example, a `## Notes` header would get the id `notes` and prevent the Discussion tab from being activated. Closes #3908 --- app/views/projects/merge_requests/_new_submit.html.haml | 4 ++-- app/views/projects/merge_requests/_show.html.haml | 6 +++--- spec/javascripts/fixtures/merge_request_tabs.html.haml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 156922cea41..0bcc826e8d4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -20,11 +20,11 @@ .mr-compare.merge-request %ul.merge-request-tabs.center-top-menu.no-top.no-bottom %li.commits-tab - = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do + = link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits %span.badge= @commits.size %li.diffs-tab.active - = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do + = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes %span.badge= @diffs.size diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index f5aff0877e7..6a89df38231 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -44,15 +44,15 @@ - if @commits.present? %ul.merge-request-tabs.center-top-menu.no-top.no-bottom %li.notes-tab - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do + = 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: '#commits', action: 'commits', toggle: 'tab'} do + = 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.size %li.diffs-tab - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes %span.badge= @merge_request.diffs.size diff --git a/spec/javascripts/fixtures/merge_request_tabs.html.haml b/spec/javascripts/fixtures/merge_request_tabs.html.haml index 7624a713948..68678c3d7e3 100644 --- a/spec/javascripts/fixtures/merge_request_tabs.html.haml +++ b/spec/javascripts/fixtures/merge_request_tabs.html.haml @@ -1,12 +1,12 @@ %ul.nav.nav-tabs.merge-request-tabs %li.notes-tab - %a{href: '/foo/bar/merge_requests/1', data: {target: '#notes', action: 'notes', toggle: 'tab'}} + %a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}} Discussion %li.commits-tab - %a{href: '/foo/bar/merge_requests/1/commits', data: {target: '#commits', action: 'commits', toggle: 'tab'}} + %a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}} Commits %li.diffs-tab - %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: '#diffs', action: 'diffs', toggle: 'tab'}} + %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}} Diffs .tab-content From bbe652cdaf11dae7fa917bac1ffa4a02f2099026 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 22:26:41 +0200 Subject: [PATCH 053/102] More postgres CI service example cleanup --- doc/ci/services/postgres.md | 57 +++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md index b8436f4f7ca..db5be070ee7 100644 --- a/doc/ci/services/postgres.md +++ b/doc/ci/services/postgres.md @@ -13,11 +13,11 @@ First, in your `.gitlab-ci.yml` add: ```yaml services: - - postgres + - postgres:latest variables: POSTGRES_DB: nice_marmot - POSTGRES_USER: gitlab_runner + POSTGRES_USER: runner POSTGRES_PASSWORD: "" ``` @@ -25,7 +25,7 @@ And then configure your application to use the database, for example: ```yaml Host: localhost -User: gitlab_runner +User: runner Password: Database: nice_marmot ``` @@ -47,39 +47,54 @@ First install the PostgreSQL server: sudo apt-get install -y postgresql postgresql-client libpq-dev ``` -Then create a user: +The next step is to create a user, so login to PostgreSQL: ```bash -# Login to PostgreSQL sudo -u postgres psql -d template1 +``` -# Create a user for GitLab Runner that can create databases -# Do not type the 'template1=#', this is part of the prompt -template1=# CREATE USER gitlab_runner CREATEDB; +Then create a user (in our case `runner`) which will be used by your +application. Change `$password` in the command below to a real strong password. -# Create the database & grant all privileges on database -template1=# CREATE DATABASE nice_marmot OWNER gitlab_runner; +*__Note:__ Do not type `template1=#`, this is part of the PostgreSQL prompt.* -# Quit the database session +```bash +template1=# CREATE USER runner WITH PASSWORD '$password' CREATEDB; +``` + +*__Note:__ Notice that we created the user with the privilege to be able to +create databases (`CREATEDB`). In the following steps we will create a database +explicitly for that user but having that privilege can be useful if in your +testing framework you have tools that drop and create databases.* + +Create the database and grant all privileges on it for the user `runner`: + +```bash +template1=# CREATE DATABASE nice_marmot OWNER runner; +``` + +If all went well you can now quit the database session: + +```bash template1=# \q ``` -Try to connect to database: +Now, try to connect to the newly created database with the user `runner` to +check that everything is in place. ```bash -# Try connecting to the new database with the new user -sudo -u gitlab_runner -H psql -d nice_marmot - -# Quit the database session -nice_marmot> \q +psql -U runner -h localhost -d nice_marmot -W ``` -Finally, configure your application to use the database: +*__Note:__ We are explicitly telling `psql` to connect to localhost in order +to use the md5 authentication. If you omit this step you will be denied access.* -```bash +Finally, configure your application to use the database, for example: + +```yaml Host: localhost -User: gitlab_runner -Password: +User: runner +Password: $password Database: nice_marmot ``` From d55dcd11d59ebfadf49290746c5f30c2e4780c6b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 22:27:23 +0200 Subject: [PATCH 054/102] Use the latest docker image in mysql CI example [ci skip] --- doc/ci/services/mysql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md index 7f50b4d76c8..44bc613e6f7 100644 --- a/doc/ci/services/mysql.md +++ b/doc/ci/services/mysql.md @@ -13,7 +13,7 @@ First, in your `.gitlab-ci.yml` add: ```yaml services: - - mysql + - mysql:latest variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) From 813c9b2aeb9e5d44c99f02a2373ee0e007d0e87d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 8 Dec 2015 15:29:46 -0500 Subject: [PATCH 055/102] Make cross-project reference's clipboard target less naive See !2023 --- app/views/projects/issues/_discussion.html.haml | 2 +- app/views/projects/merge_requests/_discussion.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 21eee70b424..f2011542ca7 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -17,7 +17,7 @@ .input-group.cross-project-reference %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'} = cross_project_reference(@project, @issue) - = clipboard_button(clipboard_target: '#cross-project-reference') + = clipboard_button(clipboard_target: 'span#cross-project-reference') .row %section.col-md-9 diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 3d7bd78dce3..d64b19ae91a 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -17,7 +17,7 @@ .input-group.cross-project-reference %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'} = cross_project_reference(@project, @merge_request) - = clipboard_button(clipboard_target: '#cross-project-reference') + = clipboard_button(clipboard_target: 'span#cross-project-reference') .row %section.col-md-9 From f66454beaa880d283ef527c3a32a3076fedf6551 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 21:43:11 +0100 Subject: [PATCH 056/102] Add indication to merge request list item that MR cannot be merged automatically --- app/assets/stylesheets/framework/lists.scss | 18 ++++++++ app/models/merge_request.rb | 4 ++ app/views/projects/issues/_issue.html.haml | 25 ++++++----- .../merge_requests/_merge_request.html.haml | 41 +++++++++++-------- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index a798ae812e3..2404f8898fe 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -124,6 +124,24 @@ ul.content-list { padding: 10px 14px; } } + + ul.controls { + list-style: none; + + li { + float: left; + padding-right: 10px; + + .author_link { + display: inline-block; + + .avatar-inline { + margin-left: 0; + margin-right: 0; + } + } + } + } } } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 080b7f7fb88..1f84dc2a577 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -484,4 +484,8 @@ class MergeRequest < ActiveRecord::Base source_project.ci_commit(last_commit.id) end end + + def broken? + self.commits.blank? || branch_missing? || cannot_be_merged? + end end diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 1eb71990e55..7d9fc8d2c86 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -6,23 +6,26 @@ .issue-title %span.issue-title-text = link_to_gfm issue.title, issue_path(issue), class: "row_title" - .pull-right.light + %ul.controls.light - if issue.closed? - %span + %li CLOSED + - if issue.assignee - = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name") + %li + = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name") + - note_count = issue.notes.user.count - if note_count > 0 -   - = link_to issue_path(issue) + "#notes" do - = icon('comments') - = note_count + %li + = link_to issue_path(issue) + "#notes" do + = icon('comments') + = note_count - else -   - = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do - = icon('comments') - = 0 + %li + = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do + = icon('comments') + = notes_count .issue-info #{issue.to_reference} · diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 1d4c9b66c42..5ee7738e9c4 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -3,31 +3,40 @@ .merge-request-title %span.merge-request-title-text = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" - .pull-right.light - - if ci_commit - = render_ci_status(ci_commit) + %ul.controls.light - if merge_request.merged? - %span + %li = icon('check') MERGED - elsif merge_request.closed? - %span + %li = icon('ban') CLOSED - - note_count = merge_request.mr_and_commit_notes.user.count + + - if ci_commit + %li + = render_ci_status(ci_commit) + + - if merge_request.open? && merge_request.broken? + %li + = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: {container: 'body'} do + = icon('exclamation-triangle') + - if merge_request.assignee -   - = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") + %li + = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") + + - note_count = merge_request.mr_and_commit_notes.user.count - if note_count > 0 -   - = link_to merge_request_path(merge_request) + "#notes" do - = icon('comments') - = note_count + %li + = link_to merge_request_path(merge_request) + "#notes" do + = icon('comments') + = note_count - else -   - = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do - = icon('comments') - = 0 + %li + = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do + = icon('comments') + = note_count .merge-request-info \##{merge_request.iid} · From 065375ca2d482faac80897e42d82b3afdcb08d99 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 21:43:52 +0100 Subject: [PATCH 057/102] Use "Build passed" in tooltip instead of "Build status: passed" --- app/helpers/ci_status_helper.rb | 2 +- features/steps/dashboard/dashboard.rb | 2 +- features/steps/project/merge_requests.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 0ecf77bb45e..599a9adc31a 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -46,7 +46,7 @@ module CiStatusHelper def render_ci_status(ci_commit) link_to ci_status_path(ci_commit), class: "c#{ci_status_color(ci_commit)}", - title: "Build status: #{ci_commit.status}", + title: "Build #{ci_commit.status}", data: { toggle: 'tooltip', placement: 'left' } do ci_status_icon(ci_commit) end diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index f0fbd8a826a..63f0ec2b6e8 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -12,7 +12,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps end step 'I should see "Shop" project CI status' do - expect(page).to have_link "Build status: skipped" + expect(page).to have_link "Build skipped" end step 'I should see last push widget' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 822cf0ffe1c..0107d9d8486 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -367,7 +367,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see merge request "Bug NS-05" with CI status' do page.within ".mr-list" do - expect(page).to have_link "Build status: pending" + expect(page).to have_link "Build pending" end end From 23b6a98de00b966728f6b5ed3747b0d2e078165f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 22:28:28 +0100 Subject: [PATCH 058/102] Move Builds tab to the end --- .../javascripts/merge_request_tabs.js.coffee | 26 +++++++++---------- .../merge_requests/_new_submit.html.haml | 16 ++++++------ .../projects/merge_requests/_show.html.haml | 12 ++++----- config/routes.rb | 2 +- .../merge_request_reference_filter.rb | 4 +-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 69a12fdd045..b0eeb1db536 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -68,10 +68,10 @@ class @MergeRequestTabs if action == 'commits' @loadCommits($target.attr('href')) - else if action == 'builds' - @loadBuilds($target.attr('href')) else if action == 'diffs' @loadDiff($target.attr('href')) + else if action == 'builds' + @loadBuilds($target.attr('href')) @setCurrentAction(action) @@ -110,7 +110,7 @@ class @MergeRequestTabs action = 'notes' if action == 'show' # Remove a trailing '/commits' or '/diffs' - new_state = @_location.pathname.replace(/\/(commits|builds|diffs)(\.html)?\/?$/, '') + new_state = @_location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '') # Append the new action if we're on a tab other than 'notes' unless action == 'notes' @@ -138,6 +138,16 @@ class @MergeRequestTabs @commitsLoaded = true @scrollToElement("#commits") + loadDiff: (source) -> + return if @diffsLoaded + + @_get + url: "#{source}.json" + @_location.search + success: (data) => + document.getElementById('diffs').innerHTML = data.html + @diffsLoaded = true + @scrollToElement("#diffs") + loadBuilds: (source) -> return if @buildsLoaded @@ -149,16 +159,6 @@ class @MergeRequestTabs @buildsLoaded = true @scrollToElement("#builds") - loadDiff: (source) -> - return if @diffsLoaded - - @_get - url: "#{source}.json" + @_location.search - success: (data) => - document.getElementById('diffs').innerHTML = data.html - @diffsLoaded = true - @scrollToElement("#diffs") - # Show or hide the loading spinner # # status - Boolean, true to show, false to hide diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 3f6e421fcd7..4172d5a4e88 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -23,22 +23,19 @@ = link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits %span.badge= @commits.size - - if @ci_commit - %li.builds-tab.active - = link_to url_for(params), data: {target: '#builds', action: 'builds', toggle: 'tab'} do - Builds - %span.badge= @statuses.size %li.diffs-tab.active = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes %span.badge= @diffs.size + - if @ci_commit + %li.builds-tab.active + = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do + Builds + %span.badge= @statuses.size .tab-content #commits.commits.tab-pane = render "projects/merge_requests/show/commits" - - if @ci_commit - #builds.builds.tab-pane - = render "projects/merge_requests/show/builds" #diffs.diffs.tab-pane.active - if @diffs.present? = render "projects/diffs/diffs", diffs: @diffs, project: @project @@ -50,6 +47,9 @@ .alert.alert-danger %h4 This comparison includes a huge diff. %p To preserve performance the line changes are not shown. + - if @ci_commit + #builds.builds.tab-pane + = render "projects/merge_requests/show/builds" :javascript $('.assign-to-me-link').on('click', function(e){ diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 686a9a2c758..960d1561e73 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -50,25 +50,25 @@ = 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.size + %li.diffs-tab + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do + Changes + %span.badge= @merge_request.diffs.size - if @ci_commit %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do Builds %span.badge= @statuses.size - %li.diffs-tab - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do - Changes - %span.badge= @merge_request.diffs.size .tab-content #notes.notes.tab-pane.voting_notes = render "projects/merge_requests/discussion" #commits.commits.tab-pane - # This tab is always loaded via AJAX - #builds.builds.tab-pane - - # This tab is always loaded via AJAX #diffs.diffs.tab-pane - # This tab is always loaded via AJAX + #builds.builds.tab-pane + - # This tab is always loaded via AJAX .mr-loading-status = spinner diff --git a/config/routes.rb b/config/routes.rb index 359cd08e134..046e1800235 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -570,9 +570,9 @@ Rails.application.routes.draw do resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do member do + get :commits get :diffs get :builds - get :commits post :merge get :merge_check get :ci_status diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 79d67870b14..2eb77c46da7 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -28,10 +28,10 @@ module Gitlab case path when '/diffs' extras.unshift "diffs" - when '/builds' - extras.unshift "builds" when '/commits' extras.unshift "commits" + when '/builds' + extras.unshift "builds" end extras From b312edaf440b729dad02749259577f6e0589c061 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 23:29:49 +0200 Subject: [PATCH 059/102] More clean up to the CI example on how to use docker images [ci skip] --- doc/ci/docker/using_docker_images.md | 100 ++++++++++----------------- 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 488479418b1..8d4bd44053e 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -1,28 +1,29 @@ # Using Docker Images -GitLab CI can use [Docker Engine](https://www.docker.com/) to build projects. +GitLab CI in conjuction with [GitLab Runner](../runners/README.md) can use +[Docker Engine](https://www.docker.com/) to test and build any application. -Docker is an open-source project that allows to use predefined images to run -applications in independent "containers" that are run within a single Linux +Docker is an open-source project that allows you to use predefined images to +run applications in independent "containers" that are run within a single Linux instance. [Docker Hub][hub] has a rich database of prebuilt images that can be used to test and build your applications. Docker, when used with GitLab CI, runs each build in a separate and isolated -container using the predefined image that is set up in `.gitlab-ci.yml`. The -container is always euphemeral which means you get only one container per build. +container using the predefined image that is set up in +[`.gitlab-ci.yml`](../yaml/README.md). This makes it easier to have a simple and reproducible build environment that can also run on your workstation. The added benefit is that you can test all the commands that we will explore later from your shell, rather than having to -test them on a CI server. +test them on a dedicated CI server. -### Register docker runner +## Register docker runner To use GitLab Runner with docker you need to register a new runner to use the `docker` executor: ```bash -gitlab-ci-multi-runner register \ +gitlab-runner register \ --url "https://gitlab.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --description "docker-ruby-2.1" \ @@ -36,7 +37,7 @@ The registered runner will use the `ruby:2.1` docker image and will run two services, `postgres:latest` and `mysql:latest`, both of which will be accessible during the build process. -### What is image +## What is image The `image` keyword is the name of the docker image that is present in the local Docker Engine (list all images with `docker images`) or any image that @@ -46,9 +47,9 @@ Hub please read the [Docker Fundamentals][] documentation. In short, with `image` we refer to the docker image, which will be used to create a container on which your build will run. -### What is service +## What is service -The `service` keyword defines just another docker image that is run during +The `services` keyword defines just another docker image that is run during your build and is linked to the docker image that the `image` keyword defines. This allows you to access the service image during build time. @@ -57,9 +58,12 @@ run a database container, eg. `mysql`. It's easier and faster to use an existing image and run it as an additional container than install `mysql` every time the project is built. -#### How is service linked to the build +You can see some widely used services examples in the relevant documentation of +[CI services examples](../services/README.md). -To better undestand how the container linking works, read +### How is service linked to the build + +To better understand how the container linking works, read [Linking containers together](https://docs.docker.com/userguide/dockerlinks/). To summarize, if you add `mysql` as service to your application, the image will @@ -69,25 +73,27 @@ The service container for MySQL will be accessible under the hostname `mysql`. So, in order to access your database service you have to connect to the host named `mysql` instead of a socket or `localhost`. -### Overwrite image and services +## Overwrite image and services -See the section below. +See [How to use other images as services](#how-to-use-other-images-as-services). -### How to use other images as services +## How to use other images as services You are not limited to have only database services. You can add as many services you need to `.gitlab-ci.yml` or manually modify `config.toml`. Any image found at [Docker Hub][hub] can be used as a service. -### Define image and services from `.gitlab-ci.yml` +## Define image and services from `.gitlab-ci.yml` You can simply define an image that will be used for all jobs and a list of services that you want to use during build time. ```yaml image: ruby:2.2 + services: - postgres:9.3 + before_script: - bundle install @@ -117,24 +123,20 @@ test:2.2: - bundle exec rake spec ``` -### Define image and services in `config.toml` +## Define image and services in `config.toml` Look for the `[runners.docker]` section: ``` -... - [runners.docker] image = "ruby:2.1" services = ["mysql:latest", "postgres:latest"] - -... ``` The image and services defined this way will be added to all builds run by that runner. -### Accessing the services +## Accessing the services Let's say that you need a Wordpress instance to test some API integration with your application. @@ -143,12 +145,8 @@ You can then use for example the [tutum/wordpress][] image in your `.gitlab-ci.yml`: ```yaml -... - services: - tutum/wordpress:latest - -... ``` When the build is run, `tutum/wordpress` will be started and you will have @@ -160,7 +158,7 @@ rules: 1. Everything after `:` is stripped 2. Backslash (`/`) is replaced with double underscores (`__`) -### Configuring services +## Configuring services Many services accept environment variables which allow you to easily change database names or set account names depending on the environment. @@ -171,46 +169,20 @@ service containers. For all possible configuration variables check the documentation of each image provided in their corresponding Docker hub page. -*Note: All variables will be passed to all service containers. It's not designed - to distinguish which variable should go where.* +*Note: All variables will be passed to all services containers. It's not +designed to distinguish which variable should go where.* -#### PostgreSQL service example +### PostgreSQL service example -To configure the database name for [postgres][postgres-hub] service, you need -to set the `POSTGRES_DB` variable: +See the specific documentation for +[using PostgreSQL as a service](../services/postgres.md). -```yaml -... +### MySQL service example -services: -- postgres +See the specific documentation for +[using MySQL as a service](../services/mysql.md). -variables: - POSTGRES_DB: gitlab - -... -``` - -For a real example visit . - -#### MySQL service example - -To use the [mysql][mysql-hub] service with an empty password during the time of -build, you need to set the `MYSQL_ALLOW_EMPTY_PASSWORD` variable: - -```yaml -... - -services: -- mysql - -variables: - MYSQL_ALLOW_EMPTY_PASSWORD: "yes" - -... -``` - -### How Docker integration works +## How Docker integration works Below is a high level overview of the steps performed by docker during build time. @@ -226,7 +198,7 @@ time. 1. Check exit status of build script. 1. Remove build container and all created service containers. -### How to debug a build locally +## How to debug a build locally *Note: The following commands are run without root privileges. You should be able to run docker with your regular user account.* From 57d71520bdc2ba79ba8182802cd944d4fb42a192 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 22:30:40 +0100 Subject: [PATCH 060/102] Make tooltip less confusing --- app/controllers/projects/application_controller.rb | 2 +- app/services/files/base_service.rb | 2 +- app/views/projects/blob/_actions.html.haml | 6 +++--- app/views/projects/tree/_tree_header.html.haml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index d3f926b62bc..eea41dbeeb1 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -21,7 +21,7 @@ class Projects::ApplicationController < ApplicationController unless @repository.branch_names.include?(@ref) redirect_to( namespace_project_tree_path(@project.namespace, @project, @ref), - notice: "This action is not allowed unless you are on top of a branch" + notice: "This action is not allowed unless you are on a branch" ) end end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 309abcd6d0b..9a67b160940 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -53,7 +53,7 @@ module Files unless project.empty_repo? unless repository.branch_names.include?(@current_branch) - raise_error("You can only create or edit files when you are on top of a branch") + raise_error("You can only create or edit files when you are on a branch") end if @current_branch != @target_branch diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 41d8765623e..b1df8d19938 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -21,6 +21,6 @@ %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete - elsif !on_top_of_branch? .btn-group{ role: "group" } - %button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on top of a branch.", data: {container: 'body'}} Edit - %button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on top of a branch.", data: {container: 'body'}} Replace - %button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on top of a branch.", data: {container: 'body'}} Delete + %button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on a branch.", data: {container: 'body'}} Edit + %button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on a branch.", data: {container: 'body'}} Replace + %button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on a branch.", data: {container: 'body'}} Delete diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 4767f824ac1..2baa5ecd974 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -32,5 +32,5 @@ New directory - elsif !on_top_of_branch? %li - %span.btn.add-to-tree.disabled.has_tooltip{href: '#', title: "You can only add files when you are on top of a branch.", data: {container: 'body'}} + %span.btn.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}} = icon('plus') From 0f89e6905db6fe821332013e8ddb2707079e0ba4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 22:31:40 +0100 Subject: [PATCH 061/102] Fix spec --- features/project/source/browse_files.feature | 6 ------ features/steps/project/source/browse_files.rb | 4 ---- 2 files changed, 10 deletions(-) diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 37f99b37619..439787f2641 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -110,12 +110,6 @@ Feature: Project Source Browse Files Given I visit a binary file in the repo Then I cannot see the edit button - Scenario: If I don't have edit permission the edit link is disabled - Given public project "Community" - And I visit project "Community" source page - And I click on ".gitignore" file in repo - Then The edit button is disabled - @javascript Scenario: I can edit and commit file Given I click on ".gitignore" file in repo diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 2792174cc93..f2b95764267 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -53,10 +53,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(page).not_to have_link 'edit' end - step 'The edit button is disabled' do - expect(page).to have_css '.disabled', text: 'Edit' - end - step 'I can edit code' do set_new_content expect(evaluate_script('blob.editor.getValue()')).to eq new_gitignore_content From 1464a69c76cf492ad8b9674e24260e917dc7d2ef Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 22:37:07 +0100 Subject: [PATCH 062/102] Tweak text of documentation --- .gitignore | 1 - doc/workflow/merge_when_build_succeeds.md | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 881b3fb81ac..f5b6427ca03 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,6 @@ public/assets/ public/uploads.* public/uploads/ shared/artifacts/ -TODO rails_best_practices_output.html /tags tmp/ diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md index 3c055650c49..75e1fdff2b2 100644 --- a/doc/workflow/merge_when_build_succeeds.md +++ b/doc/workflow/merge_when_build_succeeds.md @@ -1,12 +1,12 @@ # Merge When Build Succeeds -When reviewing a merge request that looks ready to merge but still has one or more CI builds running, you can set it to be merged automatically when the build succeeds. This way, you don't have to wait for the build to finish and remember to merge the merge request then. +When reviewing a merge request that looks ready to merge but still has one or more CI builds running, you can set it to be merged automatically when all builds succeed. This way, you don't have to wait for the builds to finish and remember to merge the request manually. ![Enable](merge_when_build_succeeds/enable.png) -When you hit the "Merge When Build Succeeds" button, the status of the Merge Request will be updated to represent the impending merge. If you cannot wait for the build to succeed and want to build immediately, this option is available in the dropdown menu on the right of the main button. +When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait for the build to succeed and want to merge immediately, this option is available in the dropdown menu on the right of the main button. -Both team developers and the author of the merge request have the option to cancel the automatic merge when they find a reason it shouldn't be merged after all. +Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged after all. ![Status](merge_when_build_succeeds/status.png) From 31b4960eca490994bd192f53c4b3f5283fe962d5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 22:39:26 +0100 Subject: [PATCH 063/102] Fix specs --- app/views/projects/issues/_issue.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 7d9fc8d2c86..f9cf4910df3 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -25,7 +25,7 @@ %li = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do = icon('comments') - = notes_count + = note_count .issue-info #{issue.to_reference} · From 1e7156ed701945349bef484d35c7f53f2ee4474b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Dec 2015 23:49:05 +0200 Subject: [PATCH 064/102] Use the name of the linked containers instead of localhost https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1917#note_2833978 --- doc/ci/services/mysql.md | 5 ++++- doc/ci/services/postgres.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md index 44bc613e6f7..c66d77122b2 100644 --- a/doc/ci/services/mysql.md +++ b/doc/ci/services/mysql.md @@ -24,12 +24,15 @@ variables: And then configure your application to use the database, for example: ```yaml -Host: localhost +Host: mysql User: root Password: mysql_strong_password Database: el_duderino ``` +If you are wondering why we used `mysql` for the `Host`, read more at +[How is service linked to the build](../docker/using_docker_images.md#how-is-service-linked-to-the-build). + You can also use any other docker image available on [Docker Hub][hub-mysql]. For example, to use MySQL 5.5 the service becomes `mysql:5.5`. diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md index db5be070ee7..17d21dbda1c 100644 --- a/doc/ci/services/postgres.md +++ b/doc/ci/services/postgres.md @@ -24,12 +24,15 @@ variables: And then configure your application to use the database, for example: ```yaml -Host: localhost +Host: postgres User: runner Password: Database: nice_marmot ``` +If you are wondering why we used `postgres` for the `Host`, read more at +[How is service linked to the build](../docker/using_docker_images.md#how-is-service-linked-to-the-build). + You can also use any other docker image available on [Docker Hub][hub-pg]. For example, to use PostgreSQL 9.3 the service becomes `postgres:9.3`. From 53fbb0ecda36be783a46413f59551f13090ea9df Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 8 Dec 2015 16:53:48 -0500 Subject: [PATCH 065/102] Improve styling for mixed list styles Tasks in an ordered list will now also show their numbers. Closes #2488 Related to #3921 --- app/assets/stylesheets/framework/lists.scss | 10 ++++++++-- app/assets/stylesheets/pages/notes.scss | 10 +++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 927641216e4..f770e535b30 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -88,8 +88,14 @@ ul.bordered-list { } } -li.task-list-item { - list-style-type: none; +ul.task-list { + li.task-list-item { + list-style-type: none; + } + + ul:not(.task-list) { + padding-left: 1.3em; + } } ul.content-list { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 1980fe0d458..4dff87abaa4 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -109,13 +109,9 @@ ul.notes { } } - // Reduce left padding of first task list ul element - ul.task-list:first-child { - padding-left: 10px; - - // sub-tasks should be padded normally - ul { - padding-left: 20px; + ul.task-list { + ul:not(.task-list) { + padding-left: 1.3em; } } From 23522a9b34c30d61e950820a197b6bd353ba1914 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 23:28:28 +0100 Subject: [PATCH 066/102] Fix specs --- spec/features/issues_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 0af5e6fc1a6..3d6d87e764a 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -293,10 +293,10 @@ describe 'Issues', feature: true do end def first_issue - page.all('ul.issues-list li').first.text + page.all('ul.issues-list > li').first.text end def last_issue - page.all('ul.issues-list li').last.text + page.all('ul.issues-list > li').last.text end end From 56f25c3e615752817446d5aedb7d66d25d815641 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Dec 2015 13:37:01 +0100 Subject: [PATCH 067/102] Improve text indication visibility on snippets --- CHANGELOG | 1 + app/helpers/visibility_level_helper.rb | 26 +++--- .../application_settings/_form.html.haml | 4 +- app/views/shared/snippets/_header.html.haml | 2 +- spec/helpers/visibility_level_helper_spec.rb | 89 ++++++++----------- 5 files changed, 55 insertions(+), 67 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 67f70e676c2..c291d4b6dec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.3.0 (unreleased) - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch - Add languages page to graphs - Block LDAP user when they are no longer found in the LDAP server + - Improve wording on project visibility levels (Zeger-Jan van de Weg) v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 72c65030f94..2e69ce923a2 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -12,22 +12,22 @@ module VisibilityLevelHelper # Return the description for the +level+ argument. # - # +level+ One of the Gitlab::VisibilityLevel constants - # +form_model+ Either a model object (Project, Snippet, etc.) or the name of - # a Project or Snippet class. + # +level+ One of the Gitlab::VisibilityLevel constants + # +form_model+ Either a model object (Project, Snippet, etc.) or the name of + # a Project or Snippet class. def visibility_level_description(level, form_model) - case form_model.is_a?(String) ? form_model : form_model.class.name - when 'PersonalSnippet', 'ProjectSnippet', 'Snippet' - snippet_visibility_level_description(level) - when 'Project' + case form_model + when Project project_visibility_level_description(level) + when Snippet + snippet_visibility_level_description(level, form_model) end end def project_visibility_level_description(level) case level when Gitlab::VisibilityLevel::PRIVATE - "Project access must be granted explicitly for each user." + "Project access must be granted explicitly to each user." when Gitlab::VisibilityLevel::INTERNAL "The project can be cloned by any logged in user." when Gitlab::VisibilityLevel::PUBLIC @@ -35,12 +35,16 @@ module VisibilityLevelHelper end end - def snippet_visibility_level_description(level) + def snippet_visibility_level_description(level, snippet = nil) case level when Gitlab::VisibilityLevel::PRIVATE - "The snippet is visible only for me." + if snippet.is_a? ProjectSnippet + "The snippet is visible only to project members." + else + "The snippet is visible only to me." + end when Gitlab::VisibilityLevel::INTERNAL - "The snippet is visible for any logged in user." + "The snippet is visible to any logged in user." when Gitlab::VisibilityLevel::PUBLIC "The snippet can be accessed without any authentication." end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index ddaf0e0e8ff..6c355366948 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -14,11 +14,11 @@ .form-group.project-visibility-level-holder = f.label :default_project_visibility, class: 'control-label col-sm-2' .col-sm-10 - = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: 'Project') + = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project) .form-group.project-visibility-level-holder = f.label :default_snippet_visibility, class: 'control-label col-sm-2' .col-sm-10 - = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: 'Snippet') + = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: PersonalSnippet) .form-group = f.label :restricted_visibility_levels, class: 'control-label col-sm-2' .col-sm-10 diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 89c1d7122b0..eb0fd21c2d4 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -1,6 +1,6 @@ .issuable-details .page-title - .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level), data: { container: 'body' }} + .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }} = visibility_level_icon(@snippet.visibility_level, fw: false) = visibility_level_label(@snippet.visibility_level) Snippet ##{@snippet.id} diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index c4f7693329c..aafc24397a9 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -7,69 +7,52 @@ describe VisibilityLevelHelper do init_haml_helpers end - let(:project) { create(:project) } + let(:project) { build(:project) } + let(:personal_snippet) { build(:personal_snippet) } + let(:project_snippet) { build(:project_snippet) } describe 'visibility_level_description' do - shared_examples 'a visibility level description' do - let(:desc) do - visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, - form_model) - end - - let(:expected_class) do - class_name = case form_model.class.name - when 'String' - form_model - else - form_model.class.name - end - - class_name.match(/(project|snippet)$/i)[0] - end - - it 'should refer to the correct class' do - expect(desc).to match(/#{expected_class}/i) + context 'used with a Project' do + it 'delegates projects to #project_visibility_level_description' do + expect(visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, project)) + .to match /project/i end end - context 'form_model argument is a String' do - context 'model object is a personal snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { 'PersonalSnippet' } - end - end - - context 'model object is a project snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { 'ProjectSnippet' } - end - end - - context 'model object is a project' do - it_behaves_like 'a visibility level description' do - let(:form_model) { 'Project' } - end + context 'called with a Snippet' do + it 'delegates snippets to #snippet_visibility_level_description' do + expect(visibility_level_description(Gitlab::VisibilityLevel::INTERNAL, project_snippet)) + .to match /snippet/i end end + end - context 'form_model argument is a model object' do - context 'model object is a personal snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { create(:personal_snippet) } - end - end + describe "#project_visibility_level_description" do + it "describes private projects" do + expect(project_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE)) + .to eq "Project access must be granted explicitly to each user." + end - context 'model object is a project snippet' do - it_behaves_like 'a visibility level description' do - let(:form_model) { create(:project_snippet, project: project) } - end - end + it "describes public projects" do + expect(project_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC)) + .to eq "The project can be cloned without any authentication." + end + end - context 'model object is a project' do - it_behaves_like 'a visibility level description' do - let(:form_model) { project } - end - end + describe "#snippet_visibility_level_description" do + it 'describes visibility only for me' do + expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, personal_snippet)) + .to eq "The snippet is visible only to me." + end + + it 'describes visibility for project members' do + expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, project_snippet)) + .to eq "The snippet is visible only to project members." + end + + it 'defaults to personal snippet' do + expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE)) + .to eq "The snippet is visible only to me." end end From 430aa23a5b82b34912dc86c323087fabd2849b7c Mon Sep 17 00:00:00 2001 From: Eirik Lygre Date: Wed, 9 Dec 2015 00:20:51 +0100 Subject: [PATCH 068/102] Add spinach tests for the clone protocol selection. --- features/explore/projects.feature | 21 +++++++++++++++++++-- features/steps/explore/projects.rb | 8 ++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/features/explore/projects.feature b/features/explore/projects.feature index 5d3870827f5..63b0ac65c4b 100644 --- a/features/explore/projects.feature +++ b/features/explore/projects.feature @@ -31,8 +31,17 @@ Feature: Explore Projects Then I should see empty public project details And I should see empty public project details with http clone info - Scenario: I visit an empty public project page as user + Scenario: I visit an empty public project page as user with no ssh-keys Given I sign in as a user + And I have no ssh keys + And public empty project "Empty Public Project" + When I visit empty project page + Then I should see empty public project details + And I should see empty public project details with http clone info + + Scenario: I visit an empty public project page as user with an ssh-key + Given I sign in as a user + And I have ssh key "ssh-rsa Work" And public empty project "Empty Public Project" When I visit empty project page Then I should see empty public project details @@ -57,8 +66,16 @@ Feature: Explore Projects Then I should see project "Community" home page And I should see an http link to the repository - Scenario: I visit public project page as user + Scenario: I visit public project page as user with no ssh-keys Given I sign in as a user + And I have no ssh keys + When I visit project "Community" page + Then I should see project "Community" home page + And I should see an http link to the repository + + Scenario: I visit public project page as user with an ssh-key + Given I sign in as a user + And I have ssh key "ssh-rsa Work" When I visit project "Community" page Then I should see project "Community" home page And I should see an ssh link to the repository diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb index 8b498e7b4a6..82cb9e24062 100644 --- a/features/steps/explore/projects.rb +++ b/features/steps/explore/projects.rb @@ -144,4 +144,12 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps def public_merge_request @public_merge_request ||= MergeRequest.find_by!(title: 'Bug fix for public project') end + + step 'I have ssh key "ssh-rsa Work"' do + create(:key, user: @user, title: "ssh-rsa Work", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work") + end + + step 'I have no ssh keys' do + Key.delete_all + end end From 9dd26749634648e2cd27b223eea5d291c0a78f37 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 9 Dec 2015 01:43:57 +0100 Subject: [PATCH 069/102] Fix only 20 group members showing on project member page Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/project_members_controller.rb | 2 +- app/views/projects/project_members/_group_members.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 07eb94e4f48..8364fc293b7 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -23,7 +23,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @group_members = @group_members.where(user_id: users) end - @group_members = @group_members.order('access_level DESC').limit(20) + @group_members = @group_members.order('access_level DESC') end @project_member = @project.project_members.new diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index d2810f9707a..1c2458fa144 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -10,7 +10,7 @@ = icon('pencil-square-o') Manage group members %ul.content-list - - members.each do |member| + - members.limit(20).each do |member| = render 'groups/group_members/group_member', member: member, show_controls: false - if members.count > 20 %li From 94dc9ef9e1a85b8a4506358479a549dc3a1306b6 Mon Sep 17 00:00:00 2001 From: Eirik Lygre Date: Wed, 9 Dec 2015 02:02:53 +0100 Subject: [PATCH 070/102] Move ssh-key steps into SharedUser. Change references in "Explore Projects" feature. Apply ssh-key requirement to "Project Create" feature. --- features/explore/projects.feature | 4 ++-- features/project/create.feature | 2 ++ features/steps/explore/projects.rb | 9 +-------- features/steps/project/create.rb | 1 + features/steps/shared/user.rb | 8 ++++++++ 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/features/explore/projects.feature b/features/explore/projects.feature index 63b0ac65c4b..629859e960d 100644 --- a/features/explore/projects.feature +++ b/features/explore/projects.feature @@ -41,7 +41,7 @@ Feature: Explore Projects Scenario: I visit an empty public project page as user with an ssh-key Given I sign in as a user - And I have ssh key "ssh-rsa Work" + And I have an ssh key And public empty project "Empty Public Project" When I visit empty project page Then I should see empty public project details @@ -75,7 +75,7 @@ Feature: Explore Projects Scenario: I visit public project page as user with an ssh-key Given I sign in as a user - And I have ssh key "ssh-rsa Work" + And I have an ssh key When I visit project "Community" page Then I should see project "Community" home page And I should see an ssh link to the repository diff --git a/features/project/create.feature b/features/project/create.feature index e9dc4fe6b3c..a86079143e5 100644 --- a/features/project/create.feature +++ b/features/project/create.feature @@ -7,6 +7,7 @@ Feature: Project Create Scenario: User create a project Given I sign in as a user When I visit new project page + And I have an ssh key And fill project form with valid data Then I should see project page And I should see empty project instuctions @@ -14,6 +15,7 @@ Feature: Project Create @javascript Scenario: Empty project instructions Given I sign in as a user + And I have an ssh key When I visit new project page And fill project form with valid data Then I see empty project instuctions diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb index 82cb9e24062..f819dec2192 100644 --- a/features/steps/explore/projects.rb +++ b/features/steps/explore/projects.rb @@ -2,6 +2,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject + include SharedUser step 'I should see project "Empty Public Project"' do expect(page).to have_content "Empty Public Project" @@ -144,12 +145,4 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps def public_merge_request @public_merge_request ||= MergeRequest.find_by!(title: 'Bug fix for public project') end - - step 'I have ssh key "ssh-rsa Work"' do - create(:key, user: @user, title: "ssh-rsa Work", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work") - end - - step 'I have no ssh keys' do - Key.delete_all - end end diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb index 0d39e1997b5..f90218f3791 100644 --- a/features/steps/project/create.rb +++ b/features/steps/project/create.rb @@ -1,6 +1,7 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps include SharedAuthentication include SharedPaths + include SharedUser step 'fill project form with valid data' do fill_in 'project_path', with: 'Empty' diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb index 250cc5b94f3..33c146f6dbc 100644 --- a/features/steps/shared/user.rb +++ b/features/steps/shared/user.rb @@ -18,4 +18,12 @@ module SharedUser def user_exists(name, options = {}) User.find_by(name: name) || create(:user, { name: name, admin: false }.merge(options)) end + + step 'I have an ssh key' do + create(:key, user: @user, title: "An ssh-key", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work") + end + + step 'I have no ssh keys' do + Key.delete_all + end end From 1d250b48922ef88b14ad8ce2af157e2f2d256773 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 9 Dec 2015 02:50:46 +0100 Subject: [PATCH 071/102] Move Network page from separate tab to sub tab of Commits Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/views/layouts/nav/_project.html.haml | 16 ++++++++-------- app/views/projects/commits/_head.html.haml | 6 ++++++ app/views/projects/network/_head.html.haml | 2 +- app/views/projects/network/show.html.haml | 3 ++- features/project/active_tab.feature | 11 ++++++----- features/steps/project/active_tab.rb | 4 ++++ features/steps/shared/project_tab.rb | 4 ---- 8 files changed, 28 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c291d4b6dec..747afc82aa0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,7 @@ v 8.3.0 (unreleased) - Add languages page to graphs - Block LDAP user when they are no longer found in the LDAP server - Improve wording on project visibility levels (Zeger-Jan van de Weg) + - Make Network page as sub tab of Commits v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 87a7707b095..2fcba7bd672 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -32,7 +32,7 @@ Files - if project_nav_tab? :commits - = nav_link(controller: %w(commit commits compare repositories tags branches releases)) do + = nav_link(controller: %w(commit commits compare repositories tags branches releases network)) do = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do = icon('history fw') %span @@ -46,13 +46,6 @@ Builds %span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all) - - if project_nav_tab? :network - = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do - = icon('code-fork fw') - %span - Network - - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do @@ -118,3 +111,10 @@ = icon('cogs fw') %span Settings + + -# Global shortcut to network page for compatibility + - if project_nav_tab? :network + %li.hidden + = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do + Network + diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index f11a41cfd7b..2f604c82f11 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -3,6 +3,12 @@ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do Commits %span.badge= number_with_delimiter(@repository.commit_count) + + - if project_nav_tab? :network + = nav_link(controller: %w(network)) do + = link_to namespace_project_network_path(@project.namespace, @project, current_ref), class: 'shortcuts-network' do + Network + = nav_link(controller: :compare) do = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do Compare diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index 9e0e0dc6bb0..28a617538b5 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -1,4 +1,4 @@ -.gray-content-block.top-block.append-bottom-default +.gray-content-block.append-bottom-default .tree-ref-holder = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 16005161df6..8065663ca2a 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,5 +1,6 @@ - page_title "Network", @ref -= header_title project_title(@project, "Network", namespace_project_network_path(@project.namespace, @project, current_ref)) += render "projects/commits/header_title" += render "projects/commits/head" = render "head" .project-network .controls diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index 8661ea98c20..2fd097d100b 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -20,11 +20,6 @@ Feature: Project Active Tab Then the active main tab should be Commits And no other main tabs should be active - Scenario: On Project Network - Given I visit my project's network page - Then the active main tab should be Network - And no other main tabs should be active - Scenario: On Project Issues Given I visit my project's issues page Then the active main tab should be Issues @@ -83,6 +78,12 @@ Feature: Project Active Tab And no other sub tabs should be active And the active main tab should be Commits + Scenario: On Project Commits/Network + Given I visit my project's network page + Then the active sub tab should be Network + And no other sub tabs should be active + And the active main tab should be Commits + Scenario: On Project Commits/Compare Given I visit my project's commits page And I click the "Compare" tab diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 9e96fa5ba49..94a5dd744d0 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -67,6 +67,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ensure_active_sub_tab('Commits') end + step 'the active sub tab should be Network' do + ensure_active_sub_tab('Network') + end + step 'the active sub tab should be Compare' do ensure_active_sub_tab('Compare') end diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb index 33ff7084e30..3bad28dc283 100644 --- a/features/steps/shared/project_tab.rb +++ b/features/steps/shared/project_tab.rb @@ -16,10 +16,6 @@ module SharedProjectTab ensure_active_main_tab('Commits') end - step 'the active main tab should be Network' do - ensure_active_main_tab('Network') - end - step 'the active main tab should be Graphs' do ensure_active_main_tab('Graphs') end From 9dbc768db8304004c08957710d423c0b2b54510a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 8 Dec 2015 21:00:01 -0800 Subject: [PATCH 072/102] Update annotations --- app/models/lfs_object.rb | 12 ++++++++++++ app/models/lfs_objects_project.rb | 11 +++++++++++ app/models/note.rb | 1 + app/models/project.rb | 1 + app/models/user.rb | 1 + spec/factories/lfs_objects.rb | 12 ++++++++++++ spec/factories/lfs_objects_projects.rb | 11 +++++++++++ spec/factories/notes.rb | 1 + spec/factories/projects.rb | 1 + spec/models/note_spec.rb | 1 + spec/models/project_spec.rb | 1 + spec/models/user_spec.rb | 1 + 12 files changed, 54 insertions(+) diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 18657c3e1c8..86b1b7e2f99 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: lfs_objects +# +# id :integer not null, primary key +# oid :string(255) not null +# size :integer not null +# created_at :datetime +# updated_at :datetime +# file :string(255) +# + class LfsObject < ActiveRecord::Base has_many :lfs_objects_projects, dependent: :destroy has_many :projects, through: :lfs_objects_projects diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb index 0fd5f089db9..890736bfc80 100644 --- a/app/models/lfs_objects_project.rb +++ b/app/models/lfs_objects_project.rb @@ -1,3 +1,14 @@ +# == Schema Information +# +# Table name: lfs_objects_projects +# +# id :integer not null, primary key +# lfs_object_id :integer not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + class LfsObjectsProject < ActiveRecord::Base belongs_to :project belongs_to :lfs_object diff --git a/app/models/note.rb b/app/models/note.rb index 8d433c57ceb..98c29ddc4cd 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -16,6 +16,7 @@ # system :boolean default(FALSE), not null # st_diff :text # updated_by_id :integer +# is_award :boolean default(FALSE), not null # require 'carrierwave/orm/activerecord' diff --git a/app/models/project.rb b/app/models/project.rb index af034a6692b..cb965ce1b9e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -28,6 +28,7 @@ # import_type :string(255) # import_source :string(255) # commit_count :integer default(0) +# import_error :text # require 'carrierwave/orm/activerecord' diff --git a/app/models/user.rb b/app/models/user.rb index cfed797e725..7155dd2bea7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -56,6 +56,7 @@ # project_view :integer default(0) # consumed_timestep :integer # layout :integer default(0) +# hide_project_limit :boolean default(FALSE) # require 'carrierwave/orm/activerecord' diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb index 7fb2d77ca32..2da107ba24b 100644 --- a/spec/factories/lfs_objects.rb +++ b/spec/factories/lfs_objects.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: lfs_objects +# +# id :integer not null, primary key +# oid :string(255) not null +# size :integer not null +# created_at :datetime +# updated_at :datetime +# file :string(255) +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb index 93de6607df8..3772236a77a 100644 --- a/spec/factories/lfs_objects_projects.rb +++ b/spec/factories/lfs_objects_projects.rb @@ -1,3 +1,14 @@ +# == Schema Information +# +# Table name: lfs_objects_projects +# +# id :integer not null, primary key +# lfs_object_id :integer not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 9d777ddfccd..35a20adeef3 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -16,6 +16,7 @@ # system :boolean default(FALSE), not null # st_diff :text # updated_by_id :integer +# is_award :boolean default(FALSE), not null # require_relative '../support/repo_helpers' diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 1d500a11ad7..112213377ff 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -28,6 +28,7 @@ # import_type :string(255) # import_source :string(255) # commit_count :integer default(0) +# import_error :text # FactoryGirl.define do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index f347f537550..e7e8887baf2 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -16,6 +16,7 @@ # system :boolean default(FALSE), not null # st_diff :text # updated_by_id :integer +# is_award :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 06a02c13bf1..dc703d8095c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -28,6 +28,7 @@ # import_type :string(255) # import_source :string(255) # commit_count :integer default(0) +# import_error :text # require 'spec_helper' diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a0f78d3b336..1aad37fa02e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -56,6 +56,7 @@ # project_view :integer default(0) # consumed_timestep :integer # layout :integer default(0) +# hide_project_limit :boolean default(FALSE) # require 'spec_helper' From 7b0ac5b6b49675790491dcf8764ebfb17b93012c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 8 Dec 2015 21:31:22 -0800 Subject: [PATCH 073/102] Remove default_branch from project API creation since an empty repository has no branches to start. Closes #3937 --- doc/api/projects.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 42919a312ae..43a50a9a810 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -398,7 +398,6 @@ Parameters: - `user_id` (required) - user_id of owner - `name` (required) - new project name - `description` (optional) - short project description -- `default_branch` (optional) - 'master' by default - `issues_enabled` (optional) - `merge_requests_enabled` (optional) - `builds_enabled` (optional) From dd980b46f1833ba72207846c66995e517b63b50d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 09:08:53 +0100 Subject: [PATCH 074/102] Use up and down --- db/migrate/20140122112253_create_merge_request_diffs.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb index 47bee269f9c..f34e30925df 100644 --- a/db/migrate/20140122112253_create_merge_request_diffs.rb +++ b/db/migrate/20140122112253_create_merge_request_diffs.rb @@ -1,5 +1,5 @@ class CreateMergeRequestDiffs < ActiveRecord::Migration - def change + def up create_table :merge_request_diffs do |t| t.string :state, null: false, default: 'collected' t.text :st_commits, null: true @@ -14,4 +14,8 @@ class CreateMergeRequestDiffs < ActiveRecord::Migration change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647 end end + + def down + drop_table :merge_request_diffs + end end From 28351806aa1f89f584d6905365fd34f5f0e8bbc7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 09:59:19 +0100 Subject: [PATCH 075/102] Give merge request widget the vars it desires --- app/controllers/projects/merge_requests_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3240c77e994..530f3d3dcb8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds] before_action :define_show_vars, only: [:show, :diffs, :commits, :builds] + before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds] # Allow read any merge_request @@ -301,6 +302,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def define_widget_vars + @ci_commit = @merge_request.ci_commit + end + def invalid_mr # Render special view for MR with removed source or target branch render 'invalid' From 903151141da36ed9b8c2333a9b66f5c611e4efb6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 09:59:39 +0100 Subject: [PATCH 076/102] Tweak specs --- .../merge_requests/merge_when_build_succeeds_spec.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index ee2fb25e9e5..a674124aab7 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -43,20 +43,14 @@ feature 'Merge When Build Succeeds', feature: true, js: true do context 'When it is enabled' do let(:merge_request) do - create(:merge_request_with_diffs, source_project: project, author: user, - merge_user: user, title: "MepMep", merge_when_build_succeeds: true) + create(:merge_request_with_diffs, :simple, source_project: project, author: user, + merge_user: user, title: "MepMep", merge_when_build_succeeds: true) end let!(:ci_commit) { create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } let!(:ci_build) { create(:ci_build, commit: ci_commit) } before do - merge_request.source_project.team << [user, :master] - merge_request.source_branch = "feature" - merge_request.target_branch = "master" - merge_request.save! - - login_as user visit_merge_request(merge_request) end From 20b55981bc09c82dcbc523123cbd81a5bf2ae593 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 10:50:28 +0100 Subject: [PATCH 077/102] Tag feature specs --- features/project/commits/branches.feature | 1 + features/project/commits/comments.feature | 1 + features/project/commits/commits.feature | 1 + features/project/commits/diff_comments.feature | 1 + features/project/commits/tags.feature | 1 + features/project/commits/user_lookup.feature | 1 + features/project/issues/award_emoji.feature | 1 + features/project/issues/filter_labels.feature | 1 + features/project/issues/issues.feature | 2 +- features/project/issues/labels.feature | 1 + features/project/issues/milestones.feature | 1 + features/project/merge_requests.feature | 1 + features/project/merge_requests/accept.feature | 1 + 13 files changed, 13 insertions(+), 1 deletion(-) diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature index 65d8e48b9b3..5103ca12947 100644 --- a/features/project/commits/branches.feature +++ b/features/project/commits/branches.feature @@ -1,3 +1,4 @@ +@project_commits Feature: Project Commits Branches Background: Given I sign in as a user diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature index 320f008abb6..fafb54b183a 100644 --- a/features/project/commits/comments.feature +++ b/features/project/commits/comments.feature @@ -1,3 +1,4 @@ +@project_commits Feature: Project Commits Comments Background: Given I sign in as a user diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index e4beeb59adc..21367fd76a0 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -1,3 +1,4 @@ +@project_commits Feature: Project Commits Background: Given I sign in as a user diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index d6e0c84537e..2bde4c8a99b 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -1,3 +1,4 @@ +@project_commits Feature: Project Commits Diff Comments Background: Given I sign in as a user diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature index 56ee091acc0..a4be39b2d40 100644 --- a/features/project/commits/tags.feature +++ b/features/project/commits/tags.feature @@ -1,3 +1,4 @@ +@project_commits Feature: Project Commits Tags Background: Given I sign in as a user diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature index db51d4a6cfa..c18f4e070f3 100644 --- a/features/project/commits/user_lookup.feature +++ b/features/project/commits/user_lookup.feature @@ -1,3 +1,4 @@ +@project_commits Feature: Project Commits User Lookup Background: Given I sign in as a user diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature index 2609f129d07..0ce99e855c6 100644 --- a/features/project/issues/award_emoji.feature +++ b/features/project/issues/award_emoji.feature @@ -1,3 +1,4 @@ +@project_issues Feature: Award Emoji Background: Given I sign in as a user diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature index e316f519861..e07f8053fb7 100644 --- a/features/project/issues/filter_labels.feature +++ b/features/project/issues/filter_labels.feature @@ -1,3 +1,4 @@ +@project_issues Feature: Project Issues Filter Labels Background: Given I sign in as a user diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 28cc43ef710..f08b30e0b88 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -1,3 +1,4 @@ +@project_issues Feature: Project Issues Background: Given I sign in as a user @@ -196,4 +197,3 @@ Feature: Project Issues And I should not see labels field And I submit new issue "500 error on profile" Then I should see issue "500 error on profile" - diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature index 039a7d83cb1..45de57f18e3 100644 --- a/features/project/issues/labels.feature +++ b/features/project/issues/labels.feature @@ -1,3 +1,4 @@ +@project_issues Feature: Project Issues Labels Background: Given I sign in as a user diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature index c1a20e9b488..1af05b3c326 100644 --- a/features/project/issues/milestones.feature +++ b/features/project/issues/milestones.feature @@ -1,3 +1,4 @@ +@project_issues Feature: Project Issues Milestones Background: Given I sign in as a user diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 6cd081c868e..35ace948888 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -1,3 +1,4 @@ +@project_merge_requests Feature: Project Merge Requests Background: Given I sign in as a user diff --git a/features/project/merge_requests/accept.feature b/features/project/merge_requests/accept.feature index 9bc2b7c8eca..d5e4f2b0bd8 100644 --- a/features/project/merge_requests/accept.feature +++ b/features/project/merge_requests/accept.feature @@ -1,3 +1,4 @@ +@project_merge_requests Feature: Project Merge Requests Acceptance Background: Given There is an open Merge Request From 9ebdee09673d79b57c07c7332ee87791229b6f72 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 10:50:38 +0100 Subject: [PATCH 078/102] Split up feature specs more --- .gitlab-ci.yml | 11 +++++++++-- lib/tasks/spinach.rake | 26 +++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e8290fb36b2..527ccb08ab7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,9 +39,16 @@ spec:other: - ruby - mysql -spinach:project: +spinach:project:half: script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project + - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half + tags: + - ruby + - mysql + +spinach:project:rest: + script: + - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest tags: - ruby - mysql diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake index d5a96fd38f4..3acfc6e2075 100644 --- a/lib/tasks/spinach.rake +++ b/lib/tasks/spinach.rake @@ -1,11 +1,31 @@ Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach') namespace :spinach do + namespace :project do + desc "GitLab | Spinach | Run project commits, issues and merge requests spinach features" + task :half do + cmds = [ + %W(rake gitlab:setup), + %W(spinach --tags @project_commits,@project_issues,@project_merge_requests), + ] + run_commands(cmds) + end + + desc "GitLab | Spinach | Run remaining project spinach features" + task :rest do + cmds = [ + %W(rake gitlab:setup), + %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets,~@project_commits,~@project_issues,~@project_merge_requests), + ] + run_commands(cmds) + end + end + desc "GitLab | Spinach | Run project spinach features" task :project do cmds = [ %W(rake gitlab:setup), - %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets,~@commits), + %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets), ] run_commands(cmds) end @@ -14,7 +34,7 @@ namespace :spinach do task :other do cmds = [ %W(rake gitlab:setup), - %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets,@commits), + %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets), ] run_commands(cmds) end @@ -33,4 +53,4 @@ def run_commands(cmds) cmds.each do |cmd| system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!") end -end \ No newline at end of file +end From 1e8d703a854b372b12a2ad41b5e54d2abc028f74 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 10:50:51 +0100 Subject: [PATCH 079/102] Tag model specs --- spec/models/broadcast_message_spec.rb | 2 +- spec/models/build_spec.rb | 2 +- spec/models/ci/commit_spec.rb | 2 +- spec/models/ci/project_services/hip_chat_message_spec.rb | 2 +- spec/models/ci/project_services/hip_chat_service_spec.rb | 2 +- spec/models/ci/project_services/mail_service_spec.rb | 2 +- spec/models/ci/project_services/slack_message_spec.rb | 2 +- spec/models/ci/project_services/slack_service_spec.rb | 2 +- spec/models/ci/project_spec.rb | 2 +- spec/models/ci/runner_project_spec.rb | 2 +- spec/models/ci/runner_spec.rb | 2 +- spec/models/ci/service_spec.rb | 2 +- spec/models/ci/trigger_spec.rb | 2 +- spec/models/ci/variable_spec.rb | 2 +- spec/models/ci/web_hook_spec.rb | 2 +- spec/models/commit_range_spec.rb | 2 +- spec/models/commit_spec.rb | 2 +- spec/models/commit_status_spec.rb | 2 +- spec/models/concerns/case_sensitivity_spec.rb | 2 +- spec/models/deploy_key_spec.rb | 2 +- spec/models/deploy_keys_project_spec.rb | 2 +- spec/models/event_spec.rb | 2 +- spec/models/external_issue_spec.rb | 2 +- spec/models/external_wiki_service_spec.rb | 2 +- spec/models/generic_commit_status_spec.rb | 2 +- spec/models/global_milestone_spec.rb | 2 +- spec/models/group_spec.rb | 2 +- spec/models/hooks/project_hook_spec.rb | 2 +- spec/models/hooks/service_hook_spec.rb | 2 +- spec/models/hooks/system_hook_spec.rb | 2 +- spec/models/hooks/web_hook_spec.rb | 2 +- spec/models/issue_spec.rb | 2 +- spec/models/key_spec.rb | 2 +- spec/models/label_link_spec.rb | 2 +- spec/models/label_spec.rb | 2 +- spec/models/member_spec.rb | 2 +- spec/models/members/group_member_spec.rb | 2 +- spec/models/members/project_member_spec.rb | 2 +- spec/models/merge_request_spec.rb | 2 +- spec/models/milestone_spec.rb | 2 +- spec/models/namespace_spec.rb | 2 +- spec/models/note_spec.rb | 2 +- spec/models/project_security_spec.rb | 2 +- spec/models/project_services/buildkite_service_spec.rb | 2 +- spec/models/project_services/drone_ci_service_spec.rb | 2 +- spec/models/project_services/flowdock_service_spec.rb | 2 +- spec/models/project_services/gemnasium_service_spec.rb | 2 +- spec/models/project_services/gitlab_ci_service_spec.rb | 2 +- .../project_services/gitlab_issue_tracker_service_spec.rb | 2 +- spec/models/project_services/hipchat_service_spec.rb | 2 +- spec/models/project_services/irker_service_spec.rb | 2 +- spec/models/project_services/jira_service_spec.rb | 2 +- spec/models/project_services/pushover_service_spec.rb | 2 +- .../models/project_services/slack_service/issue_message_spec.rb | 2 +- .../models/project_services/slack_service/merge_message_spec.rb | 2 +- spec/models/project_services/slack_service/note_message_spec.rb | 2 +- spec/models/project_services/slack_service/push_message_spec.rb | 2 +- spec/models/project_services/slack_service_spec.rb | 2 +- spec/models/project_snippet_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- spec/models/project_team_spec.rb | 2 +- spec/models/project_wiki_spec.rb | 2 +- spec/models/protected_branch_spec.rb | 2 +- spec/models/repository_spec.rb | 2 +- spec/models/service_spec.rb | 2 +- spec/models/snippet_spec.rb | 2 +- spec/models/user_spec.rb | 2 +- spec/models/wiki_page_spec.rb | 2 +- 68 files changed, 68 insertions(+), 68 deletions(-) diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 2b325f44f64..e4cac105110 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe BroadcastMessage do +describe BroadcastMessage, models: true do subject { create(:broadcast_message) } it { is_expected.to be_valid } diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 839b4c6b16e..70c831b7cbe 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -25,7 +25,7 @@ require 'spec_helper' -describe Ci::Build do +describe Ci::Build, models: true do let(:project) { FactoryGirl.create :ci_project } let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index a13f6458cac..89813cdf7fc 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -18,7 +18,7 @@ require 'spec_helper' -describe Ci::Commit do +describe Ci::Commit, models: true do let(:project) { FactoryGirl.create :ci_project } let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } diff --git a/spec/models/ci/project_services/hip_chat_message_spec.rb b/spec/models/ci/project_services/hip_chat_message_spec.rb index e23d6ae2c28..7d54b6cf84c 100644 --- a/spec/models/ci/project_services/hip_chat_message_spec.rb +++ b/spec/models/ci/project_services/hip_chat_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::HipChatMessage do +describe Ci::HipChatMessage, models: true do subject { Ci::HipChatMessage.new(build) } let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) } diff --git a/spec/models/ci/project_services/hip_chat_service_spec.rb b/spec/models/ci/project_services/hip_chat_service_spec.rb index d9ccc855edf..714f1e17e0b 100644 --- a/spec/models/ci/project_services/hip_chat_service_spec.rb +++ b/spec/models/ci/project_services/hip_chat_service_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe Ci::HipChatService do +describe Ci::HipChatService, models: true do describe "Validations" do diff --git a/spec/models/ci/project_services/mail_service_spec.rb b/spec/models/ci/project_services/mail_service_spec.rb index c03be3ef75f..638d9a4a626 100644 --- a/spec/models/ci/project_services/mail_service_spec.rb +++ b/spec/models/ci/project_services/mail_service_spec.rb @@ -14,7 +14,7 @@ require 'spec_helper' -describe Ci::MailService do +describe Ci::MailService, models: true do describe "Associations" do it { is_expected.to belong_to :project } end diff --git a/spec/models/ci/project_services/slack_message_spec.rb b/spec/models/ci/project_services/slack_message_spec.rb index 8adda6c86cc..226032b4cda 100644 --- a/spec/models/ci/project_services/slack_message_spec.rb +++ b/spec/models/ci/project_services/slack_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::SlackMessage do +describe Ci::SlackMessage, models: true do subject { Ci::SlackMessage.new(commit) } let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) } diff --git a/spec/models/ci/project_services/slack_service_spec.rb b/spec/models/ci/project_services/slack_service_spec.rb index 1ac7dfe568d..e7d7d5d6f4c 100644 --- a/spec/models/ci/project_services/slack_service_spec.rb +++ b/spec/models/ci/project_services/slack_service_spec.rb @@ -14,7 +14,7 @@ require 'spec_helper' -describe Ci::SlackService do +describe Ci::SlackService, models: true do describe "Associations" do it { is_expected.to belong_to :project } end diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index ac7e38bbcb0..346471aa9b5 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -27,7 +27,7 @@ require 'spec_helper' -describe Ci::Project do +describe Ci::Project, models: true do let(:project) { FactoryGirl.create :ci_project } let(:gl_project) { project.gl_project } subject { project } diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb index 37682c6ea0c..da8491357a5 100644 --- a/spec/models/ci/runner_project_spec.rb +++ b/spec/models/ci/runner_project_spec.rb @@ -11,6 +11,6 @@ require 'spec_helper' -describe Ci::RunnerProject do +describe Ci::RunnerProject, models: true do pending "add some examples to (or delete) #{__FILE__}" end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 9a1233b9095..6ebb5e86863 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' -describe Ci::Runner do +describe Ci::Runner, models: true do describe '#display_name' do it 'should return the description if it has a value' do runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') diff --git a/spec/models/ci/service_spec.rb b/spec/models/ci/service_spec.rb index 36cda988eb4..34e3af7f810 100644 --- a/spec/models/ci/service_spec.rb +++ b/spec/models/ci/service_spec.rb @@ -14,7 +14,7 @@ require 'spec_helper' -describe Ci::Service do +describe Ci::Service, models: true do describe "Associations" do it { is_expected.to belong_to :project } diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index b8aa3c1e777..61eb3c08296 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -12,7 +12,7 @@ require 'spec_helper' -describe Ci::Trigger do +describe Ci::Trigger, models: true do let(:project) { FactoryGirl.create :ci_project } describe 'before_validation' do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index a515f5881ff..31b56953a13 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -13,7 +13,7 @@ require 'spec_helper' -describe Ci::Variable do +describe Ci::Variable, models: true do subject { Ci::Variable.new } let(:secret_value) { 'secret' } diff --git a/spec/models/ci/web_hook_spec.rb b/spec/models/ci/web_hook_spec.rb index 2865482a212..1a4edec9d4f 100644 --- a/spec/models/ci/web_hook_spec.rb +++ b/spec/models/ci/web_hook_spec.rb @@ -11,7 +11,7 @@ require 'spec_helper' -describe Ci::WebHook do +describe Ci::WebHook, models: true do describe "Associations" do it { is_expected.to belong_to :project } end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 3c1009a2eb0..9307d97e214 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CommitRange do +describe CommitRange, models: true do describe 'modules' do subject { described_class } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 38a3dc1f4a6..ecf37b40c58 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Commit do +describe Commit, models: true do let(:project) { create(:project) } let(:commit) { project.commit } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index dca0715eed8..5e311ead28b 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -33,7 +33,7 @@ require 'spec_helper' -describe CommitStatus do +describe CommitStatus, models: true do let(:commit) { FactoryGirl.create :ci_commit } let(:commit_status) { FactoryGirl.create :commit_status, commit: commit } diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb index f7ed30f8198..25b3f4e50da 100644 --- a/spec/models/concerns/case_sensitivity_spec.rb +++ b/spec/models/concerns/case_sensitivity_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CaseSensitivity do +describe CaseSensitivity, models: true do describe '.iwhere' do let(:connection) { ActiveRecord::Base.connection } let(:model) { Class.new { include CaseSensitivity } } diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 95729932459..64ba778afea 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe DeployKey do +describe DeployKey, models: true do let(:project) { create(:project) } let(:deploy_key) { create(:deploy_key, projects: [project]) } diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index 0eb22599d18..8aedbfb8636 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -11,7 +11,7 @@ require 'spec_helper' -describe DeployKeysProject do +describe DeployKeysProject, models: true do describe "Associations" do it { is_expected.to belong_to(:deploy_key) } it { is_expected.to belong_to(:project) } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index ae53f7a536b..071582b0282 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -16,7 +16,7 @@ require 'spec_helper' -describe Event do +describe Event, models: true do describe "Associations" do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:target) } diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index 7744610db78..6ec6b9037a4 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ExternalIssue do +describe ExternalIssue, models: true do let(:project) { double('project', to_reference: 'namespace1/project1') } let(:issue) { described_class.new('EXT-1234', project) } diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/external_wiki_service_spec.rb index 4bd5b0be61c..b198aa77526 100644 --- a/spec/models/external_wiki_service_spec.rb +++ b/spec/models/external_wiki_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe ExternalWikiService do +describe ExternalWikiService, models: true do include ExternalWikiHelper describe "Associations" do it { should belong_to :project } diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index c86314c454c..d61c1c96bde 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -33,7 +33,7 @@ require 'spec_helper' -describe GenericCommitStatus do +describe GenericCommitStatus, models: true do let(:commit) { FactoryGirl.create :ci_commit } let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, commit: commit } diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index 6eeff30b20e..ba03e6aabd0 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GlobalMilestone do +describe GlobalMilestone, models: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:group) { create(:group) } diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 6f166b5ab75..646f767e6fe 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -16,7 +16,7 @@ require 'spec_helper' -describe Group do +describe Group, models: true do let!(:group) { create(:group) } describe 'associations' do diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index a2dc66fce3e..645ee0b929a 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -18,7 +18,7 @@ require 'spec_helper' -describe ProjectHook do +describe ProjectHook, models: true do describe '.push_hooks' do it 'should return hooks for push events only' do hook = create(:project_hook, push_events: true) diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index 16641c12124..1455661485b 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -18,7 +18,7 @@ require "spec_helper" -describe ServiceHook do +describe ServiceHook, models: true do describe "Associations" do it { is_expected.to belong_to :service } end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 02d2cc2c77a..138b87a9a06 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -18,7 +18,7 @@ require "spec_helper" -describe SystemHook do +describe SystemHook, models: true do describe "execute" do before(:each) do @system_hook = create(:system_hook) diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 35042788c65..2d90b0793cc 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -18,7 +18,7 @@ require 'spec_helper' -describe ProjectHook do +describe ProjectHook, models: true do describe "Associations" do it { is_expected.to belong_to :project } end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index c9aa1b063c6..52271c7c8c6 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe Issue do +describe Issue, models: true do describe "Associations" do it { is_expected.to belong_to(:milestone) } end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 2f819f60cbb..d7fe01976d8 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe Key do +describe Key, models: true do describe "Associations" do it { is_expected.to belong_to(:user) } end diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index 8c240826582..dc7510b1de3 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -12,7 +12,7 @@ require 'spec_helper' -describe LabelLink do +describe LabelLink, models: true do let(:label) { create(:label_link) } it { expect(label).to be_valid } diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 511ee8cbd96..696fbf7e0aa 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -13,7 +13,7 @@ require 'spec_helper' -describe Label do +describe Label, models: true do let(:label) { create(:label) } describe 'associations' do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 57f840c1e91..2aedca20df2 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' -describe Member do +describe Member, models: true do describe "Associations" do it { is_expected.to belong_to(:user) } end diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 652026729bb..5424c9b9cba 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' -describe GroupMember do +describe GroupMember, models: true do context 'notification' do describe "#after_create" do it "should send email to user" do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index ee912bf12a2..9f26d9eb5ce 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -19,7 +19,7 @@ require 'spec_helper' -describe ProjectMember do +describe ProjectMember, models: true do describe :import_team do before do @abilities = Six.new diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 567c911425c..6a456ca5ea0 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -25,7 +25,7 @@ require 'spec_helper' -describe MergeRequest do +describe MergeRequest, models: true do subject { create(:merge_request) } describe 'associations' do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 77c58627322..30a71987d86 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -15,7 +15,7 @@ require 'spec_helper' -describe Milestone do +describe Milestone, models: true do describe "Associations" do it { is_expected.to belong_to(:project) } it { is_expected.to have_many(:issues) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index a98b9cb7321..4fa2d2bc4d2 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -16,7 +16,7 @@ require 'spec_helper' -describe Namespace do +describe Namespace, models: true do let!(:namespace) { create(:namespace) } it { is_expected.to have_many :projects } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index e7e8887baf2..cd3c868ecc5 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -21,7 +21,7 @@ require 'spec_helper' -describe Note do +describe Note, models: true do describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:noteable) } diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb index f600a240c46..3643ad1b052 100644 --- a/spec/models/project_security_spec.rb +++ b/spec/models/project_security_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Project do +describe Project, models: true do describe :authorization do before do @p1 = create(:project) diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 230807ea672..88cd624877a 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe BuildkiteService do +describe BuildkiteService, models: true do describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index e9967f5fe0b..a2cf68a9e38 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe DroneCiService do +describe DroneCiService, models: true do describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to have_one(:service_hook) } diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index 16296607a94..ff7fbcaa004 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe FlowdockService do +describe FlowdockService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 9e156472316..ecb3ccb1673 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe GemnasiumService do +describe GemnasiumService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index b9006b693b2..835bf364050 100644 --- a/spec/models/project_services/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe GitlabCiService do +describe GitlabCiService, models: true do describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to have_one(:service_hook) } 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 e34ca09bffc..3518dbd1728 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe GitlabIssueTrackerService do +describe GitlabIssueTrackerService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index f67d7b30980..c96ab548149 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe HipchatService do +describe HipchatService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index 7d483a44c53..b783b1a576e 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -22,7 +22,7 @@ require 'spec_helper' require 'socket' require 'json' -describe IrkerService do +describe IrkerService, models: true do describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 576f5fc79eb..7d91ebe9ce6 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe JiraService do +describe JiraService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index ac10ffbd39b..96039f9491b 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe PushoverService do +describe PushoverService, models: true do describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb index b78d92f23a1..97e6f03e308 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::IssueMessage do +describe SlackService::IssueMessage, models: true do subject { SlackService::IssueMessage.new(args) } let(:args) do diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb index 581c50d6c88..dae8bd90922 100644 --- a/spec/models/project_services/slack_service/merge_message_spec.rb +++ b/spec/models/project_services/slack_service/merge_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::MergeMessage do +describe SlackService::MergeMessage, models: true do subject { SlackService::MergeMessage.new(args) } let(:args) do diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb index 21fb575480b..ebf8837570e 100644 --- a/spec/models/project_services/slack_service/note_message_spec.rb +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::NoteMessage do +describe SlackService::NoteMessage, models: true do let(:color) { '#345' } before do diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb index ddc290820d1..cda9ee670b0 100644 --- a/spec/models/project_services/slack_service/push_message_spec.rb +++ b/spec/models/project_services/slack_service/push_message_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SlackService::PushMessage do +describe SlackService::PushMessage, models: true do subject { SlackService::PushMessage.new(args) } let(:args) do diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 97b60e19e40..a9e0afad90f 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe SlackService do +describe SlackService, models: true do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index 3e8f106d27f..cc92eb0bd9f 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -17,7 +17,7 @@ require 'spec_helper' -describe ProjectSnippet do +describe ProjectSnippet, models: true do describe "Associations" do it { is_expected.to belong_to(:project) } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dc703d8095c..6ddb0e2b8f7 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -33,7 +33,7 @@ require 'spec_helper' -describe Project do +describe Project, models: true do describe 'associations' do it { is_expected.to belong_to(:group) } it { is_expected.to belong_to(:namespace) } diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 26e8fdae472..5cd5ae327bf 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe ProjectTeam do +describe ProjectTeam, models: true do let(:master) { create(:user) } let(:reporter) { create(:user) } let(:guest) { create(:user) } diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 3b889144447..876b927eaea 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe ProjectWiki do +describe ProjectWiki, models: true do let(:project) { create(:empty_project) } let(:repository) { project.repository } let(:user) { project.owner } diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 1e6937b536c..7e956cf6779 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -12,7 +12,7 @@ require 'spec_helper' -describe ProtectedBranch do +describe ProtectedBranch, models: true do describe 'Associations' do it { is_expected.to belong_to(:project) } end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index fa261e64c35..e6c415da267 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Repository do +describe Repository, models: true do include RepoHelpers let(:repository) { create(:project).repository } diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 692e5fda3ba..0ca82365b98 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -20,7 +20,7 @@ require 'spec_helper' -describe Service do +describe Service, models: true do describe "Associations" do it { is_expected.to belong_to :project } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 81581838675..eb2dbbdc5a4 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -17,7 +17,7 @@ require 'spec_helper' -describe Snippet do +describe Snippet, models: true do describe 'modules' do subject { described_class } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 1aad37fa02e..daa9d1087bf 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -61,7 +61,7 @@ require 'spec_helper' -describe User do +describe User, models: true do include Gitlab::CurrentSettings describe 'modules' do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index d7802d1734f..c1b03838aa9 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe WikiPage do +describe WikiPage, models: true do let(:project) { create(:empty_project) } let(:user) { project.owner } let(:wiki) { ProjectWiki.new(project, user) } From ae0b9017315b38dd42b4a1c9b6fb1daa78fee28a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 10:51:01 +0100 Subject: [PATCH 080/102] Split up specs more --- .gitlab-ci.yml | 9 ++++++++- lib/tasks/spec.rake | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 527ccb08ab7..943e40d5303 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,6 +24,13 @@ spec:api: - ruby - mysql +spec:models: + script: + - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models + tags: + - ruby + - mysql + spec:benchmark: script: - RAILS_ENV=test bundle exec rake spec:benchmark @@ -96,7 +103,7 @@ flay: - mysql bundler:audit: - script: + script: - "bundle exec bundle-audit update" - "bundle exec bundle-audit check" tags: diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake index 365ff2defd4..049e17e6c11 100644 --- a/lib/tasks/spec.rake +++ b/lib/tasks/spec.rake @@ -19,6 +19,15 @@ namespace :spec do run_commands(cmds) end + desc 'GitLab | Rspec | Run model specs' + task :models do + cmds = [ + %W(rake gitlab:setup), + %W(rspec spec --tag @models) + ] + run_commands(cmds) + end + desc 'GitLab | Rspec | Run benchmark specs' task :benchmark do cmds = [ @@ -32,7 +41,7 @@ namespace :spec do task :other do cmds = [ %W(rake gitlab:setup), - %W(rspec spec --tag ~@api --tag ~@feature --tag ~@benchmark) + %W(rspec spec --tag ~@api,~@feature,~@models,~@benchmark) ] run_commands(cmds) end From 7a5e77c0a0591ff5d121f93deced27b798ddbee3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 11:09:25 +0100 Subject: [PATCH 081/102] Fix rspec tag syntax --- lib/tasks/spec.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake index 049e17e6c11..343e4b63524 100644 --- a/lib/tasks/spec.rake +++ b/lib/tasks/spec.rake @@ -41,7 +41,7 @@ namespace :spec do task :other do cmds = [ %W(rake gitlab:setup), - %W(rspec spec --tag ~@api,~@feature,~@models,~@benchmark) + %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@benchmark) ] run_commands(cmds) end From 905090dbe9d140ef950995c63204a864a161dbdb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 11:18:21 +0100 Subject: [PATCH 082/102] Remove RedCloth and no longer allow bundle-audit to fail --- .gitlab-ci.yml | 3 +-- Gemfile | 1 - Gemfile.lock | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e8290fb36b2..c6d1e622bd0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -89,10 +89,9 @@ flay: - mysql bundler:audit: - script: + script: - "bundle exec bundle-audit update" - "bundle exec bundle-audit check" tags: - ruby - mysql - allow_failure: true diff --git a/Gemfile b/Gemfile index 91ad4b6fb4d..473770f53e5 100644 --- a/Gemfile +++ b/Gemfile @@ -93,7 +93,6 @@ gem 'html-pipeline', '~> 1.11.0' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'github-markup', '~> 1.3.1' gem 'redcarpet', '~> 3.3.3' -gem 'RedCloth', '~> 4.2.9' gem 'rdoc', '~>3.6' gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' diff --git a/Gemfile.lock b/Gemfile.lock index 3979418ed45..37ba22b7ceb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,6 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (2.3.2) - RedCloth (4.2.9) ace-rails-ap (2.0.1) actionmailer (4.2.4) actionpack (= 4.2.4) @@ -826,7 +825,6 @@ PLATFORMS ruby DEPENDENCIES - RedCloth (~> 4.2.9) ace-rails-ap (~> 2.0.1) activerecord-deprecated_finders (~> 1.0.3) activerecord-session_store (~> 0.1.0) From 13d6bab177d2a703d19b2fc5f28271ed083273c3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 11:55:36 +0100 Subject: [PATCH 083/102] Tag lib specs --- spec/lib/ci/ansi2html_spec.rb | 2 +- spec/lib/ci/charts_spec.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/lib/disable_email_interceptor_spec.rb | 2 +- spec/lib/extracts_path_spec.rb | 2 +- spec/lib/file_size_validator_spec.rb | 2 +- spec/lib/git_ref_validator_spec.rb | 2 +- spec/lib/gitlab/asciidoc_spec.rb | 2 +- spec/lib/gitlab/auth_spec.rb | 2 +- spec/lib/gitlab/backend/grack_auth_spec.rb | 2 +- spec/lib/gitlab/backend/shell_spec.rb | 4 ++-- spec/lib/gitlab/bitbucket_import/client_spec.rb | 2 +- spec/lib/gitlab/bitbucket_import/project_creator_spec.rb | 2 +- spec/lib/gitlab/closing_issue_extractor_spec.rb | 2 +- spec/lib/gitlab/color_schemes_spec.rb | 2 +- spec/lib/gitlab/database_spec.rb | 2 +- spec/lib/gitlab/diff/file_spec.rb | 2 +- spec/lib/gitlab/diff/parser_spec.rb | 2 +- spec/lib/gitlab/email/attachment_uploader_spec.rb | 2 +- spec/lib/gitlab/email/receiver_spec.rb | 2 +- spec/lib/gitlab/email/reply_parser_spec.rb | 2 +- spec/lib/gitlab/git_access_spec.rb | 2 +- spec/lib/gitlab/git_access_wiki_spec.rb | 2 +- spec/lib/gitlab/github_import/client_spec.rb | 2 +- spec/lib/gitlab/github_import/project_creator_spec.rb | 2 +- spec/lib/gitlab/gitlab_import/client_spec.rb | 2 +- spec/lib/gitlab/gitlab_import/project_creator_spec.rb | 2 +- spec/lib/gitlab/gitorious_import/project_creator_spec.rb | 2 +- spec/lib/gitlab/google_code_import/client_spec.rb | 2 +- spec/lib/gitlab/google_code_import/importer_spec.rb | 2 +- spec/lib/gitlab/google_code_import/project_creator_spec.rb | 2 +- spec/lib/gitlab/incoming_email_spec.rb | 2 +- spec/lib/gitlab/inline_diff_spec.rb | 2 +- spec/lib/gitlab/key_fingerprint_spec.rb | 2 +- spec/lib/gitlab/ldap/access_spec.rb | 2 +- spec/lib/gitlab/ldap/adapter_spec.rb | 2 +- spec/lib/gitlab/ldap/auth_hash_spec.rb | 2 +- spec/lib/gitlab/ldap/authentication_spec.rb | 2 +- spec/lib/gitlab/ldap/config_spec.rb | 2 +- spec/lib/gitlab/ldap/user_spec.rb | 2 +- spec/lib/gitlab/lfs/lfs_router_spec.rb | 2 +- spec/lib/gitlab/markdown/autolink_filter_spec.rb | 2 +- .../lib/gitlab/markdown/commit_range_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/commit_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/cross_project_reference_spec.rb | 2 +- spec/lib/gitlab/markdown/emoji_filter_spec.rb | 2 +- .../gitlab/markdown/external_issue_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/external_link_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/issue_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/label_reference_filter_spec.rb | 2 +- .../gitlab/markdown/merge_request_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/redactor_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/relative_link_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/sanitization_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/task_list_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/upload_link_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/user_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markup_helper_spec.rb | 2 +- spec/lib/gitlab/note_data_builder_spec.rb | 2 +- spec/lib/gitlab/o_auth/auth_hash_spec.rb | 2 +- spec/lib/gitlab/o_auth/user_spec.rb | 2 +- spec/lib/gitlab/popen_spec.rb | 2 +- spec/lib/gitlab/project_search_results_spec.rb | 2 +- spec/lib/gitlab/push_data_builder_spec.rb | 2 +- spec/lib/gitlab/reference_extractor_spec.rb | 2 +- spec/lib/gitlab/regex_spec.rb | 2 +- spec/lib/gitlab/sherlock/collection_spec.rb | 2 +- spec/lib/gitlab/sherlock/file_sample_spec.rb | 2 +- spec/lib/gitlab/sherlock/line_profiler_spec.rb | 2 +- spec/lib/gitlab/sherlock/line_sample_spec.rb | 2 +- spec/lib/gitlab/sherlock/location_spec.rb | 2 +- spec/lib/gitlab/sherlock/middleware_spec.rb | 2 +- spec/lib/gitlab/sherlock/query_spec.rb | 2 +- spec/lib/gitlab/sherlock/transaction_spec.rb | 2 +- spec/lib/gitlab/sql/union_spec.rb | 2 +- spec/lib/gitlab/themes_spec.rb | 2 +- spec/lib/gitlab/upgrader_spec.rb | 2 +- spec/lib/gitlab/uploads_transfer_spec.rb | 2 +- spec/lib/gitlab/url_builder_spec.rb | 2 +- spec/lib/gitlab/version_info_spec.rb | 2 +- spec/lib/repository_cache_spec.rb | 2 +- 85 files changed, 86 insertions(+), 86 deletions(-) diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/ci/ansi2html_spec.rb index 75c023bbc43..3a2b568f4c7 100644 --- a/spec/lib/ci/ansi2html_spec.rb +++ b/spec/lib/ci/ansi2html_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::Ansi2html do +describe Ci::Ansi2html, lib: true do subject { Ci::Ansi2html } it "prints non-ansi as-is" do diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb index 83e2ad220b8..50a77308cde 100644 --- a/spec/lib/ci/charts_spec.rb +++ b/spec/lib/ci/charts_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Charts" do +describe Ci::Charts, lib: true do context "build_times" do before do diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 6f287719ba6..ce4a5244bd0 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe GitlabCiYamlProcessor do + describe GitlabCiYamlProcessor, lib: true do let(:path) { 'path' } describe "#builds_for_ref" do diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb index a9624e9a2b7..c2a7b20b84d 100644 --- a/spec/lib/disable_email_interceptor_spec.rb +++ b/spec/lib/disable_email_interceptor_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe DisableEmailInterceptor do +describe DisableEmailInterceptor, lib: true do before do ActionMailer::Base.register_interceptor(DisableEmailInterceptor) end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 48bc60eed16..f38fadda9ba 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ExtractsPath do +describe ExtractsPath, lib: true do include ExtractsPath include RepoHelpers include Gitlab::Application.routes.url_helpers diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb index 12ccc051c74..fda6f9a6c88 100644 --- a/spec/lib/file_size_validator_spec.rb +++ b/spec/lib/file_size_validator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Gitlab::FileSizeValidatorSpec' do +describe FileSizeValidator, lib: true do let(:validator) { FileSizeValidator.new(options) } let(:attachment) { AttachmentUploader.new } let(:note) { create(:note) } diff --git a/spec/lib/git_ref_validator_spec.rb b/spec/lib/git_ref_validator_spec.rb index 4633b6f3934..dc57e94f193 100644 --- a/spec/lib/git_ref_validator_spec.rb +++ b/spec/lib/git_ref_validator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitRefValidator do +describe Gitlab::GitRefValidator, lib: true do it { expect(Gitlab::GitRefValidator.validate('feature/new')).to be_truthy } it { expect(Gitlab::GitRefValidator.validate('implement_@all')).to be_truthy } it { expect(Gitlab::GitRefValidator.validate('my_new_feature')).to be_truthy } diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 03e36fd3552..7d985fb6f1e 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'nokogiri' module Gitlab - describe Asciidoc do + describe Asciidoc, lib: true do let(:input) { 'ascii' } let(:context) { {} } diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 72806bebe1f..aad291c03cd 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Auth do +describe Gitlab::Auth, lib: true do let(:gl_auth) { Gitlab::Auth.new } describe :find do diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb index dfa0e10318a..73458735ad9 100644 --- a/spec/lib/gitlab/backend/grack_auth_spec.rb +++ b/spec/lib/gitlab/backend/grack_auth_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Grack::Auth do +describe Grack::Auth, lib: true do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index b60e23454d6..fd869f48b5c 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Shell do +describe Gitlab::Shell, lib: true do let(:project) { double('Project', id: 7, path: 'diaspora') } let(:gitlab_shell) { Gitlab::Shell.new } @@ -16,7 +16,7 @@ describe Gitlab::Shell do it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") } - describe Gitlab::Shell::KeyAdder do + describe Gitlab::Shell::KeyAdder, lib: true do describe '#add_key' do it 'normalizes space characters in the key' do io = spy diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb index dfe58637eee..aa0699f2ebf 100644 --- a/spec/lib/gitlab/bitbucket_import/client_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::BitbucketImport::Client do +describe Gitlab::BitbucketImport::Client, lib: true do let(:token) { '123456' } let(:secret) { 'secret' } let(:client) { Gitlab::BitbucketImport::Client.new(token, secret) } diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index 0e826a319e0..e1c60e07b4d 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::BitbucketImport::ProjectCreator do +describe Gitlab::BitbucketImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do { diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index fe1b94a484e..99288da1e43 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ClosingIssueExtractor do +describe Gitlab::ClosingIssueExtractor, lib: true do let(:project) { create(:project) } let(:project2) { create(:project) } let(:issue) { create(:issue, project: project) } diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb index c7be45dbcd3..0a1ec66f199 100644 --- a/spec/lib/gitlab/color_schemes_spec.rb +++ b/spec/lib/gitlab/color_schemes_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ColorSchemes do +describe Gitlab::ColorSchemes, lib: true do describe '.body_classes' do it 'returns a space-separated list of class names' do css = described_class.body_classes diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 7cdebdf209a..8461e8ce50d 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Database do +describe Gitlab::Database, lib: true do # These are just simple smoke tests to check if the methods work (regardless # of what they may return). describe '.mysql?' do diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 8b7946f3117..c7cdf8691d6 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Diff::File do +describe Gitlab::Diff::File, lib: true do include RepoHelpers let(:project) { create(:project) } diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index 4d5d1431683..ba577bd28e5 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Diff::Parser do +describe Gitlab::Diff::Parser, lib: true do include RepoHelpers let(:project) { create(:project) } diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb index 8fb432367b6..476a21bf996 100644 --- a/spec/lib/gitlab/email/attachment_uploader_spec.rb +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::Email::AttachmentUploader do +describe Gitlab::Email::AttachmentUploader, lib: true do describe "#execute" do let(:project) { build(:project) } let(:message_raw) { fixture_file("emails/attachment.eml") } diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index e470b7cd5f5..b535413bbd4 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::Email::Receiver do +describe Gitlab::Email::Receiver, lib: true do before do stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") end diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index 7cae1da8050..6f8e9a4be64 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" # Inspired in great part by Discourse's Email::Receiver -describe Gitlab::Email::ReplyParser do +describe Gitlab::Email::ReplyParser, lib: true do describe '#execute' do def test_parse_body(mail_string) described_class.new(Mail::Message.new(mail_string)).execute diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index c7291689e32..9b3a0e3a75f 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitAccess do +describe Gitlab::GitAccess, lib: true do let(:access) { Gitlab::GitAccess.new(actor, project) } let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 4cb91094cb3..77ecfce6f17 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitAccessWiki do +describe Gitlab::GitAccessWiki, lib: true do let(:access) { Gitlab::GitAccessWiki.new(user, project) } let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index 26618120316..49d8cdf4314 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GithubImport::Client do +describe Gitlab::GithubImport::Client, lib: true do let(:token) { '123456' } let(:client) { Gitlab::GithubImport::Client.new(token) } diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index ca61d3c5234..c93a3ebdaec 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GithubImport::ProjectCreator do +describe Gitlab::GithubImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do OpenStruct.new( diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb index c511c515474..e6831e7c383 100644 --- a/spec/lib/gitlab/gitlab_import/client_spec.rb +++ b/spec/lib/gitlab/gitlab_import/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitlabImport::Client do +describe Gitlab::GitlabImport::Client, lib: true do let(:token) { '123456' } let(:client) { Gitlab::GitlabImport::Client.new(token) } diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb index 2d8923d14bb..483f65cd053 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitlabImport::ProjectCreator do +describe Gitlab::GitlabImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do { diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb index c1125ca6357..946712ca38e 100644 --- a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GitoriousImport::ProjectCreator do +describe Gitlab::GitoriousImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') } let(:namespace){ create(:group, owner: user) } diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb index 37985c062b4..85949ae8dc4 100644 --- a/spec/lib/gitlab/google_code_import/client_spec.rb +++ b/spec/lib/gitlab/google_code_import/client_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::GoogleCodeImport::Client do +describe Gitlab::GoogleCodeImport::Client, lib: true do let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } subject { described_class.new(raw_data) } diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 65ad7524cc2..647631271e0 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::GoogleCodeImport::Importer do +describe Gitlab::GoogleCodeImport::Importer, lib: true do let(:mapped_user) { create(:user, username: "thilo123") } let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) } diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb index 35549b48687..499a896ee76 100644 --- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb +++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::GoogleCodeImport::ProjectCreator do +describe Gitlab::GoogleCodeImport::ProjectCreator, lib: true do let(:user) { create(:user) } let(:repo) do Gitlab::GoogleCodeImport::Repository.new( diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 5fdb9c723b1..bcdba8d4c12 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe Gitlab::IncomingEmail do +describe Gitlab::IncomingEmail, lib: true do describe "self.enabled?" do context "when reply by email is enabled" do before do diff --git a/spec/lib/gitlab/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb index 2e0a05088cc..c690c195112 100644 --- a/spec/lib/gitlab/inline_diff_spec.rb +++ b/spec/lib/gitlab/inline_diff_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::InlineDiff do +describe Gitlab::InlineDiff, lib: true do describe '#processing' do let(:diff) do < Date: Wed, 9 Dec 2015 11:55:49 +0100 Subject: [PATCH 084/102] Tag service specs --- spec/services/archive_repository_service_spec.rb | 2 +- spec/services/ci/create_commit_service_spec.rb | 2 +- spec/services/ci/create_trigger_request_service_spec.rb | 2 +- spec/services/ci/event_service_spec.rb | 2 +- spec/services/ci/image_for_build_service_spec.rb | 2 +- spec/services/ci/register_build_service_spec.rb | 2 +- spec/services/ci/web_hook_service_spec.rb | 2 +- spec/services/create_release_service_spec.rb | 2 +- spec/services/create_snippet_service_spec.rb | 2 +- spec/services/destroy_group_service_spec.rb | 2 +- spec/services/event_create_service_spec.rb | 2 +- spec/services/git_hooks_service_spec.rb | 2 +- spec/services/git_push_service_spec.rb | 2 +- spec/services/git_tag_push_service_spec.rb | 2 +- spec/services/issues/bulk_update_service_spec.rb | 2 +- spec/services/issues/close_service_spec.rb | 2 +- spec/services/issues/create_service_spec.rb | 2 +- spec/services/issues/update_service_spec.rb | 2 +- spec/services/merge_requests/close_service_spec.rb | 2 +- spec/services/merge_requests/create_service_spec.rb | 2 +- spec/services/merge_requests/merge_service_spec.rb | 2 +- spec/services/merge_requests/refresh_service_spec.rb | 2 +- spec/services/merge_requests/reopen_service_spec.rb | 2 +- spec/services/merge_requests/update_service_spec.rb | 2 +- spec/services/milestones/close_service_spec.rb | 2 +- spec/services/milestones/create_service_spec.rb | 2 +- spec/services/notes/create_service_spec.rb | 2 +- spec/services/notification_service_spec.rb | 2 +- spec/services/projects/create_service_spec.rb | 2 +- spec/services/projects/destroy_service_spec.rb | 2 +- spec/services/projects/download_service_spec.rb | 2 +- spec/services/projects/fork_service_spec.rb | 2 +- spec/services/projects/transfer_service_spec.rb | 2 +- spec/services/projects/update_service_spec.rb | 2 +- spec/services/projects/upload_service_spec.rb | 2 +- spec/services/search_service_spec.rb | 2 +- spec/services/system_hooks_service_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 2 +- spec/services/test_hook_service_spec.rb | 2 +- spec/services/update_release_service_spec.rb | 2 +- spec/services/update_snippet_service_spec.rb | 2 +- 41 files changed, 41 insertions(+), 41 deletions(-) diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb index f7a36cd9670..bd871605c66 100644 --- a/spec/services/archive_repository_service_spec.rb +++ b/spec/services/archive_repository_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe ArchiveRepositoryService do +describe ArchiveRepositoryService, services: true do let(:project) { create(:project) } subject { ArchiveRepositoryService.new(project, "master", "zip") } diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb index e0ede1d58b7..c2fafca2ad2 100644 --- a/spec/services/ci/create_commit_service_spec.rb +++ b/spec/services/ci/create_commit_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe CreateCommitService do + describe CreateCommitService, services: true do let(:service) { CreateCommitService.new } let(:project) { FactoryGirl.create(:ci_project) } let(:user) { nil } diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb index 2ef4bb50a57..c80cb58163a 100644 --- a/spec/services/ci/create_trigger_request_service_spec.rb +++ b/spec/services/ci/create_trigger_request_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::CreateTriggerRequestService do +describe Ci::CreateTriggerRequestService, services: true do let(:service) { Ci::CreateTriggerRequestService.new } let(:gl_project) { create(:project) } let(:project) { gl_project.ensure_gitlab_ci_project } diff --git a/spec/services/ci/event_service_spec.rb b/spec/services/ci/event_service_spec.rb index 1264e17ff5e..32516c75cf3 100644 --- a/spec/services/ci/event_service_spec.rb +++ b/spec/services/ci/event_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::EventService do +describe Ci::EventService, services: true do let(:project) { FactoryGirl.create :ci_project } let(:user) { double(username: "root", id: 1) } diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb index cda7d0c4a51..b43cabb4ee4 100644 --- a/spec/services/ci/image_for_build_service_spec.rb +++ b/spec/services/ci/image_for_build_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe ImageForBuildService do + describe ImageForBuildService, services: true do let(:service) { ImageForBuildService.new } let(:project) { FactoryGirl.create(:ci_project) } let(:gl_project) { FactoryGirl.create(:project, gitlab_ci_project: project) } diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index b370dfbe113..379e07982fb 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Ci - describe RegisterBuildService do + describe RegisterBuildService, services: true do let!(:service) { RegisterBuildService.new } let!(:gl_project) { FactoryGirl.create :empty_project } let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } diff --git a/spec/services/ci/web_hook_service_spec.rb b/spec/services/ci/web_hook_service_spec.rb index aa48fcbcbfd..e7d8ab30652 100644 --- a/spec/services/ci/web_hook_service_spec.rb +++ b/spec/services/ci/web_hook_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::WebHookService do +describe Ci::WebHookService, services: true do let(:project) { FactoryGirl.create :ci_project } let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb index 26d7f365bbb..61e5ae72f51 100644 --- a/spec/services/create_release_service_spec.rb +++ b/spec/services/create_release_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CreateReleaseService do +describe CreateReleaseService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:tag_name) { project.repository.tag_names.first } diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb index 8edabe9450b..c800dea04fa 100644 --- a/spec/services/create_snippet_service_spec.rb +++ b/spec/services/create_snippet_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CreateSnippetService do +describe CreateSnippetService, services: true do before do @user = create :user @admin = create :user, admin: true diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb index e28564b3866..afa89b84175 100644 --- a/spec/services/destroy_group_service_spec.rb +++ b/spec/services/destroy_group_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe DestroyGroupService do +describe DestroyGroupService, services: true do let!(:user) { create(:user) } let!(:group) { create(:group) } let!(:project) { create(:project, namespace: group) } diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 7756b973ecd..f6dc9d4008f 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe EventCreateService do +describe EventCreateService, services: true do let(:service) { EventCreateService.new } describe 'Issues' do diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index 7e018d3c9fe..2bb9c3b3db3 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GitHooksService do +describe GitHooksService, services: true do include RepoHelpers let(:user) { create :user } diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 17015d29e51..a04c242cf0e 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GitPushService do +describe GitPushService, services: true do include RepoHelpers let(:user) { create :user } diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index eed50c7ebac..e2d15f1a83d 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GitTagPushService do +describe GitTagPushService, services: true do include RepoHelpers let(:user) { create :user } diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb index 4c62fbafd73..6a7ea4b2f44 100644 --- a/spec/services/issues/bulk_update_service_spec.rb +++ b/spec/services/issues/bulk_update_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::BulkUpdateService do +describe Issues::BulkUpdateService, services: true do let(:issue) { create(:issue, project: @project) } before do diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index d711e3da104..3a8daf28f5e 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::CloseService do +describe Issues::CloseService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:issue) { create(:issue, assignee: user2) } diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 7f1ebcb3198..2148d091a57 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::CreateService do +describe Issues::CreateService, services: true do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 73d0b7f7abe..87da0e9618b 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Issues::UpdateService do +describe Issues::UpdateService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 272f3897938..50d0c288790 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::CloseService do +describe MergeRequests::CloseService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, assignee: user2) } diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index cc64d69361e..be8f1676eeb 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::CreateService do +describe MergeRequests::CreateService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index c0961ceb11e..f2160567f0a 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::MergeService do +describe MergeRequests::MergeService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, assignee: user2) } diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7ee4488521d..b892f44af03 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::RefreshService do +describe MergeRequests::RefreshService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:service) { MergeRequests::RefreshService } diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index 05146bf43f4..ac0221998f5 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::ReopenService do +describe MergeRequests::ReopenService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, assignee: user2) } diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index d899b1f01d1..2e9e6e0870d 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequests::UpdateService do +describe MergeRequests::UpdateService, services: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index 034c0f22e12..1cd6eb2ab38 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Milestones::CloseService do +describe Milestones::CloseService, services: true do let(:user) { create(:user) } let(:project) { create(:project) } let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb index 757c9a226d8..c793026e300 100644 --- a/spec/services/milestones/create_service_spec.rb +++ b/spec/services/milestones/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Milestones::CreateService do +describe Milestones::CreateService, services: true do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index cc38d257792..a797a2fe4aa 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Notes::CreateService do +describe Notes::CreateService, services: true do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 35fa412ed80..d7a898e85ff 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe NotificationService do +describe NotificationService, services: true do let(:notification) { NotificationService.new } around(:each) do |example| diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e81c4edb7d8..2d8c316e38d 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::CreateService do +describe Projects::CreateService, services: true do describe :create_by_user do before do @user = create :user diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index e83eef0b1a2..1ec27077717 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::DestroyService do +describe Projects::DestroyService, services: true do let!(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace) } let!(:path) { project.repository.path_to_repo } diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index ddee2e62dfc..5ceed5af9a5 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::DownloadService do +describe Projects::DownloadService, services: true do describe 'File service' do before do @user = create :user diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 1feba6ce048..d1ee60a0aea 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::ForkService do +describe Projects::ForkService, services: true do describe :fork_by_user do before do @from_namespace = create(:namespace) diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 47755bfc990..c46259431aa 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::TransferService do +describe Projects::TransferService, services: true do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index b347fa15f87..c36d4581989 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::UpdateService do +describe Projects::UpdateService, services: true do describe :update_by_user do before do @user = create :user diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index 1b1a80d1fe7..9268a9fb1a2 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Projects::UploadService do +describe Projects::UploadService, services: true do describe 'File service' do before do @user = create :user diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index f57bfaea879..7b3a9a75d7c 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Search::GlobalService' do +describe 'Search::GlobalService', services: true do let(:user) { create(:user) } let(:public_user) { create(:user) } let(:internal_user) { create(:user) } diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index a31fc1e4b07..febc78d2784 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SystemHooksService do +describe SystemHooksService, services: true do let(:user) { create :user } let(:project) { create :project } let(:project_member) { create :project_member } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index a45130bd473..19f63323187 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe SystemNoteService do +describe SystemNoteService, services: true do let(:project) { create(:project) } let(:author) { create(:user) } let(:noteable) { create(:issue, project: project) } diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb index 226196eedae..f034f251ba4 100644 --- a/spec/services/test_hook_service_spec.rb +++ b/spec/services/test_hook_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe TestHookService do +describe TestHookService, services: true do let(:user) { create :user } let(:project) { create :project } let(:hook) { create :project_hook, project: project } diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb index 93368c45b88..bba211089a8 100644 --- a/spec/services/update_release_service_spec.rb +++ b/spec/services/update_release_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe UpdateReleaseService do +describe UpdateReleaseService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:tag_name) { project.repository.tag_names.first } diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb index d7c516e3934..124bb76e678 100644 --- a/spec/services/update_snippet_service_spec.rb +++ b/spec/services/update_snippet_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe UpdateSnippetService do +describe UpdateSnippetService, services: true do before do @user = create :user @admin = create :user, admin: true From 6c6fb1d8bb2c6aa44553ebf35496d64ff6b202d2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 11:56:23 +0100 Subject: [PATCH 085/102] Split up spec:other even more --- .gitlab-ci.yml | 14 ++++++++++++++ lib/tasks/spec.rake | 20 +++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 943e40d5303..aec9936da28 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,20 @@ spec:models: - ruby - mysql +spec:lib: + script: + - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib + tags: + - ruby + - mysql + +spec:services: + script: + - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services + tags: + - ruby + - mysql + spec:benchmark: script: - RAILS_ENV=test bundle exec rake spec:benchmark diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake index 343e4b63524..0985ef3a669 100644 --- a/lib/tasks/spec.rake +++ b/lib/tasks/spec.rake @@ -28,6 +28,24 @@ namespace :spec do run_commands(cmds) end + desc 'GitLab | Rspec | Run service specs' + task :services do + cmds = [ + %W(rake gitlab:setup), + %W(rspec spec --tag @services) + ] + run_commands(cmds) + end + + desc 'GitLab | Rspec | Run lib specs' + task :lib do + cmds = [ + %W(rake gitlab:setup), + %W(rspec spec --tag @lib) + ] + run_commands(cmds) + end + desc 'GitLab | Rspec | Run benchmark specs' task :benchmark do cmds = [ @@ -41,7 +59,7 @@ namespace :spec do task :other do cmds = [ %W(rake gitlab:setup), - %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@benchmark) + %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services --tag ~@benchmark) ] run_commands(cmds) end From 3fd1a36d74c6af0ff040580f014505568d0b3aab Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 12:00:26 +0100 Subject: [PATCH 086/102] Fix spec --- spec/features/atom/users_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index dc41be8246f..52134556339 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -79,6 +79,6 @@ describe "User Feed", feature: true do end def safe_name - html_escape(user.name) + CGI.escapeHTML(user.name) end end From 64eaccf10a7ac70c3dc2f9565a638e9f45a8359c Mon Sep 17 00:00:00 2001 From: Felix Eckhofer Date: Wed, 9 Dec 2015 12:26:18 +0100 Subject: [PATCH 087/102] Update init script only once The init script is already being updated in section 6. --- doc/update/8.1-to-8.2.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index 7b228d6a22f..b08a79ca0aa 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -85,11 +85,10 @@ sudo -u git -H git checkout 0.4.2 sudo -u git -H make ``` -Update the GitLab init script and 'default' file. +Update the GitLab 'default' file. ``` cd /home/git/gitlab -sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab test -e /etc/default/gitlab && \ sudo sed -i.pre-8.2 's/^\([^=]*\)gitlab_git_http_server/\1gitlab_workhorse/' /etc/default/gitlab ``` From f7ff146aaac158ce2099583af5d5620204ec9b93 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 13:52:13 +0100 Subject: [PATCH 088/102] Fix size of disabled 'New file' button --- app/views/projects/tree/_tree_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 0e1f7076608..cefe33e581f 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -32,5 +32,5 @@ New directory - elsif !on_top_of_branch? %li - %span.btn.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}} + %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}} = icon('plus') From 37199719c7da301fae368348df52f90f2af95863 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 9 Dec 2015 16:19:59 +0100 Subject: [PATCH 089/102] Make sure everyone has shared/lfs-objects --- shared/lfs-objects/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 shared/lfs-objects/.gitkeep diff --git a/shared/lfs-objects/.gitkeep b/shared/lfs-objects/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d From 7e8fc4822758057ed8a2239659cdd8f49099613d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Fri, 4 Dec 2015 16:54:25 -0500 Subject: [PATCH 090/102] Normalize email when looking for GitLab users from commit info. #3854 --- app/models/commit.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 8ae5325d16a..fa88a408fa3 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -175,11 +175,11 @@ class Commit end def author - @author ||= User.find_by_any_email(author_email) + @author ||= User.find_by_any_email(author_email.downcase) end def committer - @committer ||= User.find_by_any_email(committer_email) + @committer ||= User.find_by_any_email(committer_email.downcase) end def parents From 2cb542b9d365558923ed125f76c5005188dbee77 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 9 Dec 2015 17:39:13 +0100 Subject: [PATCH 091/102] remove unnecessary code Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/commits/_head.html.haml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 2f604c82f11..fcccb002d7e 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -4,10 +4,9 @@ Commits %span.badge= number_with_delimiter(@repository.commit_count) - - if project_nav_tab? :network - = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, current_ref), class: 'shortcuts-network' do - Network + = nav_link(controller: %w(network)) do + = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do + Network = nav_link(controller: :compare) do = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do From 2c61e3c057af02068bfb57f1c3f921d9f0c87b1d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 9 Dec 2015 17:47:45 +0100 Subject: [PATCH 092/102] Fix test for network shortcut Signed-off-by: Dmitriy Zaporozhets --- features/project/shortcuts.feature | 3 ++- features/steps/project/active_tab.rb | 4 ---- features/steps/shared/project_tab.rb | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature index 0f71c32380b..10e7c234610 100644 --- a/features/project/shortcuts.feature +++ b/features/project/shortcuts.feature @@ -19,7 +19,8 @@ Feature: Project Shortcuts @javascript Scenario: Navigate to network tab Given I press "g" and "n" - Then the active main tab should be Network + Then the active sub tab should be Network + And the active main tab should be Commits @javascript Scenario: Navigate to graphs tab diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 94a5dd744d0..9e96fa5ba49 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -67,10 +67,6 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ensure_active_sub_tab('Commits') end - step 'the active sub tab should be Network' do - ensure_active_sub_tab('Network') - end - step 'the active sub tab should be Compare' do ensure_active_sub_tab('Compare') end diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb index 3bad28dc283..4fc2ece79ff 100644 --- a/features/steps/shared/project_tab.rb +++ b/features/steps/shared/project_tab.rb @@ -49,4 +49,8 @@ module SharedProjectTab step 'the active main tab should be Activity' do ensure_active_main_tab('Activity') end + + step 'the active sub tab should be Network' do + ensure_active_sub_tab('Network') + end end From 3d4b4683ebca56973a0b5f56a558d9cac3c67ed9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 9 Dec 2015 12:08:44 -0500 Subject: [PATCH 093/102] Add CHANGELOG entry for !1998 [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3017c528380..b9a746c780c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.3.0 (unreleased) - Add languages page to graphs - Block LDAP user when they are no longer found in the LDAP server - Improve wording on project visibility levels (Zeger-Jan van de Weg) + - Automatically select default clone protocol based on user preferences (Eirik Lygre) v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) From 7a2a860adcda179578098afb9b84894c1670f2c7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 9 Dec 2015 12:09:00 -0500 Subject: [PATCH 094/102] Simplify shared User SSH key steps --- features/steps/shared/user.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb index 33c146f6dbc..f0721094ee3 100644 --- a/features/steps/shared/user.rb +++ b/features/steps/shared/user.rb @@ -20,10 +20,10 @@ module SharedUser end step 'I have an ssh key' do - create(:key, user: @user, title: "An ssh-key", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work") + create(:personal_key, user: @user) end step 'I have no ssh keys' do - Key.delete_all + @user.keys.delete_all end end From 86aa535562ea627ccea93bcdf7e63468a4a148bf Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 9 Dec 2015 13:35:20 -0500 Subject: [PATCH 095/102] Run db:reset before db:create on CI --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 39d8f59cf07..c614e14e243 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ before_script: - touch log/application.log - touch log/test.log - bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" - - bundle exec rake db:create RAILS_ENV=test + - bundle exec rake db:reset db:create RAILS_ENV=test spec:feature: script: From 98958decce9e3a5764de609f0c730c7c5fde65d6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 9 Dec 2015 13:55:31 -0500 Subject: [PATCH 096/102] Add number_with_delimiter to build counts --- app/views/projects/builds/index.html.haml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 742676305a9..fbf2c293db8 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -12,17 +12,20 @@ %li{class: ('active' if @scope.nil?)} = link_to project_builds_path(@project) do Running - %span.badge.js-running-count= @all_builds.running_or_pending.count(:id) + %span.badge.js-running-count + = number_with_delimiter(@all_builds.running_or_pending.count(:id)) %li{class: ('active' if @scope == 'finished')} = link_to project_builds_path(@project, scope: :finished) do Finished - %span.badge.js-running-count= @all_builds.finished.count(:id) + %span.badge.js-running-count + = number_with_delimiter(@all_builds.finished.count(:id)) %li{class: ('active' if @scope == 'all')} = link_to project_builds_path(@project, scope: :all) do All - %span.badge.js-totalbuilds-count= @all_builds.count(:id) + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_builds.count(:id)) .gray-content-block #{(@scope || 'running').capitalize} builds from this project From 7b50965e9990bcb88f56b771d47514cbeb5316e5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 9 Dec 2015 14:21:08 -0500 Subject: [PATCH 097/102] Update CHANGELOG [ci skip] --- CHANGELOG | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b9a746c780c..01f4c386e1a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,7 +20,6 @@ v 8.3.0 (unreleased) - Add API endpoint to fetch merge request commits list - Expose events API with comment information and author info - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 - - Fix 500 error when creating a merge request that removes a submodule - Run custom Git hooks when branch is created or deleted. - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch - Add languages page to graphs @@ -31,9 +30,10 @@ v 8.3.0 (unreleased) v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) - -v 8.2.3 + - Update documentation for "Guest" permissions + - Properly convert Emoji-only comments into Award Emojis - Webhook payload has an added, modified and removed properties for each commit + - Fix 500 error when creating a merge request that removes a submodule v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) @@ -94,7 +94,6 @@ v 8.2.0 - Add email notification to former assignee upon unassignment (Adam Lieskovský) - New design for project graphs page - Remove deprecated dumped yaml file generated from previous job definitions - - Fix incoming email config defaults - Show specific runners from projects where user is master or owner - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page @@ -252,7 +251,6 @@ v 8.0.2 - Allow AWS S3 Server-Side Encryption with Amazon S3-Managed Keys for backups (Paul Beattie) v 8.0.1 - - Remove git refs used internally by GitLab from network graph (Stan Hu) - Improve CI migration procedure and documentation v 8.0.0 From c5dacce4d7e47a0504975fbb3bfaf478b95f1065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 9 Dec 2015 19:50:00 +0000 Subject: [PATCH 098/102] Use YAML.safe_load --- lib/ci/gitlab_ci_yaml_processor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 3beafcad117..7f54f5f0722 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -10,7 +10,7 @@ module Ci attr_reader :before_script, :image, :services, :variables, :path, :cache def initialize(config, path = nil) - @config = YAML.load(config) + @config = YAML.safe_load(config) @path = path unless @config.is_a? Hash @@ -250,4 +250,4 @@ module Ci end end end -end +end \ No newline at end of file From b4b9df277bb1490ba04a976fb2a59f2a0f603173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 9 Dec 2015 20:58:53 +0000 Subject: [PATCH 099/102] Allow [Symbol] when loading YAML --- lib/ci/gitlab_ci_yaml_processor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 7f54f5f0722..e7fb1b79b7c 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -10,7 +10,7 @@ module Ci attr_reader :before_script, :image, :services, :variables, :path, :cache def initialize(config, path = nil) - @config = YAML.safe_load(config) + @config = YAML.safe_load(config, [Symbol]) @path = path unless @config.is_a? Hash @@ -250,4 +250,4 @@ module Ci end end end -end \ No newline at end of file +end From 48bf0a34e2771096b175555b47133b2573faa4d8 Mon Sep 17 00:00:00 2001 From: Andrew Tomaka Date: Wed, 9 Dec 2015 16:11:21 -0500 Subject: [PATCH 100/102] "No ssh" message should be same on project page --- app/views/projects/empty.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 950ab33825e..503d156661e 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,4 +1,4 @@ -.alert_holder += content_for :flash_message do - if current_user && can?(current_user, :download_code, @project) = render 'shared/no_ssh' = render 'shared/no_password' From ab8ab9b10c6bda35d3a82e0cf52f45c1cd81eb09 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 10 Dec 2015 01:24:10 +0100 Subject: [PATCH 101/102] Fix list with controls display Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 44 ++++++++++++--------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 481e3f33a20..cc48f8c8166 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -7,7 +7,7 @@ padding: 0; list-style: none; - li { + > li { padding: 10px 15px; min-height: 20px; border-bottom: 1px solid #eee; @@ -123,24 +123,6 @@ ul.content-list { padding: 10px 14px; } } - - ul.controls { - list-style: none; - - li { - float: left; - padding-right: 10px; - - .author_link { - display: inline-block; - - .avatar-inline { - margin-left: 0; - margin-right: 0; - } - } - } - } } } @@ -149,3 +131,27 @@ ul.content-list { margin: 0; } } + +ul.controls { + padding-top: 1px; + float: right; + list-style: none; + + .btn { + padding: 10px 14px; + } + + > li { + float: left; + padding-right: 10px; + + .author_link { + display: inline-block; + + .avatar-inline { + margin-left: 0; + margin-right: 0; + } + } + } +} From f4ec906e90b2f8dbf18b359b773e3b31f5da89ff Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Wed, 9 Dec 2015 11:45:26 -0600 Subject: [PATCH 102/102] Use devise paranoid mode and ensure the same message is returned every time Skipped CI because it has already passed. Had to rebase due to CHANGELOG. --- CHANGELOG | 3 +++ app/controllers/passwords_controller.rb | 6 ++++-- config/initializers/devise.rb | 2 +- config/locales/devise.en.yml | 1 - spec/features/password_reset_spec.rb | 26 ++++++++++++------------- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 01f4c386e1a..03574b1e771 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,9 @@ v 8.2.3 - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) - Update documentation for "Guest" permissions - Properly convert Emoji-only comments into Award Emojis + - Enable devise paranoid mode to prevent user enumeration attack + +v 8.2.3 - Webhook payload has an added, modified and removed properties for each commit - Fix 500 error when creating a merge request that removes a submodule diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 2025158d065..f74daff3bd0 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -40,7 +40,9 @@ class PasswordsController < Devise::PasswordsController def throttle_reset return unless resource && resource.recently_sent_password_reset? - redirect_to new_password_path(resource_name), - alert: I18n.t('devise.passwords.recently_reset') + # Throttle reset attempts, but return a normal message to + # avoid user enumeration attack. + redirect_to new_user_session_path, + notice: I18n.t('devise.passwords.send_paranoid_instructions') end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 29506970af2..5fb43a86e13 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -60,7 +60,7 @@ Devise.setup do |config| # It will change confirmation, password recovery and other workflows # to behave the same regardless if the e-mail provided was right or wrong. # Does not affect registerable. - # config.paranoid = true + config.paranoid = true # ==> Configuration for :database_authenticatable # For bcrypt, this is the cost for hashing the password and defaults to 10. If diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 22070e37f07..bd4c3ebc69e 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -30,7 +30,6 @@ en: success: "Successfully authenticated from %{kind} account." passwords: no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." - recently_reset: "Instructions about how to reset your password have already been sent recently. Please wait a few minutes to try again." send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." updated: "Your password has been changed successfully. You are now signed in." diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb index 85e70b4d47f..257d363438c 100644 --- a/spec/features/password_reset_spec.rb +++ b/spec/features/password_reset_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' feature 'Password reset', feature: true do describe 'throttling' do it 'sends reset instructions when not previously sent' do - visit root_path - forgot_password(create(:user)) + user = create(:user) + forgot_password(user) - expect(page).to have_content(I18n.t('devise.passwords.send_instructions')) + expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) expect(current_path).to eq new_user_session_path + expect(user.recently_sent_password_reset?).to be_truthy end it 'sends reset instructions when previously sent more than a minute ago' do @@ -15,26 +16,25 @@ feature 'Password reset', feature: true do user.send_reset_password_instructions user.update_attribute(:reset_password_sent_at, 5.minutes.ago) - visit root_path - forgot_password(user) - - expect(page).to have_content(I18n.t('devise.passwords.send_instructions')) + expect{ forgot_password(user) }.to change{ user.reset_password_sent_at } + expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) expect(current_path).to eq new_user_session_path end - it "throttles multiple resets in a short timespan" do + it 'throttles multiple resets in a short timespan' do user = create(:user) user.send_reset_password_instructions + # Reload because PG handles datetime less precisely than Ruby/Rails + user.reload - visit root_path - forgot_password(user) - - expect(page).to have_content(I18n.t('devise.passwords.recently_reset')) - expect(current_path).to eq new_user_password_path + expect{ forgot_password(user) }.not_to change{ user.reset_password_sent_at } + expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions')) + expect(current_path).to eq new_user_session_path end end def forgot_password(user) + visit root_path click_on 'Forgot your password?' fill_in 'Email', with: user.email click_button 'Reset password'