Commit 931ab01a authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into 33149-rename-more-builds

* upstream/master: (34 commits)
  Revert "Merge branch 'karma-headless-chrome' into 'master'"
  Make small pipeline schedules UI enhancements.
  Remove js classes from vue component that are not needed in vue component
  Update tests and application
  Adds "Pipeline" to job's sidebar
  Change border color of job's scroll controllers to $border-color
  Add database helpers 'add_timestamps_with_timezone' and 'timestamps_with_timezone'
  Added Tectonic to the page.
  Always check read_issue permissions when loading issue
  Handle legacy jobs without name
  Do not expose internal artifacts hash in build entity
  Use wait_for_requests instead of sleep 0.3
  Limit wiki container width
  Fix migrations testing support RSpec hooks order
  Rename BuildEntity to JobEntity
  Fix support for external_url for commit statuses
  Allow to access pipelines even if they are disabled, but only present jobs and commit statuses without giving ability to access them
  add CHANGELOG.md entry for !12036
  remove phantomjs-specific test hacks
  update karma job to use chrome build image created by gitlab-build-images!41
  ...
parents f99cee8e b29bf626
...@@ -20,12 +20,6 @@ export default { ...@@ -20,12 +20,6 @@ export default {
default: 'top', default: 'top',
}, },
shortFormat: {
type: Boolean,
required: false,
default: false,
},
cssClass: { cssClass: {
type: String, type: String,
required: false, required: false,
...@@ -37,18 +31,12 @@ export default { ...@@ -37,18 +31,12 @@ export default {
tooltipMixin, tooltipMixin,
timeagoMixin, timeagoMixin,
], ],
computed: {
timeagoCssClass() {
return this.shortFormat ? 'js-short-timeago' : 'js-timeago';
},
},
}; };
</script> </script>
<template> <template>
<time <time
:class="[timeagoCssClass, cssClass]" :class="cssClass"
class="js-timeago js-timeago-render" class="js-vue-timeago"
:title="tooltipTitle(time)" :title="tooltipTitle(time)"
:data-placement="tooltipPlacement" :data-placement="tooltipPlacement"
data-container="body" data-container="body"
......
...@@ -148,7 +148,8 @@ label { ...@@ -148,7 +148,8 @@ label {
margin-top: 35px; margin-top: 35px;
} }
.form-group .control-label { .form-group .control-label,
.form-group .control-label-full-width {
font-weight: normal; font-weight: normal;
} }
......
...@@ -51,6 +51,10 @@ body { ...@@ -51,6 +51,10 @@ body {
&.limit-container-width { &.limit-container-width {
max-width: $limited-layout-width; max-width: $limited-layout-width;
} }
&.limit-container-width-sm {
max-width: 790px;
}
} }
.alert-wrapper { .alert-wrapper {
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
background: $gray-light; background: $gray-light;
border: 1px solid $gray-normal; border: 1px solid $border-color;
color: $gl-text-color; color: $gl-text-color;
.truncated-info { .truncated-info {
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
.interval-pattern-form-group { .interval-pattern-form-group {
label { label {
margin-right: 10px; margin-right: 10px;
font-size: 12px; font-weight: normal;
&[for='custom'] { &[for='custom'] {
margin-right: 0; margin-right: 0;
......
...@@ -42,9 +42,7 @@ ...@@ -42,9 +42,7 @@
} }
.git-access-header { .git-access-header {
padding: 16px 40px 11px 0; padding: $gl-padding 0 $gl-padding-top;
line-height: 28px;
font-size: 18px;
} }
.git-clone-holder { .git-clone-holder {
...@@ -66,6 +64,7 @@ ...@@ -66,6 +64,7 @@
.git-clone-holder { .git-clone-holder {
width: 480px; width: 480px;
padding-bottom: $gl-padding;
} }
.nav-controls { .nav-controls {
...@@ -89,9 +88,9 @@ ...@@ -89,9 +88,9 @@
margin: $gl-padding 0; margin: $gl-padding 0;
h3 { h3 {
font-size: 22px; font-size: 19px;
font-weight: normal; font-weight: normal;
margin-top: 1.4em; margin: $gl-padding 0;
} }
} }
......
...@@ -80,10 +80,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -80,10 +80,6 @@ class Projects::ApplicationController < ApplicationController
cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present? cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end end
def builds_enabled
return render_404 unless @project.feature_available?(:builds, current_user)
end
def require_pages_enabled! def require_pages_enabled!
not_found unless Gitlab.config.pages.enabled not_found unless Gitlab.config.pages.enabled
end end
......
...@@ -5,7 +5,6 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -5,7 +5,6 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :builds_enabled, only: :ci
def show def show
respond_to do |format| respond_to do |format|
......
...@@ -10,11 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -10,11 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, before_action :issue, except: [:index, :new, :create, :bulk_update]
:related_branches, :can_create_branch, :realtime_changes, :create_merge_request]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show, :realtime_changes]
# Allow write(create) issue # Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create] before_action :authorize_create_issue!, only: [:new, :create]
...@@ -229,18 +225,19 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -229,18 +225,19 @@ class Projects::IssuesController < Projects::ApplicationController
protected protected
def issue def issue
return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by # The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
return render_404 unless can?(current_user, :read_issue, @issue)
@issue
end end
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue alias_method :issuable, :issue
alias_method :awardable, :issue alias_method :awardable, :issue
alias_method :spammable, :issue alias_method :spammable, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
end
def authorize_update_issue! def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue) return render_404 unless can?(current_user, :update_issue, @issue)
end end
......
...@@ -4,7 +4,6 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -4,7 +4,6 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_read_pipeline! before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :builds_enabled, only: :charts
wrap_parameters Ci::Pipeline wrap_parameters Ci::Pipeline
......
...@@ -218,6 +218,10 @@ module ProjectsHelper ...@@ -218,6 +218,10 @@ module ProjectsHelper
nav_tabs << :container_registry nav_tabs << :container_registry
end end
if project.builds_enabled? && can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines
end
tab_ability_map.each do |tab, ability| tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project) if can?(current_user, ability, project)
nav_tabs << tab nav_tabs << tab
...@@ -231,7 +235,6 @@ module ProjectsHelper ...@@ -231,7 +235,6 @@ module ProjectsHelper
{ {
environments: :read_environment, environments: :read_environment,
milestones: :read_milestone, milestones: :read_milestone,
pipelines: :read_pipeline,
snippets: :read_project_snippet, snippets: :read_project_snippet,
settings: :admin_project, settings: :admin_project,
builds: :read_build, builds: :read_build,
......
...@@ -112,7 +112,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -112,7 +112,7 @@ class CommitStatus < ActiveRecord::Base
end end
def group_name def group_name
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip name.to_s.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end end
def failed_but_allowed? def failed_but_allowed?
...@@ -156,7 +156,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -156,7 +156,7 @@ class CommitStatus < ActiveRecord::Base
end end
def sortable_name def sortable_name
name.split(/(\d+)/).map do |v| name.to_s.split(/(\d+)/).map do |v|
v =~ /\d+/ ? v.to_i : v v =~ /\d+/ ? v.to_i : v
end end
end end
......
...@@ -11,6 +11,7 @@ class GenericCommitStatus < CommitStatus ...@@ -11,6 +11,7 @@ class GenericCommitStatus < CommitStatus
def set_default_values def set_default_values
self.context ||= 'default' self.context ||= 'default'
self.stage ||= 'external' self.stage ||= 'external'
self.stage_idx ||= 1000000
end end
def tags def tags
......
...@@ -203,7 +203,7 @@ class ProjectPolicy < BasePolicy ...@@ -203,7 +203,7 @@ class ProjectPolicy < BasePolicy
unless project.feature_available?(:builds, user) && repository_enabled unless project.feature_available?(:builds, user) && repository_enabled
cannot!(*named_abilities(:build)) cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline)) cannot!(*named_abilities(:pipeline) - [:read_pipeline])
cannot!(*named_abilities(:pipeline_schedule)) cannot!(*named_abilities(:pipeline_schedule))
cannot!(*named_abilities(:environment)) cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment)) cannot!(*named_abilities(:deployment))
......
class BuildDetailsEntity < BuildEntity class BuildDetailsEntity < JobEntity
expose :coverage, :erased_at, :duration expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags expose :tag_list, as: :tags
expose :user, using: UserEntity expose :user, using: UserEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build| expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build|
erase_namespace_project_job_path(project.namespace, project, build) erase_namespace_project_job_path(project.namespace, project, build)
end end
expose :artifacts, using: BuildArtifactEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
expose :iid do |build| expose :iid do |build|
build.merge_request.iid build.merge_request.iid
......
class BuildSerializer < BaseSerializer class BuildSerializer < BaseSerializer
entity BuildEntity entity JobEntity
def represent_status(resource) def represent_status(resource)
data = represent(resource, { only: [:status] }) data = represent(resource, { only: [:status] })
......
...@@ -24,6 +24,6 @@ class DeploymentEntity < Grape::Entity ...@@ -24,6 +24,6 @@ class DeploymentEntity < Grape::Entity
expose :user, using: UserEntity expose :user, using: UserEntity
expose :commit, using: CommitEntity expose :commit, using: CommitEntity
expose :deployable, using: BuildEntity expose :deployable, using: JobEntity
expose :manual_actions, using: BuildEntity expose :manual_actions, using: JobEntity
end end
class BuildEntity < Grape::Entity class JobEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
expose :id expose :id
expose :name expose :name
expose :build_path do |build| expose :build_path do |build|
path_to(:namespace_project_job, build) build.target_url || path_to(:namespace_project_job, build)
end end
expose :retry_path, if: -> (*) { retryable? } do |build| expose :retry_path, if: -> (*) { retryable? } do |build|
......
...@@ -4,7 +4,7 @@ class JobGroupEntity < Grape::Entity ...@@ -4,7 +4,7 @@ class JobGroupEntity < Grape::Entity
expose :name expose :name
expose :size expose :size
expose :detailed_status, as: :status, with: StatusEntity expose :detailed_status, as: :status, with: StatusEntity
expose :jobs, with: BuildEntity expose :jobs, with: JobEntity
private private
......
...@@ -101,12 +101,12 @@ class GitPushService < BaseService ...@@ -101,12 +101,12 @@ class GitPushService < BaseService
UpdateMergeRequestsWorker UpdateMergeRequestsWorker
.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) .perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
EventCreateService.new.push(@project, current_user, build_push_data) EventCreateService.new.push(@project, current_user, build_push_data)
Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks)
Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
if push_remove_branch? if push_remove_branch?
AfterBranchDeleteService AfterBranchDeleteService
......
...@@ -8,10 +8,12 @@ class GitTagPushService < BaseService ...@@ -8,10 +8,12 @@ class GitTagPushService < BaseService
@push_data = build_push_data @push_data = build_push_data
EventCreateService.new.push(project, current_user, @push_data) EventCreateService.new.push(project, current_user, @push_data)
Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push)
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks) SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks)
Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push)
ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size]) ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
true true
......
...@@ -72,6 +72,7 @@ ...@@ -72,6 +72,7 @@
.title .title
%span{ class: "ci-status-icon-#{@build.pipeline.status}" } %span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status) = ci_icon_for_status(@build.pipeline.status)
Pipeline
= link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit' = link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit'
from from
= link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit' = link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit'
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.form-group .form-group
.col-md-9 .col-md-9
= f.label :ref, _('Target Branch'), class: 'label-light' = f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown git-revision-dropdown-toggle', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } ) = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true = f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group .form-group
.col-md-9 .col-md-9
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
= f.hidden_field :title, value: @page.title = f.hidden_field :title, value: @page.title
.form-group .form-group
= f.label :format, class: 'control-label' .col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-10 .col-sm-12
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control" = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
.form-group .form-group
= f.label :content, class: 'control-label' .col-sm-12= f.label :content, class: 'control-label-full-width'
.col-sm-10 .col-sm-12
= render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do = render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do
= render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...' = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'shared/notes/hints' = render 'shared/notes/hints'
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
= link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown") = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown")
.form-group .form-group
= f.label :commit_message, class: 'control-label' .col-sm-12= f.label :commit_message, class: 'control-label-full-width'
.col-sm-10= f.text_field :message, class: 'form-control', rows: 18, value: commit_message .col-sm-12= f.text_field :message, class: 'form-control', rows: 18, value: commit_message
.form-actions .form-actions
- if @page && @page.persisted? - if @page && @page.persisted?
......
- @no_container = true #modal-new-wiki.modal
.modal-dialog
%div{ class: container_class } .modal-content
#modal-new-wiki.modal .modal-header
.modal-dialog %a.close{ href: "#", "data-dismiss" => "modal" } ×
.modal-content %h3.page-title New Wiki Page
.modal-header .modal-body
%a.close{ href: "#", "data-dismiss" => "modal" } × %form.new-wiki-page
%h3.page-title New Wiki Page .form-group
.modal-body = label_tag :new_wiki_path do
%form.new-wiki-page %span Page slug
.form-group = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
= label_tag :new_wiki_path do %span.new-wiki-page-slug-tip
%span Page slug = icon('lightbulb-o')
= text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true Tip: You can specify the full path for the new file.
%span.new-wiki-page-slug-tip We will automatically create any missing directories.
= icon('lightbulb-o') .form-actions
Tip: You can specify the full path for the new file. = button_tag 'Create page', class: 'build-new-wiki btn btn-create'
We will automatically create any missing directories.
.form-actions
= button_tag 'Create page', class: 'build-new-wiki btn btn-create'
- @no_container = true - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title "Edit", @page.title.capitalize, "Wiki" - page_title "Edit", @page.title.capitalize, "Wiki"
%div{ class: container_class } .wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } = icon('angle-double-left')
= icon('angle-double-left')
.nav-text .nav-text
%h2.wiki-page-title %h2.wiki-page-title
- if @page.persisted?
= link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
- else
= @page.title.capitalize
%span.light
&middot;
- if @page.persisted? - if @page.persisted?
= link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) Edit Page
- else - else
= @page.title.capitalize Create Page
%span.light
&middot;
- if @page.persisted?
Edit Page
- else
Create Page
.nav-controls .nav-controls
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New page New page
- if @page.persisted? - if @page.persisted?
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page history Page history
- if can?(current_user, :admin_wiki, @project) - if can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
Delete Delete
= render 'form' = render 'form'
= render 'sidebar' = render 'sidebar'
- @no_container = true - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title "Git Access", "Wiki" - page_title "Git Access", "Wiki"
%div{ class: container_class } .wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
%button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } = icon('angle-double-left')
= icon('angle-double-left')
.git-access-header .git-access-header
Clone repository Clone repository
%strong= @project_wiki.path_with_namespace %strong= @project_wiki.path_with_namespace
= render "shared/clone_panel", project: @project_wiki = render "shared/clone_panel", project: @project_wiki
.wiki-git-access .wiki-git-access
%h3 Install Gollum %h3 Install Gollum
%pre.dark %pre.dark
:preserve :preserve
gem install gollum gem install gollum
%p %p
It is recommended to install It is recommended to install
%code github-markdown %code github-markdown
so that GFM features render locally: so that GFM features render locally:
%pre.dark %pre.dark
:preserve :preserve
gem install github-markdown gem install github-markdown
%h3 Clone your wiki %h3 Clone your wiki
%pre.dark %pre.dark
:preserve :preserve
git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')} git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')}
cd #{h @project_wiki.path} cd #{h @project_wiki.path}
%h3 Start Gollum and edit locally %h3 Start Gollum and edit locally
%pre.dark %pre.dark
:preserve :preserve
gollum gollum
== Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.5.0 codename Knife) >> Thin web server (v1.5.0 codename Knife)
>> Maximum connections set to 1024 >> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop >> Listening on 0.0.0.0:4567, CTRL+C to stop
= render 'sidebar' = render 'sidebar'
- page_title "History", @page.title.capitalize, "Wiki" - page_title "History", @page.title.capitalize, "Wiki"
%div{ class: container_class } .wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } = icon('angle-double-left')
= icon('angle-double-left')
.nav-text .nav-text
%h2.wiki-page-title %h2.wiki-page-title
= link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
%span.light %span.light
&middot; &middot;
History History
.table-holder .table-holder
%table.table %table.table
%thead %thead
%tr
%th Page version
%th Author
%th Commit Message
%th Last updated
%th Format
%tbody
- @page.versions.each_with_index do |version, index|
- commit = version
%tr %tr
%th Page version %td
%th Author = link_to project_wiki_path_with_version(@project, @page,
%th Commit Message commit.id, index == 0) do
%th Last updated = truncate_sha(commit.id)
%th Format %td
%tbody = commit.author.name
- @page.versions.each_with_index do |version, index| %td
- commit = version = commit.message
%tr %td
%td #{time_ago_with_tooltip(version.authored_date)}
= link_to project_wiki_path_with_version(@project, @page, %td
commit.id, index == 0) do %strong
= truncate_sha(commit.id) = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
%td
= commit.author.name
%td
= commit.message
%td
#{time_ago_with_tooltip(version.authored_date)}
%td
%strong
= @page.page.wiki.page(@page.page.name, commit.id).try(:format)
= render 'sidebar' = render 'sidebar'
- @no_container = true - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title @page.title.capitalize, "Wiki" - page_title @page.title.capitalize, "Wiki"
%div{ class: container_class } .wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } = icon('angle-double-left')
= icon('angle-double-left')
.wiki-breadcrumb .wiki-breadcrumb
%span= breadcrumb(@page.slug) %span= breadcrumb(@page.slug)
.nav-text .nav-text
%h2.wiki-page-title= @page.title.capitalize %h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by %span.wiki-last-edit-by
Last edited by Last edited by
%strong %strong
#{@page.commit.author.name} #{@page.commit.author.name}
#{time_ago_with_tooltip(@page.commit.authored_date)} #{time_ago_with_tooltip(@page.commit.authored_date)}
.nav-controls .nav-controls
= render 'main_links' = render 'main_links'
- if @page.historical? - if @page.historical?
.warning_message .warning_message
This is an old version of this page. This is an old version of this page.
You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
.wiki-holder.prepend-top-default.append-bottom-default .wiki-holder.prepend-top-default.append-bottom-default
.wiki .wiki
= render_wiki_content(@page) = render_wiki_content(@page)
= render 'sidebar' = render 'sidebar'
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' }
.input-group-btn .input-group-btn
= clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard")) = clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"), class: "btn-default btn-clipboard")
:javascript :javascript
$('ul.clone-options-dropdown a').on('click',function(e){ $('ul.clone-options-dropdown a').on('click',function(e){
......
---
title: Add database helpers 'add_timestamps_with_timezone' and 'timestamps_with_timezone'
merge_request: 11229
author: @blackst0ne
---
title: Add support for image and services configuration in .gitlab-ci.yml
merge_request: 8578
author:
---
title: Github - Fix token interpolation when cloning wiki repository
merge_request:
author:
---
title: Fix support for external CI services
merge_request: 11176
author:
---
title: Handle nameless legacy jobs
merge_request:
author:
# ActiveRecord custom data type for storing datetimes with timezone information.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11229
if Gitlab::Database.postgresql?
require 'active_record/connection_adapters/postgresql_adapter'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter
NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamptz' })
end
end
end
elsif Gitlab::Database.mysql?
require 'active_record/connection_adapters/mysql2_adapter'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter
NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamp' })
end
end
end
end
# ActiveRecord custom method definitions with timezone information.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11229
require 'active_record/connection_adapters/abstract/schema_definitions'
# Appends columns `created_at` and `updated_at` to a table.
#
# It is used in table creation like:
# create_table 'users' do |t|
# t.timestamps_with_timezone
# end
module ActiveRecord
module ConnectionAdapters
class TableDefinition
def timestamps_with_timezone(**options)
options[:null] = false if options[:null].nil?
[:created_at, :updated_at].each do |column_name|
column(column_name, :datetime_with_timezone, options)
end
end
end
end
end
# rubocop:disable Migration/Datetime
class AddRequestedAtToMembers < ActiveRecord::Migration class AddRequestedAtToMembers < ActiveRecord::Migration
def change def change
add_column :members, :requested_at, :datetime add_column :members, :requested_at, :datetime
......
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class CreatePersonalAccessTokens < ActiveRecord::Migration class CreatePersonalAccessTokens < ActiveRecord::Migration
def change def change
create_table :personal_access_tokens do |t| create_table :personal_access_tokens do |t|
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class AddDeployments < ActiveRecord::Migration class AddDeployments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class AddEnvironments < ActiveRecord::Migration class AddEnvironments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class AddProtectedBranchesPushAccess < ActiveRecord::Migration class AddProtectedBranchesPushAccess < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class AddProtectedBranchesMergeAccess < ActiveRecord::Migration class AddProtectedBranchesMergeAccess < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
......
# rubocop:disable Migration/Datetime
class AddResolvedToNotes < ActiveRecord::Migration class AddResolvedToNotes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateUserAgentDetails < ActiveRecord::Migration class CreateUserAgentDetails < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateBoards < ActiveRecord::Migration class CreateBoards < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateLists < ActiveRecord::Migration class CreateLists < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# rubocop:disable RemoveIndex # rubocop:disable RemoveIndex
class AddDeletedAtToNamespaces < ActiveRecord::Migration class AddDeletedAtToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class AddTableIssueMetrics < ActiveRecord::Migration class AddTableIssueMetrics < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class AddTableMergeRequestMetrics < ActiveRecord::Migration class AddTableMergeRequestMetrics < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateProjectFeatures < ActiveRecord::Migration class CreateProjectFeatures < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class CreateMergeRequestsClosingIssues < ActiveRecord::Migration class CreateMergeRequestsClosingIssues < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateLabelPriorities < ActiveRecord::Migration class CreateLabelPriorities < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class CreateUserChatNamesTable < ActiveRecord::Migration class CreateUserChatNamesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class AddRoutesTable < ActiveRecord::Migration class AddRoutesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class AddLastUsedAtToKey < ActiveRecord::Migration class AddLastUsedAtToKey < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateTimelogsCe < ActiveRecord::Migration class CreateTimelogsCe < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class ChangeExpiresAtToDateInPersonalAccessTokens < ActiveRecord::Migration class ChangeExpiresAtToDateInPersonalAccessTokens < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateChatTeams < ActiveRecord::Migration class CreateChatTeams < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class CreateUploads < ActiveRecord::Migration class CreateUploads < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class DropCiProjects < ActiveRecord::Migration class DropCiProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class RemoveUnusedCiTablesAndColumns < ActiveRecord::Migration class RemoveUnusedCiTablesAndColumns < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateProtectedTags < ActiveRecord::Migration class CreateProtectedTags < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateSystemNoteMetadata < ActiveRecord::Migration class CreateSystemNoteMetadata < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class AddClosedAtToIssues < ActiveRecord::Migration class AddClosedAtToIssues < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
......
# rubocop:disable Migration/Timestamps
class CreateContainerRepository < ActiveRecord::Migration class CreateContainerRepository < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class CreateCiTriggerSchedules < ActiveRecord::Migration class CreateCiTriggerSchedules < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class CreatePipelineSchedulesTable < ActiveRecord::Migration class CreatePipelineSchedulesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateRedirectRoutes < ActiveRecord::Migration class CreateRedirectRoutes < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime. # Set this constant to true if this migration requires downtime.
DOWNTIME = false DOWNTIME = false
......
# rubocop:disable Migration/Datetime
class AddLastRepositoryUpdatedAtToProjects < ActiveRecord::Migration class AddLastRepositoryUpdatedAtToProjects < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
......
# rubocop:disable Migration/Timestamps
class CreateConversationalDevelopmentIndexMetrics < ActiveRecord::Migration class CreateConversationalDevelopmentIndexMetrics < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
......
# rubocop:disable Migration/Timestamps
class CreatePipelineStages < ActiveRecord::Migration class CreatePipelineStages < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class DropCiTriggerSchedulesTable < ActiveRecord::Migration class DropCiTriggerSchedulesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
...@@ -122,7 +122,7 @@ limit can vary from installation to installation. As a result it's recommended ...@@ -122,7 +122,7 @@ limit can vary from installation to installation. As a result it's recommended
you do not use more than 32 threads in a single migration. Usually 4-8 threads you do not use more than 32 threads in a single migration. Usually 4-8 threads
should be more than enough. should be more than enough.
## Removing indices ## Removing indexes
When removing an index make sure to use the method `remove_concurrent_index` instead When removing an index make sure to use the method `remove_concurrent_index` instead
of the regular `remove_index` method. The `remove_concurrent_index` method of the regular `remove_index` method. The `remove_concurrent_index` method
...@@ -142,7 +142,7 @@ class MyMigration < ActiveRecord::Migration ...@@ -142,7 +142,7 @@ class MyMigration < ActiveRecord::Migration
end end
``` ```
## Adding indices ## Adding indexes
If you need to add a unique index please keep in mind there is the possibility If you need to add a unique index please keep in mind there is the possibility
of existing duplicates being present in the database. This means that should of existing duplicates being present in the database. This means that should
...@@ -222,6 +222,41 @@ add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8) ...@@ -222,6 +222,41 @@ add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
add_column(:projects, :foo, :integer, default: 10, limit: 8) add_column(:projects, :foo, :integer, default: 10, limit: 8)
``` ```
## Timestamp column type
By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information.
The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method.
Also Rails converts the `:datetime` data type to the `timestamp` one.
Example:
```ruby
# timestamps
create_table :users do |t|
t.timestamps
end
# add_timestamps
def up
add_timestamps :users
end
# :datetime
def up
add_column :users, :last_sign_in, :datetime
end
```
Instead of using these methods one should use the following methods to store timestamps with timezones:
* `add_timestamps_with_timezone`
* `timestamps_with_timezone`
This ensures all timestamps have a time zone specified. This in turn means existing timestamps won't
suddenly use a different timezone when the system's timezone changes. It also makes it very clear which
timezone was used in the first place.
## Testing ## Testing
Make sure that your migration works with MySQL and PostgreSQL with data. An Make sure that your migration works with MySQL and PostgreSQL with data. An
......
# Installing GitLab on Kubernetes # Installing GitLab on Kubernetes
> Officially supported cloud providers are Google Container Service and Azure Container Service. > Officially supported cloud providers are Google Container Service and Azure Container Service.
> Officially supported schedulers are Kubernetes and Terraform. > Officially supported schedulers are Kubernetes, Terraform and Tectonic.
The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is
to take advantage of the official GitLab Helm charts. [Helm] is a package to take advantage of the official GitLab Helm charts. [Helm] is a package
......
...@@ -34,8 +34,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -34,8 +34,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
page.within '.awards' do page.within '.awards' do
expect do expect do
page.find('.js-emoji-btn.active').click page.find('.js-emoji-btn.active').click
sleep 0.3 wait_for_requests
end.to change{ page.all(".award-control.js-emoji-btn").size }.from(3).to(2) end.to change { page.all(".award-control.js-emoji-btn").size }.from(3).to(2)
end end
end end
......
...@@ -804,7 +804,11 @@ module API ...@@ -804,7 +804,11 @@ module API
end end
class Image < Grape::Entity class Image < Grape::Entity
expose :name expose :name, :entrypoint
end
class Service < Image
expose :alias, :command
end end
class Artifacts < Grape::Entity class Artifacts < Grape::Entity
...@@ -848,7 +852,7 @@ module API ...@@ -848,7 +852,7 @@ module API
expose :variables expose :variables
expose :steps, using: Step expose :steps, using: Step
expose :image, using: Image expose :image, using: Image
expose :services, using: Image expose :services, using: Service
expose :artifacts, using: Artifacts expose :artifacts, using: Artifacts
expose :cache, using: Cache expose :cache, using: Cache
expose :credentials, using: Credentials expose :credentials, using: Credentials
......
...@@ -45,7 +45,21 @@ module Ci ...@@ -45,7 +45,21 @@ module Ci
expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? } expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? }
expose :options do |model| expose :options do |model|
model.options # This part ensures that output of old API is still the same after adding support
# for extended docker configuration options, used by new API
#
# I'm leaving this here, not in the model, because it should be removed at the same time
# when old API will be removed (planned for August 2017).
model.options.dup.tap do |options|
options[:image] = options[:image][:name] if options[:image].is_a?(Hash)
options[:services].map! do |service|
if service.is_a?(Hash)
service[:name]
else
service
end
end
end
end end
expose :timeout do |model| expose :timeout do |model|
......
...@@ -92,7 +92,7 @@ module Github ...@@ -92,7 +92,7 @@ module Github
end end
def fetch_wiki_repository def fetch_wiki_repository
wiki_url = "https://{options.fetch(:token)}@github.com/#{repo}.wiki.git" wiki_url = "https://#{options.fetch(:token)}@github.com/#{repo}.wiki.git"
wiki_path = "#{project.path_with_namespace}.wiki" wiki_path = "#{project.path_with_namespace}.wiki"
unless project.wiki.repository_exists? unless project.wiki.repository_exists?
......
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module Ci module Ci
module Build module Build
class Image class Image
attr_reader :name attr_reader :alias, :command, :entrypoint, :name
class << self class << self
def from_image(job) def from_image(job)
...@@ -21,7 +21,14 @@ module Gitlab ...@@ -21,7 +21,14 @@ module Gitlab
end end
def initialize(image) def initialize(image)
@name = image if image.is_a?(String)
@name = image
elsif image.is_a?(Hash)
@alias = image[:alias]
@command = image[:command]
@entrypoint = image[:entrypoint]
@name = image[:name]
end
end end
def valid? def valid?
......
...@@ -8,8 +8,36 @@ module Gitlab ...@@ -8,8 +8,36 @@ module Gitlab
class Image < Node class Image < Node
include Validatable include Validatable
ALLOWED_KEYS = %i[name entrypoint].freeze
validations do validations do
validates :config, type: String validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
validates :entrypoint, type: String, allow_nil: true
end
def hash?
@config.is_a?(Hash)
end
def string?
@config.is_a?(String)
end
def name
value[:name]
end
def entrypoint
value[:entrypoint]
end
def value
return { name: @config } if string?
return @config if hash?
{}
end end
end end
end end
......
module Gitlab
module Ci
class Config
module Entry
##
# Entry that represents a configuration of Docker service.
#
class Service < Image
include Validatable
ALLOWED_KEYS = %i[name entrypoint command alias].freeze
validations do
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
validates :entrypoint, type: String, allow_nil: true
validates :command, type: String, allow_nil: true
validates :alias, type: String, allow_nil: true
end
def alias
value[:alias]
end
def command
value[:command]
end
end
end
end
end
end
...@@ -9,7 +9,30 @@ module Gitlab ...@@ -9,7 +9,30 @@ module Gitlab
include Validatable include Validatable
validations do validations do
validates :config, array_of_strings: true validates :config, type: Array
end
def compose!(deps = nil)
super do
@entries = []
@config.each do |config|
@entries << Entry::Factory.new(Entry::Service)
.value(config || {})
.create!
end
@entries.each do |entry|
entry.compose!(deps)
end
end
end
def value
@entries.map(&:value)
end
def descendants
@entries
end end
end end
end end
......
...@@ -44,6 +44,14 @@ module Gitlab ...@@ -44,6 +44,14 @@ module Gitlab
end end
end end
class HashOrStringValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Hash) || value.is_a?(String)
record.errors.add(attribute, 'should be a hash or a string')
end
end
end
class KeyValidator < ActiveModel::EachValidator class KeyValidator < ActiveModel::EachValidator
include LegacyValidationHelpers include LegacyValidationHelpers
......
...@@ -3,6 +3,10 @@ module Gitlab ...@@ -3,6 +3,10 @@ module Gitlab
module Status module Status
module External module External
module Common module Common
def label
subject.description
end
def has_details? def has_details?
subject.target_url.present? && subject.target_url.present? &&
can?(user, :read_commit_status, subject) can?(user, :read_commit_status, subject)
......
module Gitlab module Gitlab
module Database module Database
module MigrationHelpers module MigrationHelpers
# Adds `created_at` and `updated_at` columns with timezone information.
#
# This method is an improved version of Rails' built-in method `add_timestamps`.
#
# Available options are:
# default - The default value for the column.
# null - When set to `true` the column will allow NULL values.
# The default is to not allow NULL values.
def add_timestamps_with_timezone(table_name, options = {})
options[:null] = false if options[:null].nil?
[:created_at, :updated_at].each do |column_name|
if options[:default] && transaction_open?
raise '`add_timestamps_with_timezone` with default value cannot be run inside a transaction. ' \
'You can disable transactions by calling `disable_ddl_transaction!` ' \
'in the body of your migration class'
end
# If default value is presented, use `add_column_with_default` method instead.
if options[:default]
add_column_with_default(
table_name,
column_name,
:datetime_with_timezone,
default: options[:default],
allow_null: options[:null]
)
else
add_column(table_name, column_name, :datetime_with_timezone, options)
end
end
end
# Creates a new index, concurrently when supported # Creates a new index, concurrently when supported
# #
# On PostgreSQL this method creates an index concurrently, on MySQL this # On PostgreSQL this method creates an index concurrently, on MySQL this
......
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that checks if 'add_timestamps' method is called with timezone information.
class AddTimestamps < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Do not use `add_timestamps`, use `add_timestamps_with_timezone` instead'.freeze
# Check methods.
def on_send(node)
return unless in_migration?(node)
add_offense(node, :selector) if method_name(node) == :add_timestamps
end
def method_name(node)
node.children[1]
end
end
end
end
end
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that checks if datetime data type is added with timezone information.
class Datetime < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Do not use the `datetime` data type, use `datetime_with_timezone` instead'.freeze
# Check methods in table creation.
def on_def(node)
return unless in_migration?(node)
node.each_descendant(:send) do |send_node|
add_offense(send_node, :selector) if method_name(send_node) == :datetime
end
end
# Check methods.
def on_send(node)
return unless in_migration?(node)
node.each_descendant do |descendant|
add_offense(node, :expression) if descendant.type == :sym && descendant.children.last == :datetime
end
end
def method_name(node)
node.children[1]
end
end
end
end
end
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that checks if 'timestamps' method is called with timezone information.
class Timestamps < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Do not use `timestamps`, use `timestamps_with_timezone` instead'.freeze
# Check methods in table creation.
def on_def(node)
return unless in_migration?(node)
node.each_descendant(:send) do |send_node|
add_offense(send_node, :selector) if method_name(send_node) == :timestamps
end
end
def method_name(node)
node.children[1]
end
end
end
end
end
...@@ -8,7 +8,10 @@ require_relative 'cop/migration/add_column_with_default_to_large_table' ...@@ -8,7 +8,10 @@ require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key' require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index' require_relative 'cop/migration/add_index'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
require_relative 'cop/migration/remove_concurrent_index' require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index' require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default' require_relative 'cop/migration/reversible_add_column_with_default'
require_relative 'cop/migration/timestamps'
require_relative 'cop/migration/update_column_in_batches' require_relative 'cop/migration/update_column_in_batches'
...@@ -5,9 +5,12 @@ describe Projects::PipelinesController do ...@@ -5,9 +5,12 @@ describe Projects::PipelinesController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public) }
let(:feature) { ProjectFeature::DISABLED }
before do before do
project.add_developer(user) project.add_developer(user)
project.project_feature.update(
builds_access_level: feature)
sign_in(user) sign_in(user)
end end
...@@ -153,16 +156,26 @@ describe Projects::PipelinesController do ...@@ -153,16 +156,26 @@ describe Projects::PipelinesController do
format: :json format: :json
end end
it 'retries a pipeline without returning any content' do context 'when builds are enabled' do
expect(response).to have_http_status(:no_content) let(:feature) { ProjectFeature::ENABLED }
expect(build.reload).to be_retried
it 'retries a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(build.reload).to be_retried
end
end
context 'when builds are disabled' do
it 'fails to retry pipeline' do
expect(response).to have_http_status(:not_found)
end
end end
end end
describe 'POST cancel.json' do describe 'POST cancel.json' do
let!(:pipeline) { create(:ci_pipeline, project: project) } let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build) { create(:ci_build, :running, pipeline: pipeline) } let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
before do before do
post :cancel, namespace_id: project.namespace, post :cancel, namespace_id: project.namespace,
project_id: project, project_id: project,
...@@ -170,9 +183,19 @@ describe Projects::PipelinesController do ...@@ -170,9 +183,19 @@ describe Projects::PipelinesController do
format: :json format: :json
end end
it 'cancels a pipeline without returning any content' do context 'when builds are enabled' do
expect(response).to have_http_status(:no_content) let(:feature) { ProjectFeature::ENABLED }
expect(pipeline.reload).to be_canceled
it 'cancels a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(pipeline.reload).to be_canceled
end
end
context 'when builds are disabled' do
it 'fails to retry pipeline' do
expect(response).to have_http_status(:not_found)
end
end end
end end
end end
...@@ -194,8 +194,8 @@ FactoryGirl.define do ...@@ -194,8 +194,8 @@ FactoryGirl.define do
trait :extended_options do trait :extended_options do
options do options do
{ {
image: 'ruby:2.1', image: { name: 'ruby:2.1', entrypoint: '/bin/sh' },
services: ['postgres'], services: ['postgres', { name: 'docker:dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
after_script: %w(ls date), after_script: %w(ls date),
artifacts: { artifacts: {
name: 'artifacts_file', name: 'artifacts_file',
......
...@@ -68,9 +68,12 @@ describe 'Edit Project Settings', feature: true do ...@@ -68,9 +68,12 @@ describe 'Edit Project Settings', feature: true do
end end
describe 'project features visibility pages' do describe 'project features visibility pages' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:job) { create(:ci_build, pipeline: pipeline) }
let(:tools) do let(:tools) do
{ {
builds: namespace_project_pipelines_path(project.namespace, project), builds: namespace_project_job_path(project.namespace, project, job),
issues: namespace_project_issues_path(project.namespace, project), issues: namespace_project_issues_path(project.namespace, project),
wiki: namespace_project_wiki_path(project.namespace, project, :home), wiki: namespace_project_wiki_path(project.namespace, project, :home),
snippets: namespace_project_snippets_path(project.namespace, project), snippets: namespace_project_snippets_path(project.namespace, project),
......
...@@ -127,7 +127,7 @@ feature 'Pipeline Schedules', :feature do ...@@ -127,7 +127,7 @@ feature 'Pipeline Schedules', :feature do
end end
it 'shows the pipeline schedule with default ref' do it 'shows the pipeline schedule with default ref' do
page.within('.git-revision-dropdown-toggle') do page.within('.js-target-branch-dropdown') do
expect(first('.dropdown-toggle-text').text).to eq('master') expect(first('.dropdown-toggle-text').text).to eq('master')
end end
end end
......
...@@ -300,4 +300,37 @@ describe ProjectsHelper do ...@@ -300,4 +300,37 @@ describe ProjectsHelper do
expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private') expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private')
end end
end end
describe '#get_project_nav_tabs' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
before do
allow(helper).to receive(:can?) { true }
end
subject do
helper.send(:get_project_nav_tabs, project, user)
end
context 'when builds feature is enabled' do
before do
allow(project).to receive(:builds_enabled?).and_return(true)
end
it "does include pipelines tab" do
is_expected.to include(:pipelines)
end
end
context 'when builds feature is disabled' do
before do
allow(project).to receive(:builds_enabled?).and_return(false)
end
it "do not include pipelines tab" do
is_expected.not_to include(:pipelines)
end
end
end
end end
...@@ -22,7 +22,7 @@ describe('Time ago with tooltip component', () => { ...@@ -22,7 +22,7 @@ describe('Time ago with tooltip component', () => {
}).$mount(); }).$mount();
expect(vm.$el.tagName).toEqual('TIME'); expect(vm.$el.tagName).toEqual('TIME');
expect(vm.$el.classList.contains('js-timeago')).toEqual(true); expect(vm.$el.classList.contains('js-vue-timeago')).toEqual(true);
expect( expect(
vm.$el.getAttribute('data-original-title'), vm.$el.getAttribute('data-original-title'),
).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z')); ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
...@@ -44,17 +44,6 @@ describe('Time ago with tooltip component', () => { ...@@ -44,17 +44,6 @@ describe('Time ago with tooltip component', () => {
expect(vm.$el.getAttribute('data-placement')).toEqual('bottom'); expect(vm.$el.getAttribute('data-placement')).toEqual('bottom');
}); });
it('should render short format class', () => {
vm = new TimeagoTooltip({
propsData: {
time: '2017-05-08T14:57:39.781Z',
shortFormat: true,
},
}).$mount();
expect(vm.$el.classList.contains('js-short-timeago')).toEqual(true);
});
it('should render provided html class', () => { it('should render provided html class', () => {
vm = new TimeagoTooltip({ vm = new TimeagoTooltip({
propsData: { propsData: {
......
...@@ -10,12 +10,28 @@ describe Gitlab::Ci::Build::Image do ...@@ -10,12 +10,28 @@ describe Gitlab::Ci::Build::Image do
let(:image_name) { 'ruby:2.1' } let(:image_name) { 'ruby:2.1' }
let(:job) { create(:ci_build, options: { image: image_name } ) } let(:job) { create(:ci_build, options: { image: image_name } ) }
it 'fabricates an object of the proper class' do context 'when image is defined as string' do
is_expected.to be_kind_of(described_class) it 'fabricates an object of the proper class' do
is_expected.to be_kind_of(described_class)
end
it 'populates fabricated object with the proper name attribute' do
expect(subject.name).to eq(image_name)
end
end end
it 'populates fabricated object with the proper name attribute' do context 'when image is defined as hash' do
expect(subject.name).to eq(image_name) let(:entrypoint) { '/bin/sh' }
let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint } } ) }
it 'fabricates an object of the proper class' do
is_expected.to be_kind_of(described_class)
end
it 'populates fabricated object with the proper attributes' do
expect(subject.name).to eq(image_name)
expect(subject.entrypoint).to eq(entrypoint)
end
end end
context 'when image name is empty' do context 'when image name is empty' do
...@@ -41,10 +57,39 @@ describe Gitlab::Ci::Build::Image do ...@@ -41,10 +57,39 @@ describe Gitlab::Ci::Build::Image do
let(:service_image_name) { 'postgres' } let(:service_image_name) { 'postgres' }
let(:job) { create(:ci_build, options: { services: [service_image_name] }) } let(:job) { create(:ci_build, options: { services: [service_image_name] }) }
it 'fabricates an non-empty array of objects' do context 'when service is defined as string' do
is_expected.to be_kind_of(Array) it 'fabricates an non-empty array of objects' do
is_expected.not_to be_empty is_expected.to be_kind_of(Array)
expect(subject.first.name).to eq(service_image_name) is_expected.not_to be_empty
end
it 'populates fabricated objects with the proper name attributes' do
expect(subject.first).to be_kind_of(described_class)
expect(subject.first.name).to eq(service_image_name)
end
end
context 'when service is defined as hash' do
let(:service_entrypoint) { '/bin/sh' }
let(:service_alias) { 'db' }
let(:service_command) { 'sleep 30' }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
alias: service_alias, command: service_command }] })
end
it 'fabricates an non-empty array of objects' do
is_expected.to be_kind_of(Array)
is_expected.not_to be_empty
expect(subject.first).to be_kind_of(described_class)
end
it 'populates fabricated objects with the proper attributes' do
expect(subject.first.name).to eq(service_image_name)
expect(subject.first.entrypoint).to eq(service_entrypoint)
expect(subject.first.alias).to eq(service_alias)
expect(subject.first.command).to eq(service_command)
end
end end
context 'when service image name is empty' do context 'when service image name is empty' do
......
...@@ -95,13 +95,13 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -95,13 +95,13 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#image_value' do describe '#image_value' do
it 'returns valid image' do it 'returns valid image' do
expect(global.image_value).to eq 'ruby:2.2' expect(global.image_value).to eq(name: 'ruby:2.2')
end end
end end
describe '#services_value' do describe '#services_value' do
it 'returns array of services' do it 'returns array of services' do
expect(global.services_value).to eq ['postgres:9.1', 'mysql:5.5'] expect(global.services_value).to eq [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }]
end end
end end
...@@ -150,8 +150,8 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -150,8 +150,8 @@ describe Gitlab::Ci::Config::Entry::Global do
script: %w[rspec ls], script: %w[rspec ls],
before_script: %w(ls pwd), before_script: %w(ls pwd),
commands: "ls\npwd\nrspec\nls", commands: "ls\npwd\nrspec\nls",
image: 'ruby:2.2', image: { name: 'ruby:2.2' },
services: ['postgres:9.1', 'mysql:5.5'], services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: { 'VAR' => 'value' }, variables: { 'VAR' => 'value' },
...@@ -161,8 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do ...@@ -161,8 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do
before_script: [], before_script: [],
script: %w[spinach], script: %w[spinach],
commands: 'spinach', commands: 'spinach',
image: 'ruby:2.2', image: { name: 'ruby:2.2' },
services: ['postgres:9.1', 'mysql:5.5'], services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test', stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] }, cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {}, variables: {},
......
...@@ -3,43 +3,104 @@ require 'spec_helper' ...@@ -3,43 +3,104 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Image do describe Gitlab::Ci::Config::Entry::Image do
let(:entry) { described_class.new(config) } let(:entry) { described_class.new(config) }
describe 'validation' do context 'when configuration is a string' do
context 'when entry config value is correct' do let(:config) { 'ruby:2.2' }
let(:config) { 'ruby:2.2' }
describe '#value' do describe '#value' do
it 'returns image string' do it 'returns image hash' do
expect(entry.value).to eq 'ruby:2.2' expect(entry.value).to eq({ name: 'ruby:2.2' })
end
end end
end
describe '#errors' do
it 'does not append errors' do
expect(entry.errors).to be_empty
end
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
describe '#image' do
it "returns image's name" do
expect(entry.name).to eq 'ruby:2.2'
end
end
describe '#errors' do describe '#entrypoint' do
it 'does not append errors' do it "returns image's entrypoint" do
expect(entry.errors).to be_empty expect(entry.entrypoint).to be_nil
end
end end
end
end
describe '#valid?' do context 'when configuration is a hash' do
it 'is valid' do let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } }
expect(entry).to be_valid
end describe '#value' do
it 'returns image hash' do
expect(entry.value).to eq(config)
end end
end end
context 'when entry value is not correct' do describe '#errors' do
let(:config) { ['ruby:2.2'] } it 'does not append errors' do
expect(entry.errors).to be_empty
end
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
describe '#errors' do describe '#image' do
it 'saves errors' do it "returns image's name" do
expect(entry.errors) expect(entry.name).to eq 'ruby:2.2'
.to include 'image config should be a string'
end
end end
end
describe '#entrypoint' do
it "returns image's entrypoint" do
expect(entry.entrypoint).to eq '/bin/sh'
end
end
end
context 'when entry value is not correct' do
let(:config) { ['ruby:2.2'] }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'image config should be a hash or a string'
end
end
describe '#valid?' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
context 'when unexpected key is specified' do
let(:config) { { name: 'ruby:2.2', non_existing: 'test' } }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'image config contains unknown keys: non_existing'
end
end
describe '#valid?' do describe '#valid?' do
it 'is not valid' do it 'is not valid' do
expect(entry).not_to be_valid expect(entry).not_to be_valid
end
end end
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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