Commit e69af6d2 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/sidekiq-mem-killer-debug

parents a7b1b512 7ca36859
......@@ -5,20 +5,55 @@ v 8.10.0 (unreleased)
- Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Add Sidekiq queue duration to transaction metrics.
- Fix MR-auto-close text added to description. !4836
- Eager load award emoji on notes
- Fix pagination when sorting by columns with lots of ties (like priority)
- Exclude email check from the standard health check
- Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
- Fix changing issue state columns in milestone view
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Add API endpoint for a group issues !4520 (mahcsig)
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
v 8.9.1
- Fix merge requests project settings help link anchor
- Fix GitLab project import issues related to notes and builds
- Improve performance of searching repository tags by name by using a memorized tag array
- Fix false truncated warnings with ISO-8559 files
- Fix unwanted label unassignment when doing bulk action on issues page
- Fix 404 when accessing pipelines as guest user on public projects
- Fix mobile Safari bug where horizontal nav arrows would flicker on scroll
- Fix in auto merge when pipeline is nil
- Refactor labels documentation. !3347
- Eager load award emoji on notes. !4628
- Fix some CI wording in documentation. !4660
- Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720
- Add documentation for the export & import features. !4732
- Add some docs for Docker Registry configuration. !4738
- Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744
- Display group/project access requesters separately in the admin area. !4798
- Add documentation and examples for configuring cloud storage for registry images. !4812
- Clarifies documentation about artifact expiry. !4831
- Fix the Network graph links. !4832
- Fix MR-auto-close text added to description. !4836
- Add documentation for award emoji now that comments can be awarded with emojis. !4839
- Fix typo in export failure email. !4847
- Fix header vertical centering. !4170
- Fix subsequent SAML sign ins. !4718
- Set button label when picking an option from status dropdown. !4771
- Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775
- Handle external issues in IssueReferenceFilter. !4789
- Support for rendering/redacting multiple documents. !4828
- Update Todos documentation and screenshots to include new functionality. !4840
- Hide nav arrows by default. !4843
- Added bottom padding to label color suggestion link. !4845
- Use jQuery objects in ref dropdown. !4850
- Fix GitLab project import issues related to notes and builds. !4855
- Restrict header logo to 36px so it doesn't overflow. !4861
- Fix unwanted label unassignment. !4863
- Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869
- Restore old behavior around diff notes to outdated discussions. !4870
- Fix merge requests project settings help link anchor. !4873
- Fix 404 when accessing pipelines as guest user on public projects. !4881
- Remove width restriction for logo on sign-in page. !4888
- Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
- Apply selected value as label. !4886
- Fix temp file being deleted after the request while importing a GitLab project. !4894
- Fix pagination when sorting by columns with lots of ties (like priority)
- Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912
- Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915
v 8.9.0
- Fix builds API response not including commit data
......
......@@ -234,7 +234,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
gem 'sentry-raven', '~> 1.1.0'
gem 'premailer-rails', '~> 1.9.0'
......
......@@ -656,7 +656,7 @@ GEM
activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3)
thor (~> 0.14)
sentry-raven (0.15.6)
sentry-raven (1.1.0)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.7.0)
......@@ -952,7 +952,7 @@ DEPENDENCIES
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 0.15)
sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9)
sham_rack
shoulda-matchers (~> 2.8.0)
......
......@@ -50,7 +50,7 @@
#= require_directory ./ci
#= require_directory ./commit
#= require_directory ./extensions
#= require_directory ./lib
#= require_directory ./lib/utils
#= require_directory ./u2f
#= require_directory .
#= require fuzzaldrin-plus
......
......@@ -4,5 +4,4 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require Chart
#= require_tree .
#= require raphael
#= require g.raphael
#= require g.bar
......@@ -4,17 +4,9 @@ class @Milestone
type: "PUT"
url: issue_url
data: data
success: (data) ->
if data.saved == true
if data.assignee_avatar_url
img_tag = $('<img/>')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
success: (_data) =>
@successCallback(_data, li)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
......@@ -25,8 +17,9 @@ class @Milestone
type: "PUT"
url: sort_issues_url
data: data
success: (data) ->
if data.saved != true
success: (_data) =>
@successCallback(_data)
error: ->
new Flash("Issues update failed", 'alert')
dataType: "json"
......@@ -37,9 +30,10 @@ class @Milestone
type: "PUT"
url: sort_mr_url
data: data
success: (data) ->
if data.saved != true
new Flash("MR update failed", 'alert')
success: (_data) =>
@successCallback(_data)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
@updateMergeRequest: (li, merge_request_url, data) ->
......@@ -47,19 +41,22 @@ class @Milestone
type: "PUT"
url: merge_request_url
data: data
success: (data) ->
if data.saved == true
if data.assignee_avatar_url
success: (_data) =>
@successCallback(_data, li)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
@successCallback: (data, element) =>
if data.assignee
img_tag = $('<img/>')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.attr('src', data.assignee.avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
$(element).find('.assignee-icon').html(img_tag)
else
new Flash("Issue update failed", 'alert')
dataType: "json"
$(element).find('.assignee-icon').html('')
$(element).effect 'highlight'
constructor: ->
oldMouseStart = $.ui.sortable.prototype._mouseStart
......@@ -81,6 +78,8 @@ class @Milestone
stop: (event, ui) ->
$(".issues-sortable-list").css "min-height", "0px"
update: (event, ui) ->
# Prevents sorting from container which element has been removed.
if $(this).find(ui.item).length > 0
data = $(this).sortable("serialize")
Milestone.sortIssues(data)
......
......@@ -4,9 +4,6 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require raphael
#= require g.raphael
#= require g.bar
#= require_tree .
$ ->
......
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require d3
#= require_tree .
......@@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
imported_file = project_params[:file].path + "-import"
FileUtils.copy_entry(project_params[:file].path, imported_file)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
File.expand_path(project_params[:file].path),
File.expand_path(imported_file),
project_params[:path]).execute
if @project.saved?
......
......@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
def new
commit unless @repository.empty?
......@@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController
file_content_encoding: params[:encoding]
}
end
def validate_diff_params
if [:since, :to, :offset].any? { |key| params[key].blank? }
render nothing: true
end
end
end
......@@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
raw_notes = @issue.notes_with_associations.fresh
@notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_to do |format|
......@@ -111,6 +115,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :edit
end
end
format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
......
......@@ -85,6 +85,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
respond_to do |format|
format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
......@@ -190,7 +199,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
unless @merge_request.mergeable?
# Disable the CI check if merge_when_build_succeeds is enabled since we have
# to wait until CI completes to know
unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?)
@status = :failed
return
end
......@@ -325,8 +336,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@discussions = @merge_request.mr_and_commit_notes.inc_author_project_award_emoji.fresh.discussions
@notes = @discussions.flatten
@discussions = @merge_request.mr_and_commit_notes.
inc_author_project_award_emoji.
fresh.
discussions
@notes = Banzai::NoteRenderer.render(
@discussions.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
@noteable = @merge_request
# Get commits from repository
......@@ -373,4 +397,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ensure_ref_fetched
@merge_request.ensure_ref_fetched
end
def merge_when_build_succeeds_active?
params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active?
end
end
......@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController
def create
@note = Notes::CreateService.new(project, current_user, note_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
......@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
......@@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController
name: note.name
}
elsif note.valid?
Banzai::NoteRenderer.render([note], @project, current_user)
{
valid: true,
id: note.id,
......
module JavascriptHelper
def page_specific_javascripts(js = nil)
@page_specific_javascripts = js unless js.nil?
@page_specific_javascripts
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js), { integrity: true, "data-turbolinks-track" => true }
end
end
......@@ -163,7 +163,7 @@ module Ci
end
def skip_ci?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message
end
def environments
......
......@@ -264,19 +264,19 @@ class MergeRequest < ActiveRecord::Base
self.title.sub(WIP_REGEX, "")
end
def mergeable?
return false unless mergeable_state?
def mergeable?(skip_ci_check: false)
return false unless mergeable_state?(skip_ci_check: skip_ci_check)
check_if_can_be_merged
can_be_merged?
end
def mergeable_state?
def mergeable_state?(skip_ci_check: false)
return false unless open?
return false if work_in_progress?
return false if broken?
return false unless mergeable_ci_state?
return false unless skip_ci_check || mergeable_ci_state?
true
end
......
......@@ -6,6 +6,10 @@ class Note < ActiveRecord::Base
include Awardable
include Importable
# Attribute containing rendered and redacted Markdown as generated by
# Banzai::ObjectRenderer.
attr_accessor :note_html
default_value_for :system, false
attr_mentionable :note, pipeline: :note
......
.bs-callout.help-callout
%h4 How to setup CI for this project
%ol
%li
Add at least one runner to the project.
Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions.
%li
Put the .gitlab-ci.yml in the root of your repository. Examples can be found in
#{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
%li
Return to this page and refresh it, it should show a new build.
.alert.alert-danger
%p
Now you need Runners to process your builds.
%span
Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it
......@@ -30,11 +30,8 @@
= javascript_include_tag "application", integrity: true
-# FIXME: SRI doesn't apply to the dynamically-generated per-page
-# JavaScript due to a bug in sprockets-rails.
-# See https://github.com/rails/sprockets-rails/issues/359
- if page_specific_javascripts
= javascript_include_tag page_specific_javascripts, {"data-turbolinks-track" => true}
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
= csrf_meta_tags
......
- if current_user && current_user.is_admin? && Ci::Runner.count.zero?
= render 'ci/shared/no_runners'
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
- if defined?(sidebar) && sidebar
= render "layouts/ci/#{sidebar}"
- elsif current_user
= render 'layouts/nav/dashboard'
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
= render "layouts/flash"
= render 'layouts/ci/info'
%div{ class: container_class }
.content
.clearfix
= yield
%html{lang: "en"}
%head
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
%title
GitLab CI
%body
= yield :header
%table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
%tr
%td{align: "left", style: "margin: 0; padding: 10px;"}
= yield
%br
%tr
%td{align: "left", style: "margin: 0; padding: 10px;"}
%p{style: "font-size:small;color:#777"}
- if @project
You're receiving this notification because you are the one who triggered a build on the #{@project.name} project.
.nav-links.sub-nav
%ul{ class: (container_class) }
- page_specific_javascripts asset_path("graphs/application.js")
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/chart.js')
= page_specific_javascript_tag('graphs/application.js')
= nav_link(action: :show) do
= link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
......
- page_title "Network", @ref
- page_specific_javascripts asset_path("network/application.js")
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/raphael.js')
= page_specific_javascript_tag('network/application.js')
= render "projects/commits/head"
= render "head"
%div{ class: (container_class) }
......
......@@ -32,7 +32,7 @@
.note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author)
= note.note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable
= render 'projects/notes/edit_form', note: note
......
......@@ -21,6 +21,7 @@
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
- render_colored_label(label)
%span{ class: "assignee-icon" }
- if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
......
- page_title @user.name
- page_description @user.bio
- page_specific_javascripts asset_path("users/application.js")
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/d3.js')
= page_specific_javascript_tag('users/application.js')
- header_title @user.name, user_path(@user)
- @no_container = true
......
......@@ -84,6 +84,8 @@ module Gitlab
config.assets.precompile << "graphs/application.js"
config.assets.precompile << "users/application.js"
config.assets.precompile << "network/application.js"
config.assets.precompile << "lib/utils/*.js"
config.assets.precompile << "lib/*.js"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
......
# Email forcibly included in the standard checks, but the email health check
# doesn't support the full range of SMTP options, which can result in failures
# for valid SMTP configurations.
# Overwrite the HealthCheck's detection of whether email is configured
# in order to avoid the email check during standard checks
module HealthCheck
class Utils
def self.mailer_configured?
false
end
end
end
HealthCheck.setup do |config|
config.standard_checks = ['database', 'migrations', 'cache']
config.full_checks = ['database', 'migrations', 'cache']
end
......@@ -113,6 +113,10 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(Banzai::Renderer)
config.instrument_methods(Banzai::Querying)
config.instrument_instance_methods(Banzai::ObjectRenderer)
config.instrument_instance_methods(Banzai::Redactor)
config.instrument_methods(Banzai::NoteRenderer)
[Issuable, Mentionable, Participable].each do |klass|
config.instrument_instance_methods(klass)
config.instrument_instance_methods(klass::ClassMethods)
......
......@@ -10,6 +10,7 @@
if Rails.env.production?
Rails.application.config.action_mailer.delivery_method = :smtp
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
address: "email.server.com",
port: 465,
......
......@@ -44,6 +44,7 @@
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint.
- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability.
- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab.
......
# Debugging Tips
Sometimes things don't work the way they should. Here are some tips on debugging issues out
in production.
## The GNU Project Debugger (gdb)
`gdb` is a must-have tool for debugging issues. To install on Ubuntu/Debian:
```
sudo apt-get install gdb
```
On CentOS:
```
sudo yum install gdb
```
## Common Problems
Many of the tips to diagnose issues below apply to many different situations. We'll use one
concrete example to illustrate what you can do to learn what is going wrong.
### 502 Gateway Timeout after unicorn spins at 100% CPU
This error occurs when the Web server times out (default: 60 s) after not
hearing back from the unicorn worker. If the CPU spins to 100% while this in
progress, there may be something taking longer than it should.
To fix this issue, we first need to figure out what is happening. The
following tips are only recommended if you do NOT mind users being affected by
downtime. Otherwise skip to the next section.
1. Load the problematic URL
1. Run `sudo gdb -p <PID>` to attach to the unicorn process.
1. In the gdb window, type:
```
call (void) rb_backtrace()
```
1. This forces the process to generate a Ruby backtrace. Check
`/var/log/gitlab/unicorn/unicorn_stderr.log` for the backtace. For example, you may see:
```ruby
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
```
1. To see the current threads, run:
```
apply all thread bt
```
1. Once you're done debugging with `gdb`, be sure to detach from the process and exit:
```
detach
exit
```
Note that if the unicorn process terminates before you are able to run these
commands, gdb will report an error. To buy more time, you can always raise the
Unicorn timeout. For omnibus users, you can edit `/etc/gitlab/gitlab.rb` and
increase it from 60 seconds to 300:
```ruby
unicorn['worker_timeout'] = 300
```
For source installations, edit `config/unicorn.rb`.
[Reconfigure] GitLab for the changes to take effect.
[Reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
#### Troubleshooting without affecting other users
The previous section attached to a running unicorn process, and this may have
undesirable effects for users trying to access GitLab during this time. If you
are concerned about affecting others during a production system, you can run a
separate Rails process to debug the issue:
1. Log in to your GitLab account.
1. Copy the URL that is causing problems (e.g. https://gitlab.com/ABC).
1. Obtain the private token for your user (Profile Settings -> Account).
1. Bring up the GitLab Rails console. For omnibus users, run:
````
sudo gitlab-rails console
```
1. At the Rails console, run:
```ruby
[1] pry(main)> app.get '<URL FROM STEP 1>/private_token?<TOKEN FROM STEP 2>'
```
For example:
```ruby
[1] pry(main)> app.get 'https://gitlab.com/gitlab-org/gitlab-ce/issues/1?private_token=123456'
```
1. In a new window, run `top`. It should show this ruby process using 100% CPU. Write down the PID.
1. Follow step 2 from the previous section on using gdb.
# More information
* [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
* [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
# Here's the script I'll use to demonstrate - it just loops forever:
$ cat test.rb
#!/usr/bin/env ruby
loop do
sleep 1
end
# Now, I'll start the script in the background, and redirect stdout and stderr
# to /dev/null:
$ ruby ./test.rb >/dev/null 2>/dev/null &
[1] 1343
# Next, I'll grab the PID of the script (1343):
$ ps aux | grep test.rb
vagrant 1343 0.0 0.4 3884 1652 pts/0 S 14:42 0:00 ruby ./test.rb
vagrant 1345 0.0 0.2 4624 852 pts/0 S+ 14:42 0:00 grep --color=auto test.rb
# Now I start gdb. Note that I'm using sudo here. This may or may not be
# necessary in your setup. I'd try without sudo first, and fall back to adding
# it if the next step fails:
$ sudo gdb
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>.
# OK, now I'm in gdb, and I want to instruct it to attach to our Ruby process.
# I can do that using the 'attach' command, which takes a PID (the one we
# gathered above):
(gdb) attach 1343
Attaching to process 1343
Reading symbols from /opt/vagrant_ruby/bin/ruby...done.
Reading symbols from /lib/i386-linux-gnu/librt.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/i386-linux-gnu/librt.so.1
Reading symbols from /lib/i386-linux-gnu/libdl.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/i386-linux-gnu/libdl.so.2
Reading symbols from /lib/i386-linux-gnu/libcrypt.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/i386-linux-gnu/libcrypt.so.1
Reading symbols from /lib/i386-linux-gnu/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/i386-linux-gnu/libm.so.6
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/i386-linux-gnu/libc.so.6
Reading symbols from /lib/i386-linux-gnu/libpthread.so.0...(no debugging symbols found)...done.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Loaded symbols for /lib/i386-linux-gnu/libpthread.so.0
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0xb770c424 in __kernel_vsyscall ()
# Great, now gdb is attached to the target process. If the step above fails, try
# going back and running gdb under sudo. The next thing I want to do is gather
# C-level backtraces from all threads in the process. The following command
# stands for 'thread apply all backtrace':
(gdb) t a a bt
Thread 1 (Thread 0xb74d76c0 (LWP 1343)):
#0 0xb770c424 in __kernel_vsyscall ()
#1 0xb75d7abd in select () from /lib/i386-linux-gnu/libc.so.6
#2 0x08069c56 in rb_thread_wait_for (time=...) at eval.c:11376
#3 0x080a20fd in rb_f_sleep (argc=1, argv=0xbf85f490) at process.c:1633
#4 0x0805e0e2 in call_cfunc (argv=0xbf85f490, argc=1, len=-1, recv=3075299660, func=0x80a20b0 <rb_f_sleep>)
at eval.c:5778
#5 rb_call0 (klass=3075304600, recv=3075299660, id=9393, oid=9393, argc=1, argv=0xbf85f490, body=0xb74c85a8, flags=2)
at eval.c:5928
#6 0x0805e35d in rb_call (klass=3075304600, recv=3075299660, mid=9393, argc=1, argv=0xbf85f490, scope=1,
self=<optimized out>) at eval.c:6176
#7 0x080651ec in rb_eval (self=3075299660, n=0xb74c4e1c) at eval.c:3521
#8 0x0805c31c in rb_yield_0 (val=6, self=3075299660, klass=<optimized out>, flags=0, avalue=0) at eval.c:5095
#9 0x0806a1e5 in loop_i () at eval.c:5227
#10 0x08058dbd in rb_rescue2 (b_proc=0x806a1c0 <loop_i>, data1=0, r_proc=0, data2=0) at eval.c:5491
#11 0x08058f28 in rb_f_loop () at eval.c:5252
#12 0x0805e0c1 in call_cfunc (argv=0x0, argc=0, len=0, recv=3075299660, func=0x8058ef0 <rb_f_loop>) at eval.c:5781
#13 rb_call0 (klass=3075304600, recv=3075299660, id=4121, oid=4121, argc=0, argv=0x0, body=0xb74d4dbc, flags=2)
at eval.c:5928
#14 0x0805e35d in rb_call (klass=3075304600, recv=3075299660, mid=4121, argc=0, argv=0x0, scope=1, self=<optimized out>)
at eval.c:6176
#15 0x080651ec in rb_eval (self=3075299660, n=0xb74c4dcc) at eval.c:3521
#16 0x080662c6 in rb_eval (self=3075299660, n=0xb74c4de0) at eval.c:3236
#17 0x08068ee4 in ruby_exec_internal () at eval.c:1654
#18 0x08068f24 in ruby_exec () at eval.c:1674
#19 0x0806b2cd in ruby_run () at eval.c:1684
#20 0x08053771 in main (argc=2, argv=0xbf860204, envp=0xbf860210) at main.c:48
# C backtraces are sometimes sufficient, but often Ruby backtraces are necessary
# for debugging as well. Ruby has a built-in function called rb_backtrace() that
# we can use to dump out a Ruby backtrace, but it prints to stdout or stderr
# (depending on your Ruby version), which might have been redirected to a file
# or to /dev/null (as in our example) when the process started up.
#
# To get aroundt this, we'll do a little trick and redirect the target process's
# stdout and stderr to the current TTY, so that any output from the process
# will appear directly on our screen.
# First, let's close the existing file descriptors for stdout and stderr
# (FD 1 and 2, respectively):
(gdb) call (void) close(1)
(gdb) call (void) close(2)
# Next, we need to figure out the device name for the current TTY:
(gdb) shell tty
/dev/pts/0
# OK, now we can pass the device name obtained above to open() and attach
# file descriptors 1 and 2 back to the current TTY with these calls:
(gdb) call (int) open("/dev/pts/0", 2, 0)
$1 = 1
(gdb) call (int) open("/dev/pts/0", 2, 0)
$2 = 2
# Finally, we call rb_backtrace() in order to dump the Ruby backtrace:
(gdb) call (void) rb_backtrace()
from ./test.rb:4:in `sleep'
from ./test.rb:4
from ./test.rb:3:in `loop'
from ./test.rb:3
# And here's how we get out of gdb. Once you've quit, you'll probably want to
# clean up the stuck process by killing it.
(gdb) quit
A debugging session is active.
Inferior 1 [process 1343] will be detached.
Quit anyway? (y or n) y
Detaching from program: /opt/vagrant_ruby/bin/ruby, process 1343
$
......@@ -28,7 +28,7 @@ GET /issues?labels=foo,bar&state=opened
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names |
| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
......@@ -83,6 +83,82 @@ Example response:
]
```
## List group issues
Get a list of a group's issues.
```
GET /groups/:id/issues
GET /groups/:id/issues?state=opened
GET /groups/:id/issues?state=closed
GET /groups/:id/issues?labels=foo
GET /groups/:id/issues?labels=foo,bar
GET /groups/:id/issues?labels=foo,bar&state=opened
GET /groups/:id/issues?milestone=1.0.0
GET /groups/:id/issues?milestone=1.0.0&state=opened
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a group |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4/issues
```
Example response:
```json
[
{
"project_id" : 4,
"milestone" : {
"due_date" : null,
"project_id" : 4,
"state" : "closed",
"description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
"iid" : 3,
"id" : 11,
"title" : "v3.0",
"created_at" : "2016-01-04T15:31:39.788Z",
"updated_at" : "2016-01-04T15:31:39.788Z"
},
"author" : {
"state" : "active",
"web_url" : "https://gitlab.example.com/u/root",
"avatar_url" : null,
"username" : "root",
"id" : 1,
"name" : "Administrator"
},
"description" : "Omnis vero earum sunt corporis dolor et placeat.",
"state" : "closed",
"iid" : 1,
"assignee" : {
"avatar_url" : null,
"web_url" : "https://gitlab.example.com/u/lennie",
"state" : "active",
"username" : "lennie",
"id" : 9,
"name" : "Dr. Luella Kovacek"
},
"labels" : [],
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false,
"user_notes_count": 1
}
]
```
## List project issues
Get a list of a project's issues.
......@@ -104,7 +180,7 @@ GET /projects/:id/issues?iid=42
| `id` | integer | yes | The ID of a project |
| `iid` | integer | no | Return the issue having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names |
| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
......
......@@ -65,6 +65,13 @@ curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
## Resource Owner Password Credentials
## Deprecation Notice
1. Starting in GitLab 9.0, the Resource Owner Password Credentials will be *disabled* for users with two-factor authentication turned on.
2. These users can access the API using [personal access tokens] instead.
---
In this flow, a token is requested in exchange for the resource owner credentials (username and password).
The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
client is part of the device operating system or a highly privileged application), and when other authorization grant types are not
......@@ -100,3 +107,5 @@ client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http
access_token = client.password.get_token('user@example.com', 'sekret')
puts access_token.token
```
[personal access tokens]: ./README.md#personal-access-tokens
......@@ -374,40 +374,6 @@ Get Gemnasium service settings for a project.
GET /projects/:id/services/gemnasium
```
## GitLab CI
Continuous integration server from GitLab
### Create/Edit GitLab CI service
Set GitLab CI service for a project.
```
PUT /projects/:id/services/gitlab-ci
```
Parameters:
- `token` (**required**) - GitLab CI project specific token
- `project_url` (**required**) - http://ci.gitlabhq.com/projects/3
- `enable_ssl_verification` (optional) - Enable SSL verification
### Delete GitLab CI service
Delete GitLab CI service for a project.
```
DELETE /projects/:id/services/gitlab-ci
```
### Get GitLab CI service settings
Get GitLab CI service settings for a project.
```
GET /projects/:id/services/gitlab-ci
```
## HipChat
Private group chat and IM
......
# Session
## Deprecation Notice
1. Starting in GitLab 9.0, this feature will be *disabled* for users with two-factor authentication turned on.
2. These users can access the API using [personal access tokens] instead.
---
You can login with both GitLab and LDAP credentials in order to obtain the
private token.
......@@ -45,3 +52,5 @@ Example response:
"private_token": "9koXpg98eAheJpvBs5tK"
}
```
[personal access tokens]: ./README.md#personal-access-tokens
......@@ -1034,8 +1034,8 @@ You can find the link under `/ci/lint` of your gitlab instance.
## Skipping builds
If your commit message contains `[ci skip]`, the commit will be created but the
builds will be skipped.
If your commit message contains `[ci skip]` or `[skip ci]`, using any
capitalization, the commit will be created but the builds will be skipped.
## Examples
......
......@@ -8,7 +8,7 @@ the matched text will be closed. This happens when the commit is pushed to a pro
When not specified, the default `issue_closing_pattern` as shown below will be used:
```bash
((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)
((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)
```
Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that matches a reference to a local issue (`#123`), cross-project issue (`group/project#123`) or a link to an issue (`https://gitlab.example.com/group/project/issues/123`).
......
......@@ -44,13 +44,13 @@ to avoid getting this error, you need to remove all instances of the
**Omnibus Installation**
```
$ sudo gitlab-rails runner "Service.where(type: 'JenkinsService').delete_all"
$ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all"
```
**Source Installation**
```
$ bundle exec rails runner "Service.where(type: 'JenkinsService').delete_all" production
$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production
```
## Downgrade to CE
......
......@@ -52,7 +52,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
### CPU
- 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
- 1 core supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
- **2 cores** is the **recommended** number of cores and supports up to 500 users
- 4 cores supports up to 2,000 users
- 8 cores supports up to 5,000 users
......
......@@ -59,6 +59,41 @@ module API
end
end
resource :groups do
# Get a list of group issues
#
# Parameters:
# id (required) - The ID of a group
# state (optional) - Return "opened" or "closed" issues
# labels (optional) - Comma-separated list of label names
# milestone (optional) - Milestone title
# order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
# sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
#
# Example Requests:
# GET /groups/:id/issues
# GET /groups/:id/issues?state=opened
# GET /groups/:id/issues?state=closed
# GET /groups/:id/issues?labels=foo
# GET /groups/:id/issues?labels=foo,bar
# GET /groups/:id/issues?labels=foo,bar&state=opened
# GET /groups/:id/issues?milestone=1.0.0
# GET /groups/:id/issues?milestone=1.0.0&state=closed
get ":id/issues" do
group = find_group(params[:id])
params[:state] ||= 'opened'
params[:group_id] = group.id
params[:milestone_title] = params.delete(:milestone)
params[:label_name] = params.delete(:labels)
params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort]
issues = IssuesFinder.new(current_user, params).execute
present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
resource :projects do
# Get a list of project issues
#
......
......@@ -7,40 +7,13 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
nodes = Querying.css(doc, 'a.gfm[data-reference-type]')
visible = nodes_visible_to_user(nodes)
nodes.each do |node|
unless visible.include?(node)
# The reference should be replaced by the original text,
# which is not always the same as the rendered text.
text = node.attr('data-original') || node.text
node.replace(text)
end
end
Redactor.new(project, current_user).redact([doc])
doc
end
private
def nodes_visible_to_user(nodes)
per_type = Hash.new { |h, k| h[k] = [] }
visible = Set.new
nodes.each do |node|
per_type[node.attr('data-reference-type')] << node
end
per_type.each do |type, nodes|
parser = Banzai::ReferenceParser[type].new(project, current_user)
visible.merge(parser.nodes_visible_to_user(current_user, nodes))
end
visible
end
def current_user
context[:current_user]
end
......
module Banzai
module NoteRenderer
# Renders a collection of Note instances.
#
# notes - The notes to render.
# project - The project to use for rendering/redacting.
# user - The user viewing the notes.
# path - The request path.
# wiki - The project's wiki.
# git_ref - The current Git reference.
def self.render(notes, project, user = nil, path = nil, wiki = nil, git_ref = nil)
renderer = ObjectRenderer.new(project,
user,
requested_path: path,
project_wiki: wiki,
ref: git_ref,
pipeline: :note)
renderer.render(notes, :note)
end
end
end
module Banzai
# Class for rendering multiple objects (e.g. Note instances) in a single pass.
#
# Rendered Markdown is stored in an attribute in every object based on the
# name of the attribute containing the Markdown. For example, when the
# attribute `note` is rendered the HTML is stored in `note_html`.
class ObjectRenderer
attr_reader :project, :user
# Make sure to set the appropriate pipeline in the `raw_context` attribute
# (e.g. `:note` for Note instances).
#
# project - A Project to use for rendering and redacting Markdown.
# user - The user viewing the Markdown/HTML documents, if any.
# context - A Hash containing extra attributes to use in the rendering
# pipeline.
def initialize(project, user = nil, raw_context = {})
@project = project
@user = user
@raw_context = raw_context
end
# Renders and redacts an Array of objects.
#
# objects - The objects to render
# attribute - The attribute containing the raw Markdown to render.
#
# Returns the same input objects.
def render(objects, attribute)
documents = render_objects(objects, attribute)
redacted = redact_documents(documents)
objects.each_with_index do |object, index|
object.__send__("#{attribute}_html=", redacted.fetch(index))
end
objects
end
# Renders the attribute of every given object.
def render_objects(objects, attribute)
objects.map do |object|
render_attribute(object, attribute)
end
end
# Redacts the list of documents.
#
# Returns an Array containing the redacted documents.
def redact_documents(documents)
redactor = Redactor.new(project, user)
redactor.redact(documents).map do |document|
document.to_html.html_safe
end
end
# Returns a Banzai context for the given object and attribute.
def context_for(object, attribute)
context = base_context.merge(cache_key: [object, attribute])
if object.respond_to?(:author)
context[:author] = object.author
end
context
end
# Renders the attribute of an object.
#
# Returns a `Nokogiri::HTML::Document`.
def render_attribute(object, attribute)
context = context_for(object, attribute)
string = object.__send__(attribute)
html = Banzai.render(string, context)
Banzai::Pipeline[:relative_link].to_document(html, context)
end
def base_context
@base_context ||= @raw_context.merge(current_user: user, project: project)
end
end
end
module Banzai
module Pipeline
class RelativeLinkPipeline < BasePipeline
def self.filters
FilterArray[
Filter::RelativeLinkFilter
]
end
end
end
end
module Banzai
# Class for removing Markdown references a certain user is not allowed to
# view.
class Redactor
attr_reader :user, :project
# project - A Project to use for redacting links.
# user - The currently logged in user (if any).
def initialize(project, user = nil)
@project = project
@user = user
end
# Redacts the references in the given Array of documents.
#
# This method modifies the given documents in-place.
#
# documents - A list of HTML documents containing references to redact.
#
# Returns the documents passed as the first argument.
def redact(documents)
nodes = documents.flat_map do |document|
Querying.css(document, 'a.gfm[data-reference-type]')
end
redact_nodes(nodes)
documents
end
# Redacts the given nodes
#
# nodes - An Array of HTML nodes to redact.
def redact_nodes(nodes)
visible = nodes_visible_to_user(nodes)
nodes.each do |node|
unless visible.include?(node)
# The reference should be replaced by the original text,
# which is not always the same as the rendered text.
text = node.attr('data-original') || node.text
node.replace(text)
end
end
end
# Returns the nodes visible to the current user.
#
# nodes - The input nodes to check.
#
# Returns a new Array containing the visible nodes.
def nodes_visible_to_user(nodes)
per_type = Hash.new { |h, k| h[k] = [] }
visible = Set.new
nodes.each do |node|
per_type[node.attr('data-reference-type')] << node
end
per_type.each do |type, nodes|
parser = Banzai::ReferenceParser[type].new(project, user)
visible.merge(parser.nodes_visible_to_user(user, nodes))
end
visible
end
end
end
......@@ -2,7 +2,7 @@ module Ci
class GitlabCiYamlProcessor
class ValidationError < StandardError; end
include Gitlab::Ci::Config::Node::ValidationHelpers
include Gitlab::Ci::Config::Node::LegacyValidationHelpers
DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
......
......@@ -4,8 +4,6 @@ module Gitlab
# Base GitLab CI Configuration facade
#
class Config
delegate :valid?, :errors, to: :@global
##
# Temporary delegations that should be removed after refactoring
#
......@@ -18,6 +16,14 @@ module Gitlab
@global.process!
end
def valid?
@global.valid?
end
def errors
@global.errors
end
def to_hash
@config
end
......
......@@ -15,27 +15,24 @@ module Gitlab
#
module Configurable
extend ActiveSupport::Concern
include Validatable
def allowed_nodes
self.class.allowed_nodes || {}
included do
validations do
validates :config, hash: true
end
end
private
def prevalidate!
unless @value.is_a?(Hash)
@errors << 'should be a configuration entry with hash value'
end
end
def create_node(key, factory)
factory.with(value: @value[key])
factory.nullify! unless @value.has_key?(key)
factory.with(value: @config[key], key: key)
factory.nullify! unless @config.has_key?(key)
factory.create!
end
class_methods do
def allowed_nodes
def nodes
Hash[@allowed_nodes.map { |key, factory| [key, factory.dup] }]
end
......@@ -47,7 +44,6 @@ module Gitlab
define_method(symbol) do
raise Entry::InvalidError unless valid?
@nodes[symbol].try(:value)
end
......
......@@ -8,14 +8,14 @@ module Gitlab
class Entry
class InvalidError < StandardError; end
attr_accessor :description
attr_reader :config
attr_accessor :key, :description
def initialize(value)
@value = value
def initialize(config)
@config = config
@nodes = {}
@errors = []
prevalidate!
@validator = self.class.validator.new(self)
@validator.validate
end
def process!
......@@ -23,50 +23,54 @@ module Gitlab
return unless valid?
compose!
nodes.each(&:process!)
nodes.each(&:validate!)
process_nodes!
end
def nodes
@nodes.values
end
def valid?
errors.none?
end
def leaf?
allowed_nodes.none?
self.class.nodes.none?
end
def errors
@errors + nodes.map(&:errors).flatten
def key
@key || self.class.name.demodulize.underscore
end
def allowed_nodes
{}
def valid?
errors.none?
end
def validate!
raise NotImplementedError
def errors
@validator.full_errors +
nodes.map(&:errors).flatten
end
def value
raise NotImplementedError
end
private
def self.nodes
{}
end
def prevalidate!
def self.validator
Validator
end
private
def compose!
allowed_nodes.each do |key, essence|
self.class.nodes.each do |key, essence|
@nodes[key] = create_node(key, essence)
end
end
def process_nodes!
nodes.each(&:process!)
end
def create_node(key, essence)
raise NotImplementedError
end
......
......@@ -30,6 +30,7 @@ module Gitlab
@entry_class.new(@attributes[:value]).tap do |entry|
entry.description = @attributes[:description]
entry.key = @attributes[:key]
end
end
end
......
......@@ -2,7 +2,7 @@ module Gitlab
module Ci
class Config
module Node
module ValidationHelpers
module LegacyValidationHelpers
private
def validate_duration(value)
......
......@@ -11,16 +11,14 @@ module Gitlab
# implementation in Runner.
#
class Script < Entry
include ValidationHelpers
include Validatable
def value
@value.join("\n")
validations do
validates :config, array_of_strings: true
end
def validate!
unless validate_array_of_strings(@value)
@errors << 'before_script should be an array of strings'
end
def value
@config.join("\n")
end
end
end
......
module Gitlab
module Ci
class Config
module Node
module Validatable
extend ActiveSupport::Concern
class_methods do
def validator
validator = Class.new(Node::Validator)
if defined?(@validations)
@validations.each { |rules| validator.class_eval(&rules) }
end
validator
end
private
def validations(&block)
(@validations ||= []).append(block)
end
end
end
end
end
end
end
module Gitlab
module Ci
class Config
module Node
class Validator < SimpleDelegator
include ActiveModel::Validations
include Node::Validators
def initialize(node)
super(node)
@node = node
end
def full_errors
errors.full_messages.map do |error|
"#{@node.key} #{error}".humanize
end
end
def self.name
'Validator'
end
end
end
end
end
end
module Gitlab
module Ci
class Config
module Node
module Validators
class ArrayOfStringsValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
def validate_each(record, attribute, value)
unless validate_array_of_strings(value)
record.errors.add(attribute, 'should be an array of strings')
end
end
end
class HashValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Hash)
record.errors.add(attribute, 'should be a configuration entry hash')
end
end
end
end
end
end
end
end
......@@ -23,7 +23,11 @@ module Gitlab
private
def decompress_archive
untar_zxf(archive: @archive_file, dir: @shared.export_path)
result = untar_zxf(archive: @archive_file, dir: @shared.export_path)
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
true
end
end
end
......
......@@ -10,17 +10,22 @@ module Gitlab
end
def execute
Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file,
shared: @shared)
if check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
if import_file && check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
project_tree.restored_project
else
raise Projects::ImportService::Error.new(@shared.errors.join(', '))
end
remove_import_file
end
private
def import_file
Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file,
shared: @shared)
end
def check_version!
Gitlab::ImportExport::VersionChecker.check!(shared: @shared)
end
......@@ -59,6 +64,10 @@ module Gitlab
def wiki_repo_path
File.join(@shared.export_path, 'project.wiki.bundle')
end
def remove_import_file
FileUtils.rm_rf(@archive_file)
end
end
end
end
require 'rails_helper'
describe Projects::BlobController do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
user = create(:user)
project.team << [user, :master]
sign_in(user)
end
describe 'GET diff' do
render_views
def do_get(opts = {})
params = { namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: 'master/CHANGELOG' }
get :diff, params.merge(opts)
end
context 'when essential params are missing' do
it 'renders nothing' do
do_get
expect(response.body).to be_blank
end
end
context 'when essential params are present' do
it 'renders the diff content' do
do_get(since: 1, to: 5, offset: 10)
expect(response.body).to be_present
end
end
end
end
......@@ -264,6 +264,18 @@ describe Projects::MergeRequestsController do
merge_when_build_succeeds
end
context 'when project.only_allow_merge_if_build_succeeds? is true' do
before do
project.update_column(:only_allow_merge_if_build_succeeds, true)
end
it 'returns :merge_when_build_succeeds' do
merge_when_build_succeeds
expect(assigns(:status)).to eq(:merge_when_build_succeeds)
end
end
end
end
end
......
#= require lib/common_utils
#= require lib/utils/common_utils
describe 'Application', ->
describe 'disable buttons', ->
......
#= require lib/text_utility
#= require lib/utils/text_utility
#= require issue
describe 'Issue', ->
......
#= require bootstrap
#= require select2
#= require lib/type_utility
#= require lib/utils/type_utility
#= require gl_dropdown
#= require api
#= require project_select
......
#= require gl_dropdown
#= require search_autocomplete
#= require jquery
#= require lib/common_utils
#= require lib/type_utility
#= require lib/utils/common_utils
#= require lib/utils/type_utility
#= require fuzzaldrin-plus
......
require 'spec_helper'
describe Banzai::NoteRenderer do
describe '.render' do
it 'renders a Note' do
note = double(:note)
project = double(:project)
wiki = double(:wiki)
user = double(:user)
expect(Banzai::ObjectRenderer).to receive(:new).
with(project, user,
requested_path: 'foo',
project_wiki: wiki,
ref: 'bar',
pipeline: :note).
and_call_original
expect_any_instance_of(Banzai::ObjectRenderer).
to receive(:render).with([note], :note)
described_class.render([note], project, user, 'foo', wiki, 'bar')
end
end
end
require 'spec_helper'
describe Banzai::ObjectRenderer do
let(:project) { create(:empty_project) }
let(:user) { project.owner }
describe '#render' do
it 'renders and redacts an Array of objects' do
renderer = described_class.new(project, user)
object = double(:object, note: 'hello', note_html: nil)
expect(renderer).to receive(:render_objects).with([object], :note).
and_call_original
expect(renderer).to receive(:redact_documents).
with(an_instance_of(Array)).
and_call_original
expect(object).to receive(:note_html=).with('<p>hello</p>')
renderer.render([object], :note)
end
end
describe '#render_objects' do
it 'renders an Array of objects' do
object = double(:object, note: 'hello')
renderer = described_class.new(project, user)
expect(renderer).to receive(:render_attribute).with(object, :note).
and_call_original
rendered = renderer.render_objects([object], :note)
expect(rendered).to be_an_instance_of(Array)
expect(rendered[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
end
end
describe '#redact_documents' do
it 'redacts a set of documents and returns them as an Array of Strings' do
doc = Nokogiri::HTML.fragment('<p>hello</p>')
renderer = described_class.new(project, user)
expect_any_instance_of(Banzai::Redactor).to receive(:redact).
with([doc]).
and_call_original
redacted = renderer.redact_documents([doc])
expect(redacted).to eq(['<p>hello</p>'])
end
end
describe '#context_for' do
let(:object) { double(:object, note: 'hello') }
let(:renderer) { described_class.new(project, user) }
it 'returns a Hash' do
expect(renderer.context_for(object, :note)).to be_an_instance_of(Hash)
end
it 'includes the cache key' do
context = renderer.context_for(object, :note)
expect(context[:cache_key]).to eq([object, :note])
end
context 'when the object responds to "author"' do
it 'includes the author in the context' do
expect(object).to receive(:author).and_return('Alice')
context = renderer.context_for(object, :note)
expect(context[:author]).to eq('Alice')
end
end
context 'when the object does not respond to "author"' do
it 'does not include the author in the context' do
context = renderer.context_for(object, :note)
expect(context.key?(:author)).to eq(false)
end
end
end
describe '#render_attribute' do
it 'renders the attribute of an object' do
object = double(:doc, note: 'hello')
renderer = described_class.new(project, user, pipeline: :note)
doc = renderer.render_attribute(object, :note)
expect(doc).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
expect(doc.to_html).to eq('<p>hello</p>')
end
end
describe '#base_context' do
let(:context) do
described_class.new(project, user, pipeline: :note).base_context
end
it 'returns a Hash' do
expect(context).to be_an_instance_of(Hash)
end
it 'includes the custom attributes' do
expect(context[:pipeline]).to eq(:note)
end
it 'includes the current user' do
expect(context[:current_user]).to eq(user)
end
it 'includes the current project' do
expect(context[:project]).to eq(project)
end
end
end
require 'spec_helper'
describe Banzai::Redactor do
let(:user) { build(:user) }
let(:project) { build(:empty_project) }
let(:redactor) { described_class.new(project, user) }
describe '#redact' do
it 'redacts an Array of documents' do
doc1 = Nokogiri::HTML.
fragment('<a class="gfm" data-reference-type="issue">foo</a>')
doc2 = Nokogiri::HTML.
fragment('<a class="gfm" data-reference-type="issue">bar</a>')
expect(redactor).to receive(:nodes_visible_to_user).and_return([])
expect(redactor.redact([doc1, doc2])).to eq([doc1, doc2])
expect(doc1.to_html).to eq('foo')
expect(doc2.to_html).to eq('bar')
end
end
describe '#redact_nodes' do
it 'redacts an Array of nodes' do
doc = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
node = doc.children[0]
expect(redactor).to receive(:nodes_visible_to_user).
with([node]).
and_return(Set.new)
redactor.redact_nodes([node])
expect(doc.to_html).to eq('foo')
end
end
describe '#nodes_visible_to_user' do
it 'returns a Set containing the visible nodes' do
doc = Nokogiri::HTML.fragment('<a data-reference-type="issue"></a>')
node = doc.children[0]
expect_any_instance_of(Banzai::ReferenceParser::IssueParser).
to receive(:nodes_visible_to_user).
with(user, [node]).
and_return([node])
expect(redactor.nodes_visible_to_user([node])).to eq(Set.new([node]))
end
end
end
......@@ -951,7 +951,7 @@ EOT
config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Before script config should be an array of strings")
end
it "returns errors if job before_script parameter is not an array of strings" do
......@@ -1206,5 +1206,17 @@ EOT
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings")
end
end
describe "Validate configuration templates" do
templates = Dir.glob("#{Rails.root.join('vendor/gitlab-ci-yml')}/**/*.gitlab-ci.yml")
templates.each do |file|
it "does not return errors for #{file}" do
file = File.read(file)
expect { GitlabCiYamlProcessor.new(file) }.not_to raise_error
end
end
end
end
end
......@@ -7,26 +7,26 @@ describe Gitlab::Ci::Config::Node::Configurable do
node.include(described_class)
end
describe 'allowed nodes' do
describe 'configured nodes' do
before do
node.class_eval do
allow_node :object, Object, description: 'test object'
end
end
describe '#allowed_nodes' do
it 'has valid allowed nodes' do
expect(node.allowed_nodes).to include :object
describe '.nodes' do
it 'has valid nodes' do
expect(node.nodes).to include :object
end
it 'creates a node factory' do
expect(node.allowed_nodes[:object])
expect(node.nodes[:object])
.to be_an_instance_of Gitlab::Ci::Config::Node::Factory
end
it 'returns a duplicated factory object' do
first_factory = node.allowed_nodes[:object]
second_factory = node.allowed_nodes[:object]
first_factory = node.nodes[:object]
second_factory = node.nodes[:object]
expect(first_factory).not_to be_equal(second_factory)
end
......
......@@ -25,6 +25,16 @@ describe Gitlab::Ci::Config::Node::Factory do
expect(entry.description).to eq 'test description'
end
end
context 'when setting key' do
it 'creates entry with custom key' do
entry = factory
.with(value: ['ls', 'pwd'], key: 'test key')
.create!
expect(entry.key).to eq 'test key'
end
end
end
context 'when not setting value' do
......
......@@ -3,13 +3,19 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Node::Global do
let(:global) { described_class.new(hash) }
describe '#allowed_nodes' do
describe '.nodes' do
it 'can contain global config keys' do
expect(global.allowed_nodes).to include :before_script
expect(described_class.nodes).to include :before_script
end
it 'returns a hash' do
expect(global.allowed_nodes).to be_a Hash
expect(described_class.nodes).to be_a Hash
end
end
describe '#key' do
it 'returns underscored class name' do
expect(global.key).to eq 'global'
end
end
......@@ -79,7 +85,7 @@ describe Gitlab::Ci::Config::Node::Global do
describe '#errors' do
it 'reports errors from child nodes' do
expect(global.errors)
.to include 'before_script should be an array of strings'
.to include 'Before script config should be an array of strings'
end
end
......
require 'spec_helper'
describe Gitlab::Ci::Config::Node::Script do
let(:entry) { described_class.new(value) }
let(:entry) { described_class.new(config) }
describe '#validate!' do
before { entry.validate! }
describe '#process!' do
before { entry.process! }
context 'when entry value is correct' do
let(:value) { ['ls', 'pwd'] }
context 'when entry config value is correct' do
let(:config) { ['ls', 'pwd'] }
describe '#value' do
it 'returns concatenated command' do
......@@ -29,12 +29,12 @@ describe Gitlab::Ci::Config::Node::Script do
end
context 'when entry value is not correct' do
let(:value) { 'ls' }
let(:config) { 'ls' }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include /should be an array of strings/
.to include 'Script config should be an array of strings'
end
end
......
require 'spec_helper'
describe Gitlab::Ci::Config::Node::Validatable do
let(:node) { Class.new }
before do
node.include(described_class)
end
describe '.validator' do
before do
node.class_eval do
attr_accessor :test_attribute
validations do
validates :test_attribute, presence: true
end
end
end
it 'returns validator' do
expect(node.validator.superclass)
.to be Gitlab::Ci::Config::Node::Validator
end
context 'when validating node instance' do
let(:node_instance) { node.new }
context 'when attribute is valid' do
before do
node_instance.test_attribute = 'valid'
end
it 'instance of validator is valid' do
expect(node.validator.new(node_instance)).to be_valid
end
end
context 'when attribute is not valid' do
before do
node_instance.test_attribute = nil
end
it 'instance of validator is invalid' do
expect(node.validator.new(node_instance)).to be_invalid
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Config::Node::Validator do
let(:validator) { Class.new(described_class) }
let(:validator_instance) { validator.new(node) }
let(:node) { spy('node') }
shared_examples 'delegated validator' do
context 'when node is valid' do
before do
allow(node).to receive(:test_attribute).and_return('valid value')
end
it 'validates attribute in node' do
expect(node).to receive(:test_attribute)
expect(validator_instance).to be_valid
end
it 'returns no errors' do
validator_instance.validate
expect(validator_instance.full_errors).to be_empty
end
end
context 'when node is invalid' do
before do
allow(node).to receive(:test_attribute).and_return(nil)
end
it 'validates attribute in node' do
expect(node).to receive(:test_attribute)
expect(validator_instance).to be_invalid
end
it 'returns errors' do
validator_instance.validate
expect(validator_instance.full_errors).not_to be_empty
end
end
end
describe 'attributes validations' do
before do
validator.class_eval do
validates :test_attribute, presence: true
end
end
it_behaves_like 'delegated validator'
end
describe 'interface validations' do
before do
validator.class_eval do
validate do
unless @node.test_attribute == 'valid value'
errors.add(:test_attribute, 'invalid value')
end
end
end
end
it_behaves_like 'delegated validator'
end
end
......@@ -67,6 +67,12 @@ describe Gitlab::Ci::Config do
expect(config.errors).not_to be_empty
end
end
describe '#errors' do
it 'returns an array of strings' do
expect(config.errors).to all(be_an_instance_of(String))
end
end
end
end
end
......
......@@ -136,6 +136,148 @@ describe API::API, api: true do
end
end
describe "GET /groups/:id/issues" do
let!(:group) { create(:group) }
let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) }
let!(:group_closed_issue) do
create :closed_issue,
author: user,
assignee: user,
project: group_project,
state: :closed,
milestone: group_milestone
end
let!(:group_confidential_issue) do
create :issue,
:confidential,
project: group_project,
author: author,
assignee: assignee
end
let!(:group_issue) do
create :issue,
author: user,
assignee: user,
project: group_project,
milestone: group_milestone
end
let!(:group_label) do
create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
end
let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) }
let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) }
let!(:group_empty_milestone) do
create(:milestone, title: '4.0.0', project: group_project)
end
let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) }
before do
group_project.team << [user, :reporter]
end
let(:base_url) { "/groups/#{group.id}/issues" }
it 'returns group issues without confidential issues for non project members' do
get api(base_url, non_member)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(group_issue.title)
end
it 'returns group confidential issues for author' do
get api(base_url, author)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
it 'returns group confidential issues for assignee' do
get api(base_url, assignee)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
it 'returns group issues with confidential issues for project members' do
get api(base_url, user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
it 'returns group confidential issues for admin' do
get api(base_url, admin)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
it 'returns an array of labeled group issues' do
get api("#{base_url}?labels=#{group_label.title}", 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['labels']).to eq([group_label.title])
end
it 'returns an array of labeled group issues where all labels match' do
get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if no group issue matches labels' do
get api("#{base_url}?labels=foo,bar", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if milestone does not exist' do
get api("#{base_url}?milestone=foo", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an array of issues in given milestone' do
get api("#{base_url}?milestone=#{group_milestone.title}", 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(group_issue.id)
end
it 'returns an array of issues matching state in milestone' do
get api("#{base_url}?milestone=#{group_milestone.title}"\
'&state=closed', 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(group_closed_issue.id)
end
end
describe "GET /projects/:id/issues" do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
......
......@@ -83,6 +83,9 @@ describe CreateCommitBuildsService, services: true do
context 'when commit contains a [ci skip] directive' do
let(:message) { "some message[ci skip]" }
let(:messageFlip) { "some message[skip ci]" }
let(:capMessage) { "some message[CI SKIP]" }
let(:capMessageFlip) { "some message[SKIP CI]" }
before do
allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
......@@ -96,12 +99,55 @@ describe CreateCommitBuildsService, services: true do
after: '31das312',
commits: commits
)
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
expect(pipeline.status).to eq("skipped")
end
it "skips builds creation if there is [skip ci] tag in commit message" do
commits = [{ message: messageFlip }]
pipeline = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
expect(pipeline.status).to eq("skipped")
end
it "skips builds creation if there is [CI SKIP] tag in commit message" do
commits = [{ message: capMessage }]
pipeline = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
expect(pipeline.status).to eq("skipped")
end
it "skips builds creation if there is [SKIP CI] tag in commit message" do
commits = [{ message: capMessageFlip }]
pipeline = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
expect(pipeline.status).to eq("skipped")
end
it "does not skips builds creation if there is no [ci skip] tag in commit message" do
it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do
allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" }
commits = [{ message: "some message" }]
......
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