Commit e405b8f3 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce_upstream' into 'master'

CE upstream



See merge request !183
parents de511d98 60ab7989
...@@ -4,6 +4,11 @@ services: ...@@ -4,6 +4,11 @@ services:
- mysql:latest - mysql:latest
- redis:latest - redis:latest
cache:
key: "ruby22"
paths:
- vendor
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
...@@ -159,6 +164,10 @@ spec:feature:ruby21: ...@@ -159,6 +164,10 @@ spec:feature:ruby21:
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -169,6 +178,10 @@ spec:api:ruby21: ...@@ -169,6 +178,10 @@ spec:api:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -179,6 +192,10 @@ spec:models:ruby21: ...@@ -179,6 +192,10 @@ spec:models:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -189,6 +206,10 @@ spec:lib:ruby21: ...@@ -189,6 +206,10 @@ spec:lib:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -199,6 +220,10 @@ spec:services:ruby21: ...@@ -199,6 +220,10 @@ spec:services:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -209,6 +234,10 @@ spec:benchmark:ruby21: ...@@ -209,6 +234,10 @@ spec:benchmark:ruby21:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake spec:benchmark - RAILS_ENV=test bundle exec rake spec:benchmark
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -220,6 +249,10 @@ spec:other:ruby21: ...@@ -220,6 +249,10 @@ spec:other:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -230,6 +263,10 @@ spinach:project:half:ruby21: ...@@ -230,6 +263,10 @@ spinach:project:half:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -240,6 +277,10 @@ spinach:project:rest:ruby21: ...@@ -240,6 +277,10 @@ spinach:project:rest:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -250,6 +291,11 @@ spinach:other:ruby21: ...@@ -250,6 +291,11 @@ spinach:other:ruby21:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
cache:
key: "ruby21"
paths:
- vendor
tags: tags:
- ruby - ruby
- mysql - mysql
...@@ -2,7 +2,9 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,7 +2,9 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Fix Elasticsearch blob results linking to the wrong reference ID (Stan Hu) - Fix Elasticsearch blob results linking to the wrong reference ID (Stan Hu)
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse) - Cache various Repository methods to improve performance (Yorick Peterse)
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu) - Update New Relic gem to 3.14.1.311 (Stan Hu)
- Add "visibility" flag to GET /projects api endpoint - Add "visibility" flag to GET /projects api endpoint
...@@ -15,11 +17,14 @@ v 8.5.0 (unreleased) ...@@ -15,11 +17,14 @@ v 8.5.0 (unreleased)
- New UI for pagination - New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
set it up set it up
- API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger)
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Fix relative links in other markup formats (Ben Boeckel)
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list - Fix label links for a merge request pointing to issues list
- Don't vendor minified JS - Don't vendor minified JS
- Increase project import timeout to 15 minutes - Increase project import timeout to 15 minutes
- Be more permissive with email address validation: it only has to contain a single '@'
- Display 404 error on group not found - Display 404 error on group not found
- Track project import failure - Track project import failure
- Support Two-factor Authentication for LDAP users - Support Two-factor Authentication for LDAP users
...@@ -32,12 +37,14 @@ v 8.5.0 (unreleased) ...@@ -32,12 +37,14 @@ v 8.5.0 (unreleased)
- Optimized performance of finding issues to be closed by a merge request - Optimized performance of finding issues to be closed by a merge request
- API: Expose MergeRequest#merge_status (Andrei Dziahel) - API: Expose MergeRequest#merge_status (Andrei Dziahel)
- Revert "Add IP check against DNSBLs at account sign-up" - Revert "Add IP check against DNSBLs at account sign-up"
- Actually use the `skip_merges` option in Repository#commits (Tony Chu)
- Fix API to keep request parameters in Link header (Michael Potthoff) - Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues - Prevent parse error when name of project ends with .atom and prevent path issues
- Mark inline difference between old and new paths when a file is renamed - Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu) - Support Akismet spam checking for creation of issues via API (Stan Hu)
- API: Allow to set or update a merge-request's milestone (Kirill Skachkov)
- Improve UI consistency between projects and groups lists - Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page - Add sort dropdown to dashboard projects page
- Fixed logo animation on Safari (Roman Rott) - Fixed logo animation on Safari (Roman Rott)
...@@ -47,11 +54,15 @@ v 8.5.0 (unreleased) ...@@ -47,11 +54,15 @@ v 8.5.0 (unreleased)
- Faster snippet search - Faster snippet search
- Title for milestones should be unique (Zeger-Jan van de Weg) - Title for milestones should be unique (Zeger-Jan van de Weg)
- Validate correctness of maximum attachment size application setting - Validate correctness of maximum attachment size application setting
- Replaces "Create merge request" link with one to the "Merge Request" when one exists
- Fix CI builds badge, add a new link to builds badge, deprecate the old one
- Fix broken link to project in build notification emails
v 8.4.4 v 8.4.4
- Update omniauth-saml gem to 1.4.2 - Update omniauth-saml gem to 1.4.2
- Prevent long-running backup tasks from timing out the database connection - Prevent long-running backup tasks from timing out the database connection
- Add a Project setting to allow guests to view build logs (defaults to true) - Add a Project setting to allow guests to view build logs (defaults to true)
- Sort project milestones by due date including issue editor (Oliver Rogers / Orih)
v 8.4.3 v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger - Increase lfs_objects size column to 8-byte integer to allow files larger
...@@ -393,6 +404,7 @@ v 8.1.0 ...@@ -393,6 +404,7 @@ v 8.1.0
- Improved performance of the trending projects page - Improved performance of the trending projects page
- Remove CI migration task - Remove CI migration task
- Improved performance of finding projects by their namespace - Improved performance of finding projects by their namespace
- Add assignee data to Issuables' hook_data (Bram Daams)
- Fix bug where transferring a project would result in stale commit links (Stan Hu) - Fix bug where transferring a project would result in stale commit links (Stan Hu)
- Fix build trace updating - Fix build trace updating
- Include full path of source and target branch names in New Merge Request page (Stan Hu) - Include full path of source and target branch names in New Merge Request page (Stan Hu)
......
...@@ -364,6 +364,7 @@ corresponding merge request should be updated to have the following: ...@@ -364,6 +364,7 @@ corresponding merge request should be updated to have the following:
This makes it easier for release managers to keep track of what still has to be This makes it easier for release managers to keep track of what still has to be
merged and where changes have to be merged into. merged and where changes have to be merged into.
Like all merge requests the target should be master so all bugfixes are in master.
## Definition of done ## Definition of done
......
...@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres ...@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres
gem 'devise', '~> 3.5.4' gem 'devise', '~> 3.5.4'
gem 'devise-async', '~> 0.9.0' gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0' gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2' gem 'omniauth', '~> 1.3.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
...@@ -112,7 +112,7 @@ gem 'rouge', '~> 1.10.1' ...@@ -112,7 +112,7 @@ gem 'rouge', '~> 1.10.1'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
gem 'nokogiri', '1.6.7.2' gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
# Diffs # Diffs
gem 'diffy', '~> 3.0.3' gem 'diffy', '~> 3.0.3'
......
...@@ -515,9 +515,9 @@ GEM ...@@ -515,9 +515,9 @@ GEM
rack (~> 1.2) rack (~> 1.2)
octokit (3.8.0) octokit (3.8.0)
sawyer (~> 0.6.0, >= 0.5.3) sawyer (~> 0.6.0, >= 0.5.3)
omniauth (1.2.2) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (~> 1.0) rack (>= 1.0, < 3)
omniauth-azure-oauth2 (0.0.6) omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0) jwt (~> 1.0)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -989,11 +989,11 @@ DEPENDENCIES ...@@ -989,11 +989,11 @@ DEPENDENCIES
nested_form (~> 0.3.2) nested_form (~> 0.3.2)
net-ldap net-ldap
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
nokogiri (= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
nprogress-rails (~> 0.1.6.7) nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 3.8.0)
omniauth (~> 1.2.2) omniauth (~> 1.3.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2) omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2) omniauth-cas3 (~> 1.1.2)
......
...@@ -49,10 +49,11 @@ class @AwardsHandler ...@@ -49,10 +49,11 @@ class @AwardsHandler
counter.text(parseInt(counter.text()) - 1) counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active") emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji) @removeMeFromAuthorList(emoji)
else if emoji =="thumbsup" || emoji == "thumbsdown" else if emoji == "thumbsup" || emoji == "thumbsdown"
emojiIcon.tooltip("destroy") emojiIcon.tooltip("destroy")
counter.text(0) counter.text(0)
emojiIcon.removeClass("active") emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji)
else else
emojiIcon.tooltip("destroy") emojiIcon.tooltip("destroy")
emojiIcon.remove() emojiIcon.remove()
......
...@@ -12,9 +12,13 @@ module Ci ...@@ -12,9 +12,13 @@ module Ci
# Project status badge # Project status badge
# Image with build status for sha or ref # Image with build status for sha or ref
#
# This action in DEPRECATED, this is here only for backwards compatibility
# with projects migrated from GitLab CI.
#
def badge def badge
return render_404 unless @project
image = Ci::ImageForBuildService.new.execute(@project, params) image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end end
......
class Projects::BadgesController < Projects::ApplicationController
def build
respond_to do |format|
format.html { render_404 }
format.svg do
image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
end
end
end
end
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry] before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
before_action :authorize_update_build!, except: [:index, :show, :status] before_action :authorize_update_build!, except: [:index, :show, :status]
layout 'project'
layout "project"
def index def index
@scope = params[:scope] @scope = params[:scope]
...@@ -24,7 +22,6 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -24,7 +22,6 @@ class Projects::BuildsController < Projects::ApplicationController
def cancel_all def cancel_all
@project.builds.running_or_pending.each(&:cancel) @project.builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project) redirect_to namespace_project_builds_path(project.namespace, project)
end end
...@@ -47,20 +44,18 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -47,20 +44,18 @@ class Projects::BuildsController < Projects::ApplicationController
end end
build = Ci::Build.retry(@build) build = Ci::Build.retry(@build)
redirect_to build_path(build) redirect_to build_path(build)
end end
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
def cancel def cancel
@build.cancel @build.cancel
redirect_to build_path(@build) redirect_to build_path(@build)
end end
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
private private
def build def build
......
...@@ -21,6 +21,9 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -21,6 +21,9 @@ class Projects::CommitsController < Projects::ApplicationController
@note_counts = project.notes.where(commit_id: @commits.map(&:id)). @note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
@merge_request = @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
respond_to do |format| respond_to do |format|
format.html format.html
format.json { pager_json("projects/commits/_commits", @commits.size) } format.json { pager_json("projects/commits/_commits", @commits.size) }
......
...@@ -4,24 +4,23 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -4,24 +4,23 @@ class Projects::CompareController < Projects::ApplicationController
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :assign_ref_vars, only: [:index, :show]
before_action :merge_request, only: [:index, :show]
def index def index
@ref = Addressable::URI.unescape(params[:to])
end end
def show def show
base_ref = Addressable::URI.unescape(params[:from])
@ref = head_ref = Addressable::URI.unescape(params[:to])
diff_options = { ignore_whitespace_change: true } if params[:w] == '1' diff_options = { ignore_whitespace_change: true } if params[:w] == '1'
compare_result = CompareService.new. compare_result = CompareService.new.
execute(@project, head_ref, @project, base_ref, diff_options) execute(@project, @head_ref, @project, @base_ref, diff_options)
if compare_result if compare_result
@commits = Commit.decorate(compare_result.commits, @project) @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs @diffs = compare_result.diffs
@commit = @project.commit(head_ref) @commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(base_ref, head_ref) @base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diff_refs = [@base_commit, @commit] @diff_refs = [@base_commit, @commit]
@line_notes = [] @line_notes = []
end end
...@@ -31,4 +30,16 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -31,4 +30,16 @@ class Projects::CompareController < Projects::ApplicationController
redirect_to namespace_project_compare_path(@project.namespace, @project, redirect_to namespace_project_compare_path(@project.namespace, @project,
params[:from], params[:to]) params[:from], params[:to])
end end
private
def assign_ref_vars
@base_ref = Addressable::URI.unescape(params[:from])
@ref = @head_ref = Addressable::URI.unescape(params[:to])
end
def merge_request
@merge_request ||= @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref)
end
end end
...@@ -11,11 +11,12 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -11,11 +11,12 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@milestones = case params[:state] @milestones =
when 'all'; @project.milestones.order("state, due_date DESC") case params[:state]
when 'closed'; @project.milestones.closed.order("due_date DESC") when 'all' then @project.milestones.reorder(due_date: :desc, title: :asc)
else @project.milestones.active.order("due_date ASC") when 'closed' then @project.milestones.closed.reorder(due_date: :desc, title: :asc)
end else @project.milestones.active.reorder(due_date: :asc, title: :asc)
end
@milestones = @milestones.includes(:project) @milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page]).per(PER_PAGE) @milestones = @milestones.page(params[:page]).per(PER_PAGE)
......
...@@ -215,8 +215,7 @@ module ApplicationHelper ...@@ -215,8 +215,7 @@ module ApplicationHelper
file_content file_content
end end
else else
GitHub::Markup.render(file_name, file_content). other_markup(file_name, file_content)
force_encoding(file_content.encoding).html_safe
end end
rescue RuntimeError rescue RuntimeError
simple_format(file_content) simple_format(file_content)
......
...@@ -78,6 +78,21 @@ module GitlabMarkdownHelper ...@@ -78,6 +78,21 @@ module GitlabMarkdownHelper
) )
end end
def other_markup(file_name, text)
Gitlab::OtherMarkup.render(
file_name,
text,
project: @project,
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
project_wiki: @project_wiki,
requested_path: @path,
ref: @ref,
commit: @commit
)
end
# Return the first line of +text+, up to +max_chars+, after parsing the line # Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the # as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then # +max_chars+ limit. If the length limit falls within a tag's contents, then
......
...@@ -44,14 +44,14 @@ module IssuesHelper ...@@ -44,14 +44,14 @@ module IssuesHelper
end end
def bulk_update_milestone_options def bulk_update_milestone_options
milestones = project_active_milestones.to_a milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None) milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id]) options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end end
def milestone_options(object) def milestone_options(object)
milestones = object.project.milestones.active.to_a milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None) milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id) options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
...@@ -69,7 +69,7 @@ module IssuesHelper ...@@ -69,7 +69,7 @@ module IssuesHelper
end end
end end
def issue_button_visibility(issue, closed) def issue_button_visibility(issue, closed)
return 'hidden' if issue.closed? == closed return 'hidden' if issue.closed? == closed
end end
......
...@@ -98,10 +98,6 @@ module ProjectsHelper ...@@ -98,10 +98,6 @@ module ProjectsHelper
project_nav_tabs.include? name project_nav_tabs.include? name
end end
def project_active_milestones
@project.milestones.active.order("due_date, title ASC")
end
def project_for_deploy_key(deploy_key) def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project) if deploy_key.projects.include?(@project)
@project @project
......
...@@ -3,26 +3,27 @@ module Emails ...@@ -3,26 +3,27 @@ module Emails
def build_fail_email(build_id, to) def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers add_project_headers
add_build_headers add_build_headers('failed')
headers['X-GitLab-Build-Status'] = "failed"
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end end
def build_success_email(build_id, to) def build_success_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers add_project_headers
add_build_headers add_build_headers('success')
headers['X-GitLab-Build-Status'] = "success"
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end end
private private
def add_build_headers
def add_build_headers(status)
headers['X-GitLab-Build-Id'] = @build.id headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref headers['X-GitLab-Build-Ref'] = @build.ref
headers['X-GitLab-Build-Status'] = status.to_s
end end
end end
end end
...@@ -72,8 +72,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -72,8 +72,8 @@ class ApplicationSetting < ActiveRecord::Base
url: true url: true
validates :admin_notification_email, validates :admin_notification_email,
allow_blank: true, email: true,
email: true allow_blank: true
validates :two_factor_grace_period, validates :two_factor_grace_period,
numericality: { greater_than_or_equal_to: 0 } numericality: { greater_than_or_equal_to: 0 }
......
...@@ -126,7 +126,7 @@ module Issuable ...@@ -126,7 +126,7 @@ module Issuable
end end
def to_hook_data(user) def to_hook_data(user)
{ hook_data = {
object_kind: self.class.name.underscore, object_kind: self.class.name.underscore,
user: user.hook_attrs, user: user.hook_attrs,
repository: { repository: {
...@@ -137,6 +137,9 @@ module Issuable ...@@ -137,6 +137,9 @@ module Issuable
}, },
object_attributes: hook_attrs object_attributes: hook_attrs
} }
hook_data.merge!(assignee: assignee.hook_attrs) if assignee
hook_data
end end
def label_names def label_names
......
...@@ -15,7 +15,7 @@ class Email < ActiveRecord::Base ...@@ -15,7 +15,7 @@ class Email < ActiveRecord::Base
belongs_to :user belongs_to :user
validates :user_id, presence: true validates :user_id, presence: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? } validate :unique_email, if: ->(email) { email.email_changed? }
before_validation :cleanup_email before_validation :cleanup_email
......
...@@ -40,7 +40,6 @@ class Member < ActiveRecord::Base ...@@ -40,7 +40,6 @@ class Member < ActiveRecord::Base
if: :invite? if: :invite?
}, },
email: { email: {
strict_mode: true,
allow_nil: true allow_nil: true
}, },
uniqueness: { uniqueness: {
......
...@@ -140,6 +140,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -140,6 +140,7 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) }
scope :opened, -> { with_state(:opened) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
...@@ -244,7 +245,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -244,7 +245,7 @@ class MergeRequest < ActiveRecord::Base
return unless unchecked? return unless unchecked?
can_be_merged = can_be_merged =
project.repository.can_be_merged?(source_sha, target_branch) !broken? && project.repository.can_be_merged?(source_sha, target_branch)
if can_be_merged if can_be_merged
mark_as_mergeable mark_as_mergeable
......
...@@ -88,7 +88,8 @@ class Repository ...@@ -88,7 +88,8 @@ class Repository
offset: offset, offset: offset,
# --follow doesn't play well with --skip. See: # --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false follow: false,
skip_merges: skip_merges
} }
commits = Gitlab::Git::Commit.where(options) commits = Gitlab::Git::Commit.where(options)
......
...@@ -148,11 +148,8 @@ class User < ActiveRecord::Base ...@@ -148,11 +148,8 @@ class User < ActiveRecord::Base
# Validations # Validations
# #
validates :name, presence: true validates :name, presence: true
# Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to validates :notification_email, presence: true, email: true
# duplicate that here as the validation framework will have duplicate errors in the event of a failure. validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
validates :email, presence: true, email: { strict_mode: true }
validates :notification_email, presence: true, email: { strict_mode: true }
validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username, validates :username,
......
module Ci module Ci
class ImageForBuildService class ImageForBuildService
def execute(project, params) def execute(project, opts)
sha = params[:sha] sha = opts[:sha] || ref_sha(project, opts[:ref])
sha ||=
if params[:ref]
project.commit(params[:ref]).try(:sha)
end
commit = project.ci_commits.ordered.find_by(sha: sha) commit = project.ci_commits.ordered.find_by(sha: sha)
image_name = image_for_commit(commit) image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name) image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name)
OpenStruct.new(
path: image_path,
name: image_name
)
end end
private private
def ref_sha(project, ref)
project.commit(ref).try(:sha) if ref
end
def image_for_commit(commit) def image_for_commit(commit)
return 'build-unknown.svg' unless commit return 'build-unknown.svg' unless commit
'build-' + commit.status + ".svg" 'build-' + commit.status + ".svg"
end end
end end
......
...@@ -29,11 +29,7 @@ class CreateBranchService < BaseService ...@@ -29,11 +29,7 @@ class CreateBranchService < BaseService
end end
if new_branch if new_branch
push_data = build_push_data(project, current_user, new_branch) # GitPushService handles execution of services and hooks for branch pushes
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
success(new_branch) success(new_branch)
else else
error('Invalid reference name') error('Invalid reference name')
......
...@@ -25,11 +25,7 @@ class DeleteBranchService < BaseService ...@@ -25,11 +25,7 @@ class DeleteBranchService < BaseService
end end
if repository.rm_branch(current_user, branch_name) if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch) # GitPushService handles execution of services and hooks for branch pushes
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
success('Branch was removed') success('Branch was removed')
else else
error('Failed to remove branch') error('Failed to remove branch')
......
# EmailValidator
#
# Based on https://github.com/balexand/email_validator
#
# Extended to use only strict mode with following allowed characters:
# ' - apostrophe
#
# See http://www.remote.org/jochen/mail/info/chars.html
#
class EmailValidator < ActiveModel::EachValidator class EmailValidator < ActiveModel::EachValidator
PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
unless value =~ PATTERN record.errors.add(attribute, :invalid) unless value =~ Devise.email_regexp
record.errors.add(attribute, options[:message] || :invalid)
end
end end
end end
- content_for :header do - content_for :header do
%h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
GitLab (build failed) GitLab (build failed)
%h3 %h3
Project: Project:
= link_to ci_project_url(@project) do = link_to namespace_project_url(@project.namespace, @project) do
= @project.name = @project.name
%p %p
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%h3 %h3
Project: Project:
= link_to ci_project_url(@project) do = link_to namespace_project_url(@project.namespace, @project) do
= @project.name = @project.name
%p %p
......
...@@ -11,7 +11,10 @@ ...@@ -11,7 +11,10 @@
= render 'shared/ref_switcher', destination: 'commits' = render 'shared/ref_switcher', destination: 'commits'
.block-controls.hidden-xs.hidden-sm .block-controls.hidden-xs.hidden-sm
- if create_mr_button?(@repository.root_ref, @ref) - if @merge_request.present?
.control
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control .control
= link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
= icon('plus') = icon('plus')
......
...@@ -13,12 +13,13 @@ ...@@ -13,12 +13,13 @@
= text_field_tag :to, params[:to], class: "form-control", required: true = text_field_tag :to, params[:to], class: "form-control", required: true
&nbsp; &nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if create_mr_button? - if @merge_request.present?
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
= link_to create_mr_path, class: 'prepend-left-10 btn' do = link_to create_mr_path, class: 'prepend-left-10 btn' do
= icon("plus") = icon("plus")
Create Merge Request Create Merge Request
:javascript :javascript
var availableTags = #{@project.repository.ref_names.to_json}; var availableTags = #{@project.repository.ref_names.to_json};
......
...@@ -29,14 +29,15 @@ ...@@ -29,14 +29,15 @@
= link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
= sort_title_oldest_updated = sort_title_oldest_updated
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user && can?(current_user, :fork_project, @project)
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= icon('code-fork fw') = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
Fork = icon('code-fork fw')
- else Fork
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do - else
= icon('code-fork fw') = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
Fork = icon('code-fork fw')
Fork
.projects-list-holder .projects-list-holder
......
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
Edited Edited
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago') = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests .merge-requests
= render 'merge_requests' = render 'merge_requests'
.content-block .content-block
= render 'votes/votes_block', votable: @issue = render 'votes/votes_block', votable: @issue
...@@ -57,4 +57,4 @@ ...@@ -57,4 +57,4 @@
.issuable-discussion .issuable-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue = render 'shared/issuable/sidebar', issuable: @issue
\ No newline at end of file
...@@ -659,7 +659,7 @@ Rails.application.routes.draw do ...@@ -659,7 +659,7 @@ Rails.application.routes.draw do
resource :variables, only: [:show, :update] resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :destroy] resources :triggers, only: [:index, :create, :destroy]
resources :builds, only: [:index, :show] do resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do collection do
post :cancel_all post :cancel_all
end end
...@@ -751,6 +751,12 @@ Rails.application.routes.draw do ...@@ -751,6 +751,12 @@ Rails.application.routes.draw do
resources :approvers, only: :destroy resources :approvers, only: :destroy
resources :runner_projects, only: [:create, :destroy] resources :runner_projects, only: [:create, :destroy]
resources :badges, only: [], path: 'badges/*ref',
constraints: { ref: Gitlab::Regex.git_reference_regex } do
collection do
get :build, constraints: { format: /svg/ }
end
end
end end
get "/audit_events" => "audit_events#project_log" get "/audit_events" => "audit_events#project_log"
......
class Gitlab::Seeder::Builds class Gitlab::Seeder::Builds
BUILD_STATUSES = %w(running pending success failed canceled)
def initialize(project) def initialize(project)
@project = project @project = project
end end
def seed! def seed!
ci_commits.each do |ci_commit| ci_commits.each do |ci_commit|
build = Ci::Build.new(build_attributes_for(ci_commit))
artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file
end
artifacts_cache_file(artifacts_metadata_path) do |file|
build.artifacts_metadata = file
end
begin begin
build.save! build_create!(ci_commit, name: 'test build 1')
build_create!(ci_commit, status: 'success', name: 'test build 2')
print '.' print '.'
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
print 'F' print 'F'
...@@ -36,6 +25,28 @@ class Gitlab::Seeder::Builds ...@@ -36,6 +25,28 @@ class Gitlab::Seeder::Builds
[] []
end end
def build_create!(ci_commit, opts = {})
attributes = build_attributes_for(ci_commit).merge(opts)
build = Ci::Build.new(attributes)
if %w(success failed).include?(build.status)
artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file
end
artifacts_cache_file(artifacts_metadata_path) do |file|
build.artifacts_metadata = file
end
end
build.save!
if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end
end
def build_attributes_for(ci_commit) def build_attributes_for(ci_commit)
{ name: 'test build', commands: "$ build command", { name: 'test build', commands: "$ build command",
stage: 'test', stage_idx: 1, ref: 'master', stage: 'test', stage_idx: 1, ref: 'master',
...@@ -49,7 +60,7 @@ class Gitlab::Seeder::Builds ...@@ -49,7 +60,7 @@ class Gitlab::Seeder::Builds
end end
def build_status def build_status
BUILD_STATUSES.sample Ci::Build::AVAILABLE_STATUSES.sample
end end
def artifacts_archive_path def artifacts_archive_path
......
...@@ -8,20 +8,16 @@ Get a list of builds in a project. ...@@ -8,20 +8,16 @@ Get a list of builds in a project.
GET /projects/:id/builds GET /projects/:id/builds
``` ```
### Parameters | Attribute | Type | Required | Description |
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------| |-----------|---------|----------|---------------------|
| id | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| scope | string|array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided | | `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
### Example of request
``` ```
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds" curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
``` ```
### Example of response Example of response
```json ```json
[ [
...@@ -112,21 +108,17 @@ Get a list of builds for specific commit in a project. ...@@ -112,21 +108,17 @@ Get a list of builds for specific commit in a project.
GET /projects/:id/repository/commits/:sha/builds GET /projects/:id/repository/commits/:sha/builds
``` ```
### Parameters | Attribute | Type | Required | Description |
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------| |-----------|---------|----------|---------------------|
| id | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| sha | string | yes | The SHA id of a commit | | `sha` | string | yes | The SHA id of a commit |
| scope | string|array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided | | `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
### Example of request
``` ```
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds" curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
``` ```
### Example of response Example of response
```json ```json
[ [
...@@ -203,20 +195,16 @@ Get a single build of a project ...@@ -203,20 +195,16 @@ Get a single build of a project
GET /projects/:id/builds/:build_id GET /projects/:id/builds/:build_id
``` ```
### Parameters | Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| Attribute | Type | required | Description | | `id` | integer | yes | The ID of a project |
|-----------|---------|----------|---------------------| | `build_id` | integer | yes | The ID of a build |
| id | integer | yes | The ID of a project |
| build\_id | integer | yes | The ID of a build |
### Example of request
``` ```
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8" curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
``` ```
### Example of response Example of response
```json ```json
{ {
...@@ -267,20 +255,16 @@ Cancel a single build of a project ...@@ -267,20 +255,16 @@ Cancel a single build of a project
POST /projects/:id/builds/:build_id/cancel POST /projects/:id/builds/:build_id/cancel
``` ```
### Parameters | Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| Attribute | Type | required | Description | | `id` | integer | yes | The ID of a project |
|-----------|---------|----------|---------------------| | `build_id` | integer | yes | The ID of a build |
| id | integer | yes | The ID of a project |
| build\_id | integer | yes | The ID of a build |
### Example of request
``` ```
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel" curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
``` ```
### Example of response Example of response
```json ```json
{ {
...@@ -317,20 +301,16 @@ Retry a single build of a project ...@@ -317,20 +301,16 @@ Retry a single build of a project
POST /projects/:id/builds/:build_id/retry POST /projects/:id/builds/:build_id/retry
``` ```
### Parameters | Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| Attribute | Type | required | Description | | `id` | integer | yes | The ID of a project |
|-----------|---------|----------|---------------------| | `build_id` | integer | yes | The ID of a build |
| id | integer | yes | The ID of a project |
| build\_id | integer | yes | The ID of a build |
### Example of request
``` ```
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry" curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
``` ```
### Example of response Example of response
```json ```json
{ {
......
...@@ -259,6 +259,7 @@ Parameters: ...@@ -259,6 +259,7 @@ Parameters:
- `description` (optional) - Description of MR - `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id) - `target_project_id` (optional) - The target project (numeric id)
- `labels` (optional) - Labels for MR as a comma-separated list - `labels` (optional) - Labels for MR as a comma-separated list
- `milestone_id` (optional) - Milestone ID
```json ```json
{ {
...@@ -328,6 +329,7 @@ Parameters: ...@@ -328,6 +329,7 @@ Parameters:
- `description` - Description of MR - `description` - Description of MR
- `state_event` - New state (close|reopen|merge) - `state_event` - New state (close|reopen|merge)
- `labels` (optional) - Labels for MR as a comma-separated list - `labels` (optional) - Labels for MR as a comma-separated list
- `milestone_id` (optional) - Milestone ID
```json ```json
{ {
...@@ -516,3 +518,65 @@ Parameters: ...@@ -516,3 +518,65 @@ Parameters:
## Comments on merge requets ## Comments on merge requets
Comments are done via the [notes](notes.md) resource. Comments are done via the [notes](notes.md) resource.
## List issues that will close on merge
Get all the issues that would be closed by merging the provided merge request.
```
GET /projects/:id/merge_requests/:merge_request_id/closes_issues
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues
```
Example response:
```json
[
{
"state" : "opened",
"description" : "Ratione dolores corrupti mollitia soluta quia.",
"author" : {
"state" : "active",
"id" : 18,
"web_url" : "https://gitlab.example.com/u/eileen.lowe",
"name" : "Alexandra Bashirian",
"avatar_url" : null,
"username" : "eileen.lowe"
},
"milestone" : {
"project_id" : 1,
"description" : "Ducimus nam enim ex consequatur cumque ratione.",
"state" : "closed",
"due_date" : null,
"iid" : 2,
"created_at" : "2016-01-04T15:31:39.996Z",
"title" : "v4.0",
"id" : 17,
"updated_at" : "2016-01-04T15:31:39.996Z"
},
"project_id" : 1,
"assignee" : {
"state" : "active",
"id" : 1,
"name" : "Administrator",
"web_url" : "https://gitlab.example.com/u/root",
"avatar_url" : null,
"username" : "root"
},
"updated_at" : "2016-01-04T15:31:51.081Z",
"id" : 76,
"title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
"labels" : []
},
]
```
...@@ -270,7 +270,7 @@ This will forcefully (`-f`) remove the `build` container, the two service ...@@ -270,7 +270,7 @@ This will forcefully (`-f`) remove the `build` container, the two service
containers as well as all volumes (`-v`) that were created with the container containers as well as all volumes (`-v`) that were created with the container
creation. creation.
[Docker Fundamentals]: https://docs.docker.com/engine/introduction/understanding-docker/ [Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/
[hub]: https://hub.docker.com/ [hub]: https://hub.docker.com/
[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/ [linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/ [tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/
......
...@@ -184,6 +184,14 @@ you expected. ...@@ -184,6 +184,14 @@ you expected.
You are also able to view the status of any commit in the various pages in You are also able to view the status of any commit in the various pages in
GitLab, such as **Commits** and **Merge Requests**. GitLab, such as **Commits** and **Merge Requests**.
## Builds badge
You can access a builds badge image using following link:
```
http://example.gitlab.com/namespace/project/badges/branch/build.svg
```
## Next steps ## Next steps
Awesome! You started using CI in GitLab! Awesome! You started using CI in GitLab!
......
...@@ -8,6 +8,8 @@ In addition, having to take a server offline for a an upgrade small or big is ...@@ -8,6 +8,8 @@ In addition, having to take a server offline for a an upgrade small or big is
a big burden for most organizations. For this reason it is important that your a big burden for most organizations. For this reason it is important that your
migrations are written carefully, can be applied online and adhere to the style guide below. migrations are written carefully, can be applied online and adhere to the style guide below.
It's advised to have offline migrations only in major GitLab releases.
When writing your migrations, also consider that databases might have stale data When writing your migrations, also consider that databases might have stale data
or inconsistencies and guard for that. Try to make as little assumptions as possible or inconsistencies and guard for that. Try to make as little assumptions as possible
about the state of the database. about the state of the database.
...@@ -33,6 +35,8 @@ It is always preferable to have a migration run online. If you expect the migrat ...@@ -33,6 +35,8 @@ It is always preferable to have a migration run online. If you expect the migrat
to take particularly long (for instance, if it loops through all notes), to take particularly long (for instance, if it loops through all notes),
this is valuable information to add. this is valuable information to add.
If you don't provide the information it means that a migration is safe to run online.
### Reversibility ### Reversibility
Your migration should be reversible. This is very important, as it should Your migration should be reversible. This is very important, as it should
...@@ -85,4 +89,4 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i ...@@ -85,4 +89,4 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i
execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})") execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})")
execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})") execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
end end
``` ```
\ No newline at end of file
...@@ -361,7 +361,7 @@ GitLab Shell is an SSH access and repository management software developed speci ...@@ -361,7 +361,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse cd gitlab-workhorse
sudo -u git -H git checkout 0.6.3 sudo -u git -H git checkout 0.6.4
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
...@@ -424,24 +424,24 @@ will point the link to `wikis/style` when the link is inside of a wiki markdown ...@@ -424,24 +424,24 @@ will point the link to `wikis/style` when the link is inside of a wiki markdown
Here's our logo (hover to see the title text): Here's our logo (hover to see the title text):
Inline-style: Inline-style:
![alt text](assets/logo.svg) ![alt text](img/logo.png)
Reference-style: Reference-style:
![alt text1][logo] ![alt text1][logo]
[logo]: assets/logo.svg [logo]: img/logo.png
Here's our logo: Here's our logo:
Inline-style: Inline-style:
![alt text](/assets/logo.svg) ![alt text](img/logo.png)
Reference-style: Reference-style:
![alt text][logo] ![alt text][logo]
[logo]: /assets/logo.svg [logo]: img/logo.png
## Blockquotes ## Blockquotes
......
...@@ -18,8 +18,6 @@ for two-factor authentication. If you restore a GitLab backup without ...@@ -18,8 +18,6 @@ for two-factor authentication. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor restoring the database encryption key, users who have two-factor
authentication enabled will lose access to your GitLab server. authentication enabled will lose access to your GitLab server.
If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
``` ```
# use this command if you've installed GitLab with the Omnibus package # use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create sudo gitlab-rake gitlab:backup:create
......
...@@ -49,7 +49,7 @@ GitLab 8.1. ...@@ -49,7 +49,7 @@ GitLab 8.1.
```bash ```bash
cd /home/git/gitlab-workhorse cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout 0.6.3 sudo -u git -H git checkout 0.6.4
sudo -u git -H make sudo -u git -H make
``` ```
...@@ -72,12 +72,22 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ...@@ -72,12 +72,22 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
``` ```
### 7. Start application ### 7. Update configuration files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-4-stable:config/gitlab.yml.example origin/8-5-stable:config/gitlab.yml.example
```
### 8. Start application
sudo service gitlab start sudo service gitlab start
sudo service nginx restart sudo service nginx restart
### 8. Check application status ### 9. Check application status
Check if GitLab and its environment are configured correctly: Check if GitLab and its environment are configured correctly:
......
...@@ -80,7 +80,6 @@ X-Gitlab-Event: Push Hook ...@@ -80,7 +80,6 @@ X-Gitlab-Event: Push Hook
} }
], ],
"total_commits_count": 4 "total_commits_count": 4
} }
``` ```
...@@ -162,6 +161,11 @@ X-Gitlab-Event: Issue Hook ...@@ -162,6 +161,11 @@ X-Gitlab-Event: Issue Hook
"iid": 23, "iid": 23,
"url": "http://example.com/diaspora/issues/23", "url": "http://example.com/diaspora/issues/23",
"action": "open" "action": "open"
},
"assignee": {
"name": "User1",
"username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
} }
} }
``` ```
...@@ -326,7 +330,12 @@ X-Gitlab-Event: Note Hook ...@@ -326,7 +330,12 @@ X-Gitlab-Event: Note Hook
"email": "john@example.com" "email": "john@example.com"
} }
}, },
"work_in_progress": false "work_in_progress": false,
"assignee": {
"name": "User1",
"username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}
} }
} }
``` ```
...@@ -401,7 +410,7 @@ X-Gitlab-Event: Note Hook ...@@ -401,7 +410,7 @@ X-Gitlab-Event: Note Hook
**Request body:** **Request body:**
``` ```json
{ {
"object_kind": "note", "object_kind": "note",
"user": { "user": {
...@@ -514,7 +523,12 @@ X-Gitlab-Event: Merge Request Hook ...@@ -514,7 +523,12 @@ X-Gitlab-Event: Merge Request Hook
}, },
"work_in_progress": false, "work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1", "url": "http://example.com/diaspora/merge_requests/1",
"action": "open" "action": "open",
"assignee": {
"name": "User1",
"username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}
} }
} }
``` ```
......
...@@ -187,12 +187,15 @@ If you have an issue that spans across multiple repositories, the best thing is ...@@ -187,12 +187,15 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png) ![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them. With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
In GitLab EE and .com you can also [rebase before merge](http://doc.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical. This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server. However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them. Somebody can have referred to the commits or cherry-picked them.
When you rebase you change the identifier (SHA-1) of the commit and this is confusing. When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
If you do that the same change will be known under multiple identifiers and this can cause much confusion. If you do that the same change will be known under multiple identifiers and this can cause much confusion.
If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit. If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
Another reasons not to rebase is that you lose authorship information, maybe someone created a merge request, another person pushed a commit on there to improve it and a third one merged it.
In this case rebasing all the commits into one prevent the other authors from being properly attributed and sharing part of the [git blame](https://git-scm.com/docs/git-blame).
People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on. People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
This will lead to many commits per change which makes the history harder to understand. This will lead to many commits per change which makes the history harder to understand.
...@@ -221,13 +224,11 @@ You can reuse recorded resolutions (rerere) sometimes, but without rebasing you ...@@ -221,13 +224,11 @@ You can reuse recorded resolutions (rerere) sometimes, but without rebasing you
There has to be a better way to avoid many merge commits. There has to be a better way to avoid many merge commits.
The way to prevent creating many merge commits is to not frequently merge master into the feature branch. The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
We'll discuss the three reasons to merge in master: leveraging code, solving merge conflicts and long running branches. We'll discuss the three reasons to merge in master: leveraging code, merge conflicts, and long running branches.
If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit. If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this. If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
You should aim to prevent merge conflicts where they are likely to occur. You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order.
One example is the CHANGELOG file where each significant change in the codebase is documented under a version header. For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG merge=union` so that there are fewer merge conflicts in it.
Instead of everyone adding their change at the bottom of the list for the current version it is better to randomly insert it in the current list for that version.
This it is likely that multiple feature branches that add to the CHANGELOG can be merged before a conflict occurs.
The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project. The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
......
Feature: Project Badges Build
Background:
Given I sign in as a user
And I own a project
And project has CI enabled
And project has a recent build
Scenario: I want to see a badge for successfully built project
Given recent build is successful
When I display builds badge for a master branch
Then I should see a build success badge
Scenario: I want to see a badge for project with failed builds
Given recent build failed
When I display builds badge for a master branch
Then I should see a build failed badge
Scenario: I want to see a badge for project with running builds
Given recent build is successful
And project has another build that is running
When I display builds badge for a master branch
Then I should see a build running badge
...@@ -7,6 +7,26 @@ Feature: Project Commits ...@@ -7,6 +7,26 @@ Feature: Project Commits
Scenario: I browse commits list for master branch Scenario: I browse commits list for master branch
Then I see project commits Then I see project commits
And I should not see button to create a new merge request
Then I click the "Compare" tab
And I should not see button to create a new merge request
Scenario: I browse commits list for feature branch without a merge request
Given I visit commits list page for feature branch
Then I see feature branch commits
And I see button to create a new merge request
Then I click the "Compare" tab
And I see button to create a new merge request
Scenario: I browse commits list for feature branch with an open merge request
Given project have an open merge request
And I visit commits list page for feature branch
Then I see feature branch commits
And I should not see button to create a new merge request
And I should see button to the merge request
Then I click the "Compare" tab
And I should not see button to create a new merge request
And I should see button to the merge request
Scenario: I browse atom feed of commits list for master branch Scenario: I browse atom feed of commits list for master branch
Given I click atom feed link Given I click atom feed link
...@@ -30,6 +50,22 @@ Feature: Project Commits ...@@ -30,6 +50,22 @@ Feature: Project Commits
And I click side-by-side diff button And I click side-by-side diff button
Then I see inline diff button Then I see inline diff button
@javascript
Scenario: I compare branches without a merge request
Given I visit compare refs page
And I fill compare fields with branches
Then I see compared branches
And I see button to create a new merge request
@javascript
Scenario: I compare branches with an open merge request
Given project have an open merge request
And I visit compare refs page
And I fill compare fields with branches
Then I see compared branches
And I should not see button to create a new merge request
And I should see button to the merge request
@javascript @javascript
Scenario: I compare refs Scenario: I compare refs
Given I visit compare refs page Given I visit compare refs page
......
...@@ -7,7 +7,16 @@ Feature: Award Emoji ...@@ -7,7 +7,16 @@ Feature: Award Emoji
And I visit "Bugfix" issue page And I visit "Bugfix" issue page
@javascript @javascript
Scenario: I add and remove award in the issue Scenario: I repeatedly add and remove thumbsup award in the issue
Given I click the thumbsup award Emoji
Then I have award added
Given I click the thumbsup award Emoji
Then I have no awards added
Given I click the thumbsup award Emoji
Then I have award added
@javascript
Scenario: I add and remove custom award in the issue
Given I click to emoji-picker Given I click to emoji-picker
Then The search field is focused Then The search field is focused
And I click to emoji in the picker And I click to emoji in the picker
......
class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedBuilds
include RepoHelpers
step 'I display builds badge for a master branch' do
visit build_namespace_project_badges_path(@project.namespace, @project, ref: :master, format: :svg)
end
step 'I should see a build success badge' do
expect_badge('success')
end
step 'I should see a build failed badge' do
expect_badge('failed')
end
step 'I should see a build running badge' do
expect_badge('running')
end
def expect_badge(status)
svg = Nokogiri::XML.parse(page.body)
expect(page.response_headers).to include('Content-Type' => 'image/svg+xml')
expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
end
end
...@@ -33,6 +33,13 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -33,6 +33,13 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "Showing #{sample_commit.files_changed_count} changed files" expect(page).to have_content "Showing #{sample_commit.files_changed_count} changed files"
end end
step 'I fill compare fields with branches' do
fill_in 'from', with: 'feature'
fill_in 'to', with: 'master'
click_button 'Compare'
end
step 'I fill compare fields with refs' do step 'I fill compare fields with refs' do
fill_in "from", with: sample_commit.parent_id fill_in "from", with: sample_commit.parent_id
fill_in "to", with: sample_commit.id fill_in "to", with: sample_commit.id
...@@ -56,6 +63,56 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -56,6 +63,56 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "Showing 2 changed files" expect(page).to have_content "Showing 2 changed files"
end end
step 'I visit commits list page for feature branch' do
visit namespace_project_commits_path(@project.namespace, @project, 'feature', { limit: 5 })
end
step 'I see feature branch commits' do
commit = @project.repository.commit('0b4bc9a')
expect(page).to have_content(@project.name)
expect(page).to have_content(commit.message[0..12])
expect(page).to have_content(commit.short_id)
end
step 'project have an open merge request' do
create(:merge_request,
title: 'Feature',
source_project: @project,
source_branch: 'feature',
target_branch: 'master',
author: @project.users.first
)
end
step 'I click the "Compare" tab' do
click_link('Compare')
end
step 'I fill compare fields with branches' do
fill_in 'from', with: 'master'
fill_in 'to', with: 'feature'
click_button 'Compare'
end
step 'I see compared branches' do
expect(page).to have_content 'Commits (1)'
expect(page).to have_content 'Showing 1 changed file with 5 additions and 0 deletions'
end
step 'I see button to create a new merge request' do
expect(page).to have_link 'Create Merge Request'
end
step 'I should not see button to create a new merge request' do
expect(page).to_not have_link 'Create Merge Request'
end
step 'I should see button to the merge request' do
merge_request = MergeRequest.find_by(title: 'Feature')
expect(page).to have_link "View Open Merge Request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request)
end
step 'I see breadcrumb links' do step 'I see breadcrumb links' do
expect(page).to have_selector('ul.breadcrumb') expect(page).to have_selector('ul.breadcrumb')
expect(page).to have_selector('ul.breadcrumb a', count: 4) expect(page).to have_selector('ul.breadcrumb a', count: 4)
......
...@@ -8,6 +8,15 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -8,6 +8,15 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
visit namespace_project_issue_path(@project.namespace, @project, @issue) visit namespace_project_issue_path(@project.namespace, @project, @issue)
end end
step 'I click the thumbsup award Emoji' do
page.within '.awards' do
thumbsup = page.find('.award .emoji-1F44D')
thumbsup.click
thumbsup.hover
sleep 0.3
end
end
step 'I click to emoji-picker' do step 'I click to emoji-picker' do
page.within '.awards-controls' do page.within '.awards-controls' do
page.find('.add-award').click page.find('.add-award').click
...@@ -40,6 +49,23 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -40,6 +49,23 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
page.within '.awards' do page.within '.awards' do
expect(page).to have_selector '.award' expect(page).to have_selector '.award'
expect(page.find('.award.active .counter')).to have_content '1' expect(page.find('.award.active .counter')).to have_content '1'
expect(page.find('.award.active')['data-original-title']).to eq('me')
end
end
step 'I have no awards added' do
page.within '.awards' do
expect(page).to have_selector '.award'
expect(page.all('.award').size).to eq(2)
# Check tooltip data
page.all('.award').each do |element|
expect(element['title']).to eq("")
end
page.all('.award .counter').each do |element|
expect(element).to have_content '0'
end
end end
end end
......
...@@ -6,8 +6,20 @@ module SharedBuilds ...@@ -6,8 +6,20 @@ module SharedBuilds
end end
step 'project has a recent build' do step 'project has a recent build' do
ci_commit = create :ci_commit, project: @project, sha: sample_commit.id @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha)
@build = create :ci_build, commit: ci_commit @build = create(:ci_build, commit: @ci_commit)
end
step 'recent build is successful' do
@build.update_column(:status, 'success')
end
step 'recent build failed' do
@build.update_column(:status, 'failed')
end
step 'project has another build that is running' do
create(:ci_build, commit: @ci_commit, name: 'second build', status: 'running')
end end
step 'I visit recent build details page' do step 'I visit recent build details page' do
......
...@@ -71,6 +71,7 @@ module API ...@@ -71,6 +71,7 @@ module API
# title (required) - Title of MR # title (required) - Title of MR
# description - Description of MR # description - Description of MR
# labels (optional) - Labels for MR as a comma-separated list # labels (optional) - Labels for MR as a comma-separated list
# milestone_id (optional) - Milestone ID
# #
# Example: # Example:
# POST /projects/:id/merge_requests # POST /projects/:id/merge_requests
...@@ -78,7 +79,7 @@ module API ...@@ -78,7 +79,7 @@ module API
post ":id/merge_requests" do post ":id/merge_requests" do
authorize! :create_merge_request, user_project authorize! :create_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title] required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description] attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description, :milestone_id]
# Validate label names in advance # Validate label names in advance
if (errors = validate_label_params(params)).any? if (errors = validate_label_params(params)).any?
...@@ -163,11 +164,12 @@ module API ...@@ -163,11 +164,12 @@ module API
# state_event - Status of MR. (close|reopen|merge) # state_event - Status of MR. (close|reopen|merge)
# description - Description of MR # description - Description of MR
# labels (optional) - Labels for a MR as a comma-separated list # labels (optional) - Labels for a MR as a comma-separated list
# milestone_id (optional) - Milestone ID
# Example: # Example:
# PUT /projects/:id/merge_requests/:merge_request_id # PUT /projects/:id/merge_requests/:merge_request_id
# #
put path do put path do
attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description] attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description, :milestone_id]
merge_request = user_project.merge_requests.find(params[:merge_request_id]) merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :update_merge_request, merge_request authorize! :update_merge_request, merge_request
...@@ -300,6 +302,19 @@ module API ...@@ -300,6 +302,19 @@ module API
render_api_error!("Failed to save note #{note.errors.messages}", 400) render_api_error!("Failed to save note #{note.errors.messages}", 400)
end end
end end
# List issues that will close on merge
#
# Parameters:
# id (required) - The ID of a project
# merge_request_id (required) - ID of MR
# Examples:
# GET /projects/:id/merge_requests/:merge_request_id/closes_issues
get "#{path}/closes_issues" do
merge_request = user_project.merge_requests.find(params[:merge_request_id])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue
end
end end
end end
end end
......
module Banzai
module Pipeline
class AsciidocPipeline < BasePipeline
def self.filters
[
Filter::RelativeLinkFilter
]
end
end
end
end
...@@ -31,9 +31,7 @@ module Gitlab ...@@ -31,9 +31,7 @@ module Gitlab
html = ::Asciidoctor.convert(input, asciidoc_opts) html = ::Asciidoctor.convert(input, asciidoc_opts)
if context[:project] html = Banzai.post_process(html, context)
html = Banzai.render(html, context.merge(pipeline: :asciidoc))
end
html.html_safe html.html_safe
end end
......
...@@ -40,6 +40,7 @@ module Gitlab ...@@ -40,6 +40,7 @@ module Gitlab
end end
def highlighted_lines def highlighted_lines
@blob.load_all_data!(repository)
@highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines @highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines
end end
......
...@@ -17,7 +17,7 @@ module Gitlab ...@@ -17,7 +17,7 @@ module Gitlab
end end
def true_value def true_value
if self.class.postgresql? if Gitlab::Database.postgresql?
"'t'" "'t'"
else else
1 1
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
end end
def false_value def false_value
if self.class.postgresql? if Gitlab::Database.postgresql?
"'f'" "'f'"
else else
0 0
...@@ -47,9 +47,5 @@ module Gitlab ...@@ -47,9 +47,5 @@ module Gitlab
row.first row.first
end end
end end
def connection
self.class.connection
end
end end
end end
module Gitlab
# Parser/renderer for markups without other special support code.
module OtherMarkup
# Public: Converts the provided markup into HTML.
#
# input - the source text in a markup format
# context - a Hash with the template context:
# :commit
# :project
# :project_wiki
# :requested_path
# :ref
#
def self.render(file_name, input, context)
html = GitHub::Markup.render(file_name, input).
force_encoding(input.encoding)
html = Banzai.post_process(html, context)
html.html_safe
end
end
end
...@@ -90,24 +90,6 @@ namespace :gitlab do ...@@ -90,24 +90,6 @@ namespace :gitlab do
end end
end end
def check_database_is_not_sqlite
print "Database is SQLite ... "
database_config_file = Rails.root.join("config", "database.yml")
unless File.read(database_config_file) =~ /adapter:\s+sqlite/
puts "no".green
else
puts "yes".red
puts "Please fix this by removing the SQLite entry from the database.yml".blue
for_more_information(
"https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL",
see_database_guide
)
fix_and_rerun
end
end
def check_gitlab_config_exists def check_gitlab_config_exists
print "GitLab config exists? ... " print "GitLab config exists? ... "
......
#!/bin/bash #!/bin/bash
if [ -f /.dockerinit ]; then if [ -f /.dockerinit ]; then
# Docker runners use `/cache` folder which is persisted every build mkdir -p vendor
if [ ! -e /cache/phantomjs_1.9.8-0jessie_amd64.deb ]; then if [ ! -e vendor/phantomjs_1.9.8-0jessie_amd64.deb ]; then
wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
mv phantomjs_1.9.8-0jessie_amd64.deb /cache mv phantomjs_1.9.8-0jessie_amd64.deb vendor/
fi fi
dpkg -i /cache/phantomjs_1.9.8-0jessie_amd64.deb dpkg -i vendor/phantomjs_1.9.8-0jessie_amd64.deb
apt-get update -qq apt-get update -qq
apt-get -o dir::cache::archives="/cache/apt" install -y -qq --force-yes \ apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip
cp config/database.yml.mysql config/database.yml cp config/database.yml.mysql config/database.yml
...@@ -20,7 +20,7 @@ if [ -f /.dockerinit ]; then ...@@ -20,7 +20,7 @@ if [ -f /.dockerinit ]; then
cp config/resque.yml.example config/resque.yml cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml sed -i 's/localhost/redis/g' config/resque.yml
export FLAGS=(--path /cache) export FLAGS=(--path vendor)
else else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml cp config/database.yml.mysql config/database.yml
......
...@@ -293,6 +293,10 @@ describe ApplicationHelper do ...@@ -293,6 +293,10 @@ describe ApplicationHelper do
describe 'render_markup' do describe 'render_markup' do
let(:content) { 'Noël' } let(:content) { 'Noël' }
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
it 'should preserve encoding' do it 'should preserve encoding' do
expect(content.encoding.name).to eq('UTF-8') expect(content.encoding.name).to eq('UTF-8')
......
...@@ -42,22 +42,6 @@ module Gitlab ...@@ -42,22 +42,6 @@ module Gitlab
end end
end end
context "with project in context" do
let(:context) { { project: create(:project) } }
it "should filter converted input via HTML pipeline and return result" do
filtered_html = '<b>ASCII</b>'
allow(Asciidoctor).to receive(:convert).and_return(html)
expect(Banzai).to receive(:render)
.with(html, context.merge(pipeline: :asciidoc))
.and_return(filtered_html)
expect( render('foo', context) ).to eql filtered_html
end
end
def render(*args) def render(*args)
described_class.render(*args) described_class.render(*args)
end end
......
require 'spec_helper' require 'spec_helper'
class MigrationTest
include Gitlab::Database
end
describe Gitlab::Database, lib: true do describe Gitlab::Database, lib: true do
# These are just simple smoke tests to check if the methods work (regardless # These are just simple smoke tests to check if the methods work (regardless
# of what they may return). # of what they may return).
...@@ -34,4 +38,32 @@ describe Gitlab::Database, lib: true do ...@@ -34,4 +38,32 @@ describe Gitlab::Database, lib: true do
end end
end end
end end
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
expect(MigrationTest.new.true_value).to eq "'t'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
expect(MigrationTest.new.true_value).to eq 1
end
end
describe '#false_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
expect(MigrationTest.new.false_value).to eq "'f'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
expect(MigrationTest.new.false_value).to eq 0
end
end
end end
require 'spec_helper'
require 'email_spec'
require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Matchers
include_context 'gitlab email notification'
describe 'build notification email' do
let(:build) { create(:ci_build) }
let(:project) { build.project }
shared_examples 'build email' do
it 'contains name of project' do
is_expected.to have_body_text build.project_name
end
it 'contains link to project' do
is_expected.to have_body_text namespace_project_path(project.namespace, project)
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
describe 'build success' do
subject { Notify.build_success_email(build.id, 'wow@example.com') }
before { build.success }
it_behaves_like 'build email'
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details'
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do
is_expected.to have_subject /Build success for/
end
end
describe 'build fail' do
subject { Notify.build_fail_email(build.id, 'wow@example.com') }
before { build.drop }
it_behaves_like 'build email'
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details'
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do
is_expected.to have_subject /Build failed for/
end
end
end
end
require 'spec_helper'
require 'email_spec'
require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Matchers
include_context 'gitlab email notification'
describe 'profile notifications' do
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
let(:token) { 'kETLwRaayvigPq_x3SNM' }
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
end
it 'includes a link for user to set password' do
params = "reset_password_token=#{token}"
is_expected.to have_body_text(
%r{http://localhost(:\d+)?/users/password/edit\?#{params}}
)
end
it 'explains the reset link expiration' do
is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
is_expected.to have_body_text(new_user_password_url)
is_expected.to have_body_text(/\?user_email=.*%40.*/)
end
end
describe 'for users that signed up, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
end
describe 'user added ssh key' do
let(:key) { create(:personal_key) }
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
is_expected.to have_body_text /#{key.title}/
end
it 'includes a link to ssh keys page' do
is_expected.to have_body_text /#{profile_keys_path}/
end
end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
end
end
require 'spec_helper' require 'spec_helper'
require 'email_spec' require 'email_spec'
require 'mailers/shared/notify'
describe Notify do describe Notify do
include EmailSpec::Helpers include EmailSpec::Helpers
include EmailSpec::Matchers include EmailSpec::Matchers
include RepoHelpers include RepoHelpers
new_user_address = 'newguy@example.com' include_context 'gitlab email notification'
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
let(:build) { create(:ci_build) }
before(:each) do
ActionMailer::Base.deliveries.clear
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
end
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
end
it 'has a Reply-To address' do
reply_to = subject.header[:reply_to].addresses
expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: /
end
it 'has headers that reference an existing thread' do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'a new user email' do |user_email, site_path|
it 'is sent to the new user' do
is_expected.to deliver_to user_email
end
it 'has the correct subject' do
is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
is_expected.to have_body_text /#{user_email}/
end
it 'includes a link to the site' do
is_expected.to have_body_text /#{site_path}/
end
end
shared_examples 'it should have Gmail Actions links' do
it { is_expected.to have_body_text /ViewAction/ }
end
shared_examples 'it should not have Gmail Actions links' do
it { is_expected.to_not have_body_text /ViewAction/ }
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Issue/ }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Merge request/ }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Commit/ }
end
shared_examples 'an unsubscribeable thread' do
it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples "a user cannot unsubscribe through footer link" do
it { is_expected.not_to have_body_text /unsubscribe/ }
end
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
token = 'kETLwRaayvigPq_x3SNM'
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
end
it 'includes a link for user to set password' do
params = "reset_password_token=#{token}"
is_expected.to have_body_text(
%r{http://localhost(:\d+)?/users/password/edit\?#{params}}
)
end
it 'explains the reset link expiration' do
is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
is_expected.to have_body_text(new_user_password_url)
is_expected.to have_body_text(/\?user_email=.*%40.*/)
end
end
describe 'for users that signed up, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
end
describe 'user added ssh key' do
let(:key) { create(:personal_key) }
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
is_expected.to have_body_text /#{key.title}/
end
it 'includes a link to ssh keys page' do
is_expected.to have_body_text /#{profile_keys_path}/
end
end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
context 'for a project' do context 'for a project' do
describe 'items that are assignable, the email' do describe 'items that are assignable, the email' do
...@@ -1015,50 +791,4 @@ describe Notify do ...@@ -1015,50 +791,4 @@ describe Notify do
should have_body_text(unsubscribe_link) should have_body_text(unsubscribe_link)
end end
end end
describe 'build success' do
before { build.success }
subject { Notify.build_success_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do
should have_subject /Build success for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
describe 'build fail' do
before { build.drop }
subject { Notify.build_fail_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do
should have_subject /Build failed for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
end end
shared_context 'gitlab email notification' do
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
let(:new_user_address) { 'newguy@example.com' }
before do
ActionMailer::Base.deliveries.clear
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
end
end
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
end
it 'has a Reply-To address' do
reply_to = subject.header[:reply_to].addresses
expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: /
end
it 'has headers that reference an existing thread' do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'a new user email' do
it 'is sent to the new user' do
is_expected.to deliver_to new_user_address
end
it 'has the correct subject' do
is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
is_expected.to have_body_text /#{new_user_address}/
end
end
shared_examples 'it should have Gmail Actions links' do
it { is_expected.to have_body_text /ViewAction/ }
end
shared_examples 'it should not have Gmail Actions links' do
it { is_expected.to_not have_body_text /ViewAction/ }
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Issue/ }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Merge request/ }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Commit/ }
end
shared_examples 'an unsubscribeable thread' do
it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples "a user cannot unsubscribe through footer link" do
it { is_expected.not_to have_body_text /unsubscribe/ }
end
...@@ -74,6 +74,10 @@ describe ApplicationSetting, models: true do ...@@ -74,6 +74,10 @@ describe ApplicationSetting, models: true do
.only_integer .only_integer
.is_greater_than(0) .is_greater_than(0)
end end
it_behaves_like 'an object with email-formated attributes', :admin_notification_email do
subject { setting }
end
end end
context 'restricted signup domains' do context 'restricted signup domains' do
......
...@@ -79,6 +79,16 @@ describe Issue, "Issuable" do ...@@ -79,6 +79,16 @@ describe Issue, "Issuable" do
expect(hook_data[:repository][:description]).to eq(issue.project.description) expect(hook_data[:repository][:description]).to eq(issue.project.description)
expect(hook_data[:repository][:homepage]).to eq(issue.project.web_url) expect(hook_data[:repository][:homepage]).to eq(issue.project.web_url)
expect(hook_data[:object_attributes]).to eq(issue.hook_attrs) expect(hook_data[:object_attributes]).to eq(issue.hook_attrs)
expect(hook_data).to_not have_key(:assignee)
end
context "issue is assigned" do
before { issue.update_attribute(:assignee, user) }
it "returns correct hook data" do
expect(hook_data[:object_attributes]['assignee_id']).to eq(user.id)
expect(hook_data[:assignee]).to eq(user.hook_attrs)
end
end end
end end
......
# == Schema Information
#
# Table name: emails
#
# id :integer not null, primary key
# user_id :integer not null
# email :string(255) not null
# created_at :datetime
# updated_at :datetime
#
require 'spec_helper'
describe Email, models: true do
describe 'validations' do
it_behaves_like 'an object with email-formated attributes', :email do
subject { build(:email) }
end
end
end
...@@ -31,6 +31,10 @@ describe Member, models: true do ...@@ -31,6 +31,10 @@ describe Member, models: true do
it { is_expected.to validate_presence_of(:source) } it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) } it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
it_behaves_like 'an object with email-formated attributes', :invite_email do
subject { build(:project_member) }
end
context "when an invite email is provided" do context "when an invite email is provided" do
let(:member) { build(:project_member, invite_email: "user@example.com", user: nil) } let(:member) { build(:project_member, invite_email: "user@example.com", user: nil) }
...@@ -159,7 +163,7 @@ describe Member, models: true do ...@@ -159,7 +163,7 @@ describe Member, models: true do
describe "#generate_invite_token" do describe "#generate_invite_token" do
let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) } let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
it "sets the invite token" do it "sets the invite token" do
expect { member.generate_invite_token }.to change { member.invite_token} expect { member.generate_invite_token }.to change { member.invite_token}
end end
......
...@@ -355,6 +355,13 @@ describe Repository, models: true do ...@@ -355,6 +355,13 @@ describe Repository, models: true do
end end
end end
describe :skip_merged_commit do
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
describe "Elastic search", elastic: true do describe "Elastic search", elastic: true do
before do before do
Repository.__elasticsearch__.create_index! Repository.__elasticsearch__.create_index!
......
...@@ -119,37 +119,15 @@ describe User, models: true do ...@@ -119,37 +119,15 @@ describe User, models: true do
it { is_expected.to validate_length_of(:bio).is_within(0..255) } it { is_expected.to validate_length_of(:bio).is_within(0..255) }
describe 'email' do it_behaves_like 'an object with email-formated attributes', :email do
it 'accepts info@example.com' do subject { build(:user) }
user = build(:user, email: 'info@example.com') end
expect(user).to be_valid
end
it 'accepts info+test@example.com' do
user = build(:user, email: 'info+test@example.com')
expect(user).to be_valid
end
it "accepts o'reilly@example.com" do
user = build(:user, email: "o'reilly@example.com")
expect(user).to be_valid
end
it 'rejects test@test@example.com' do
user = build(:user, email: 'test@test@example.com')
expect(user).to be_invalid
end
it 'rejects mailto:test@example.com' do
user = build(:user, email: 'mailto:test@example.com')
expect(user).to be_invalid
end
it "rejects lol!'+=?><#$%^&*()@gmail.com" do it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do
user = build(:user, email: "lol!'+=?><#$%^&*()@gmail.com") subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } }
expect(user).to be_invalid end
end
describe 'email' do
context 'when no signup domains listed' do context 'when no signup domains listed' do
before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return([]) } before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return([]) }
it 'accepts any email' do it 'accepts any email' do
......
...@@ -10,6 +10,7 @@ describe API::API, api: true do ...@@ -10,6 +10,7 @@ describe API::API, api: true do
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) } let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
before do before do
project.team << [user, :reporters] project.team << [user, :reporters]
...@@ -170,10 +171,12 @@ describe API::API, api: true do ...@@ -170,10 +171,12 @@ describe API::API, api: true do
source_branch: 'feature_conflict', source_branch: 'feature_conflict',
target_branch: 'master', target_branch: 'master',
author: user, author: user,
labels: 'label, label2' labels: 'label, label2',
milestone_id: milestone.id
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(json_response['title']).to eq('Test merge_request') expect(json_response['title']).to eq('Test merge_request')
expect(json_response['labels']).to eq(['label', 'label2']) expect(json_response['labels']).to eq(['label', 'label2'])
expect(json_response['milestone']['id']).to eq(milestone.id)
end end
it "should return 422 when source_branch equals target_branch" do it "should return 422 when source_branch equals target_branch" do
...@@ -374,18 +377,24 @@ describe API::API, api: true do ...@@ -374,18 +377,24 @@ describe API::API, api: true do
end end
describe "PUT /projects/:id/merge_requests/:merge_request_id" do describe "PUT /projects/:id/merge_requests/:merge_request_id" do
it "should return merge_request" do it "updates title and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['title']).to eq('New title') expect(json_response['title']).to eq('New title')
end end
it "should return merge_request" do it "updates description and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description" put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['description']).to eq('New description') expect(json_response['description']).to eq('New description')
end end
it "updates milestone_id and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id
expect(response.status).to eq(200)
expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "should return 400 when source_branch is specified" do it "should return 400 when source_branch is specified" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master" source_branch: "master", target_branch: "master"
...@@ -449,6 +458,28 @@ describe API::API, api: true do ...@@ -449,6 +458,28 @@ describe API::API, api: true do
end end
end end
describe 'GET :id/merge_requests/:merge_request_id/closes_issues' do
it 'returns the issue that will be closed on merge' do
issue = create(:issue, project: project)
mr = merge_request.tap do |mr|
mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
end
get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
end
def mr_with_later_created_and_updated_at_time def mr_with_later_created_and_updated_at_time
merge_request merge_request
merge_request.created_at += 1.hour merge_request.created_at += 1.hour
......
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
# Note: You have access to `email_value` which is the email address value
# being currently tested).
shared_examples 'an object with email-formated attributes' do |*attributes|
attributes.each do |attribute|
describe "specifically its :#{attribute} attribute" do
%w[
info@example.com
info+test@example.com
o'reilly@example.com
mailto:test@example.com
lol!'+=?><#$%^&*()@gmail.com
].each do |valid_email|
context "with a value of '#{valid_email}'" do
let(:email_value) { valid_email }
it 'is valid' do
subject.send("#{attribute}=", valid_email)
expect(subject).to be_valid
end
end
end
%w[
foobar
test@test@example.com
].each do |invalid_email|
context "with a value of '#{invalid_email}'" do
let(:email_value) { invalid_email }
it 'is invalid' do
subject.send("#{attribute}=", invalid_email)
expect(subject).to be_invalid
end
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment