Commit e5b05fe8 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch 'master' into artifacts-from-ref-and-build-name-api

* master: (261 commits)
  Add link to user profile to commit avatar (!5163)
  Refactor service settings view
  Fix a problem with processing a pipeline where stage only has manual actions
  A CHANGELOG entry
  Don't show other actions of the same name
  Use limit parameter rather than hardcoded value
  Remove icons from explore nav
  Change how we style redirect_to
  Use flash[:notice] only
  Create PipelinesSettingsController for showing settings page
  Fix a few nitpicks
  Allow to disable user request access to groups/projects
  Enable Style/MultilineTernaryOperator rubocop cop
  Fix review comments
  Update routes
  Update CHANGELOG
  Improve implementation of variables
  Log cron_jobs configuration instead of raising exception
  Added checks for migration downtime
  Ensure to_json methods take optional argument
  ...
parents 122b046b b4717017
...@@ -148,7 +148,7 @@ spinach 9 10: *spinach-knapsack ...@@ -148,7 +148,7 @@ spinach 9 10: *spinach-knapsack
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23 .spinach-knapsack-ruby23: &spinach-knapsack-ruby23
<<: *spinach-knapsack <<: *spinach-knapsack
<<: *ruby-23 <<: *ruby-23
rspec 0 20 ruby23: *rspec-knapsack-ruby23 rspec 0 20 ruby23: *rspec-knapsack-ruby23
rspec 1 20 ruby23: *rspec-knapsack-ruby23 rspec 1 20 ruby23: *rspec-knapsack-ruby23
rspec 2 20 ruby23: *rspec-knapsack-ruby23 rspec 2 20 ruby23: *rspec-knapsack-ruby23
...@@ -196,6 +196,7 @@ rake flog: *exec ...@@ -196,6 +196,7 @@ rake flog: *exec
rake flay: *exec rake flay: *exec
rake db:migrate:reset: *exec rake db:migrate:reset: *exec
license_finder: *exec license_finder: *exec
rake downtime_check: *exec
bundler:audit: bundler:audit:
stage: test stage: test
......
...@@ -291,6 +291,10 @@ Style/MultilineMethodDefinitionBraceLayout: ...@@ -291,6 +291,10 @@ Style/MultilineMethodDefinitionBraceLayout:
Style/MultilineOperationIndentation: Style/MultilineOperationIndentation:
Enabled: false Enabled: false
# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
Style/MultilineTernaryOperator:
Enabled: true
# Favor unless over if for negative conditions (or control flow or). # Favor unless over if for negative conditions (or control flow or).
Style/NegatedIf: Style/NegatedIf:
Enabled: true Enabled: true
......
...@@ -226,10 +226,6 @@ Style/LineEndConcatenation: ...@@ -226,10 +226,6 @@ Style/LineEndConcatenation:
Style/MethodCallParentheses: Style/MethodCallParentheses:
Enabled: false Enabled: false
# Offense count: 3
Style/MultilineTernaryOperator:
Enabled: false
# Offense count: 62 # Offense count: 62
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/MutableConstant: Style/MutableConstant:
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased) v 8.11.0 (unreleased)
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Limit git rev-list output count to one in forced push check
v 8.10.0 (unreleased) v 8.10.0 (unreleased)
- Fix profile activity heatmap to show correct day name (eanplatter) - Fix profile activity heatmap to show correct day name (eanplatter)
- Speed up ExternalWikiHelper#get_project_wiki_path
- Expose {should,force}_remove_source_branch (Ben Boeckel) - Expose {should,force}_remove_source_branch (Ben Boeckel)
- Add the functionality to be able to rename a file. !5049 (tiagonbotelho)
- Disable PostgreSQL statement timeout during migrations - Disable PostgreSQL statement timeout during migrations
- Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho) - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho)
- Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Fix commit builds API, return all builds for all pipelines for given commit. !4849
- Replace Haml with Hamlit to make view rendering faster. !3666 - Replace Haml with Hamlit to make view rendering faster. !3666
- Refresh the branch cache after `git gc` runs - Refresh the branch cache after `git gc` runs
- Allow to disable request access button on projects/groups
- Refactor repository paths handling to allow multiple git mount points - Refactor repository paths handling to allow multiple git mount points
- Optimize system note visibility checking by memoizing the visible reference count !5070 - Optimize system note visibility checking by memoizing the visible reference count !5070
- Add Application Setting to configure default Repository Path for new projects - Add Application Setting to configure default Repository Path for new projects
- Delete award emoji when deleting a user - Delete award emoji when deleting a user
- Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell) - Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell)
- Add an API for downloading latest successful build from a particular branch or tag !5347 - Add an API for downloading latest successful build from a particular branch or tag !5347
- Add link to profile to commit avatar !5163 (winniehell)
- Wrap code blocks on Activies and Todos page. !4783 (winniehell) - Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Align flash messages with left side of page content !4959 (winniehell) - Align flash messages with left side of page content !4959 (winniehell)
- Display tooltip for "Copy to Clipboard" button !5164 (winniehell) - Display tooltip for "Copy to Clipboard" button !5164 (winniehell)
...@@ -25,6 +30,9 @@ v 8.10.0 (unreleased) ...@@ -25,6 +30,9 @@ v 8.10.0 (unreleased)
- Escape file extension when parsing search results !5141 (winniehell) - Escape file extension when parsing search results !5141 (winniehell)
- Apply the trusted_proxies config to the rack request object for use with rack_attack - Apply the trusted_proxies config to the rack request object for use with rack_attack
- Upgrade to Rails 4.2.7. !5236 - Upgrade to Rails 4.2.7. !5236
- Extend exposed environment variables for CI builds
- Allow to pull code with deploy key from public projects
- Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts)
- Add Sidekiq queue duration to transaction metrics. - Add Sidekiq queue duration to transaction metrics.
- Add a new column `artifacts_size` to table `ci_builds` !4964 - Add a new column `artifacts_size` to table `ci_builds` !4964
- Let Workhorse serve format-patch diffs - Let Workhorse serve format-patch diffs
...@@ -33,6 +41,7 @@ v 8.10.0 (unreleased) ...@@ -33,6 +41,7 @@ v 8.10.0 (unreleased)
- Added day name to contribution calendar tooltips - Added day name to contribution calendar tooltips
- Make images fit to the size of the viewport !4810 - Make images fit to the size of the viewport !4810
- Fix check for New Branch button on Issue page !4630 (winniehell) - Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix GFM autocomplete not working on wiki pages
- Fix MR-auto-close text added to description. !4836 - Fix MR-auto-close text added to description. !4836
- Support U2F devices in Firefox. !5177 - Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
...@@ -50,6 +59,7 @@ v 8.10.0 (unreleased) ...@@ -50,6 +59,7 @@ v 8.10.0 (unreleased)
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020 - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
- Updated project header design - Updated project header design
- Issuable collapsed assignee tooltip is now the users name - Issuable collapsed assignee tooltip is now the users name
- Fix compare view not changing code view rendering style
- Exclude email check from the standard health check - Exclude email check from the standard health check
- Updated layout for Projects, Groups, Users on Admin area !4424 - Updated layout for Projects, Groups, Users on Admin area !4424
- Fix changing issue state columns in milestone view - Fix changing issue state columns in milestone view
...@@ -89,6 +99,7 @@ v 8.10.0 (unreleased) ...@@ -89,6 +99,7 @@ v 8.10.0 (unreleased)
- Avoid to retrieve MR closes_issues as much as possible. - Avoid to retrieve MR closes_issues as much as possible.
- Add API endpoint for a group issues !4520 (mahcsig) - Add API endpoint for a group issues !4520 (mahcsig)
- Add Bugzilla integration !4930 (iamtjg) - Add Bugzilla integration !4930 (iamtjg)
- Fix new snippet style bug (elliotec)
- Instrument Rinku usage - Instrument Rinku usage
- Be explicit to define merge request discussion variables - Be explicit to define merge request discussion variables
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
...@@ -99,6 +110,7 @@ v 8.10.0 (unreleased) ...@@ -99,6 +110,7 @@ v 8.10.0 (unreleased)
- Don't render discussion notes when requesting diff tab through AJAX - Don't render discussion notes when requesting diff tab through AJAX
- Add basic system information like memory and disk usage to the admin panel - Add basic system information like memory and disk usage to the admin panel
- Don't garbage collect commits that have related DB records like comments - Don't garbage collect commits that have related DB records like comments
- Allow to setup event by channel on slack service
- More descriptive message for git hooks and file locks - More descriptive message for git hooks and file locks
- Aliases of award emoji should be stored as original name. !5060 (dixpac) - Aliases of award emoji should be stored as original name. !5060 (dixpac)
- Handle custom Git hook result in GitLab UI - Handle custom Git hook result in GitLab UI
...@@ -124,13 +136,17 @@ v 8.10.0 (unreleased) ...@@ -124,13 +136,17 @@ v 8.10.0 (unreleased)
- Fix last update timestamp on issues not preserved on gitlab.com and project imports - Fix last update timestamp on issues not preserved on gitlab.com and project imports
- Fix issues importing projects from EE to CE - Fix issues importing projects from EE to CE
- Fix creating group with space in group path - Fix creating group with space in group path
- Improve cron_jobs loading error messages !5318 - Improve cron_jobs loading error messages !5318 / !5360
- Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska)
- Limit the number of retries on error to 3 for exporting projects - Limit the number of retries on error to 3 for exporting projects
- Allow empty repositories on project import/export - Allow empty repositories on project import/export
- Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska) - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska)
- Allow bulk (un)subscription from issues in issue index - Allow bulk (un)subscription from issues in issue index
- Fix MR diff encoding issues exporting GitLab projects - Fix MR diff encoding issues exporting GitLab projects
- Move builds settings out of project settings and rename Pipelines
- Add builds badge to Pipelines settings page
- Export and import avatar as part of project import/export
- Fix migration corrupting import data for old version upgrades
v 8.9.6 v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154 - Fix importing of events under notes for GitLab projects. !5154
......
...@@ -578,7 +578,7 @@ GEM ...@@ -578,7 +578,7 @@ GEM
railties (>= 4.2.0, < 5.1) railties (>= 4.2.0, < 5.1)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (2.0.3) rouge (2.0.5)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
......
...@@ -232,7 +232,9 @@ ...@@ -232,7 +232,9 @@
.nav-block { .nav-block {
.controls { .controls {
float: right; float: right;
margin-top: 11px; margin-top: 8px;
padding-bottom: 7px;
border-bottom: 1px solid $border-color;
} }
} }
......
...@@ -49,6 +49,17 @@ ...@@ -49,6 +49,17 @@
border-color: $border-dark; border-color: $border-dark;
color: $color; color: $color;
} }
svg {
path {
fill: $color;
}
use {
stroke: $color;
}
}
} }
@mixin btn-green { @mixin btn-green {
...@@ -173,6 +184,13 @@ ...@@ -173,6 +184,13 @@
.caret { .caret {
margin-left: 5px; margin-left: 5px;
} }
svg {
height: 15px;
width: auto;
position: relative;
top: 2px;
}
} }
.btn-lg { .btn-lg {
......
...@@ -198,6 +198,10 @@ header.header-pinned-nav { ...@@ -198,6 +198,10 @@ header.header-pinned-nav {
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
cursor: pointer; cursor: pointer;
.btn {
background-color: $gray-light;
}
} }
} }
......
...@@ -122,7 +122,8 @@ ...@@ -122,7 +122,8 @@
button { button {
float: right; float: right;
padding: 3px 5px; padding: 1px 5px;
background-color: $gray-light;
} }
} }
......
...@@ -78,6 +78,14 @@ form.edit-issue { ...@@ -78,6 +78,14 @@ form.edit-issue {
} }
} }
.merge-request-ci-status {
svg {
margin-right: 4px;
position: relative;
top: 1px;
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.issue-btn-group { .issue-btn-group {
width: 100%; width: 100%;
......
...@@ -60,8 +60,10 @@ ...@@ -60,8 +60,10 @@
.ci_widget { .ci_widget {
border-bottom: 1px solid #eef0f2; border-bottom: 1px solid #eef0f2;
i { svg {
margin-right: 4px; margin-right: 4px;
position: relative;
top: 1px;
} }
&.ci-success { &.ci-success {
...@@ -196,6 +198,16 @@ ...@@ -196,6 +198,16 @@
.merge-request-title { .merge-request-title {
margin-bottom: 2px; margin-bottom: 2px;
.ci-status-link {
svg {
height: 16px;
width: 16px;
position: relative;
top: 3px;
}
}
} }
} }
......
...@@ -49,6 +49,14 @@ ...@@ -49,6 +49,14 @@
.commit-link { .commit-link {
.ci-status {
svg {
top: 1px;
margin-right: 0;
}
}
a:hover { a:hover {
text-decoration: none; text-decoration: none;
} }
...@@ -124,6 +132,15 @@ ...@@ -124,6 +132,15 @@
} }
} }
.stage-cell {
svg {
height: 18px;
width: 18px;
vertical-align: middle;
}
}
.duration, .duration,
.finished-at { .finished-at {
color: $table-text-gray; color: $table-text-gray;
......
...@@ -129,6 +129,17 @@ ...@@ -129,6 +129,17 @@
color: $layout-link-gray; color: $layout-link-gray;
} }
svg {
path {
fill: $layout-link-gray;
}
use {
stroke: $layout-link-gray;
}
}
.fa-caret-down { .fa-caret-down {
margin-left: 3px; margin-left: 3px;
} }
...@@ -322,18 +333,53 @@ a.deploy-project-label { ...@@ -322,18 +333,53 @@ a.deploy-project-label {
} }
.fork-namespaces { .fork-namespaces {
.fork-thumbnail { .row {
text-align: center; -webkit-flex-wrap: wrap;
margin-bottom: $gl-padding; display: -webkit-flex;
display: flex;
.caption { flex-wrap: wrap;
padding: $gl-padding 0; justify-content: flex-start;
min-height: 30px;
} .fork-thumbnail {
@include border-radius($border-radius-base);
background-color: $white-light;
border: 1px solid $border-white-light;
height: 202px;
margin: $gl-padding;
text-align: center;
width: 169px;
&:hover, &.forked {
background-color: $row-hover;
border-color: $row-hover-border;
}
.no-avatar {
width: 100px;
height: 100px;
background-color: $gray-light;
border: 1px solid $gray-dark;
margin: 0 auto;
@include border-radius(50%);
i {
font-size: 100px;
color: $gray-dark;
}
}
a {
display: block;
width: 100%;
height: 100%;
padding-top: $gl-padding;
color: $gl-gray;
.caption {
min-height: 30px;
padding: $gl-padding 0;
}
}
img { img {
@include border-radius(50%); @include border-radius(50%);
max-width: 100px; max-width: 100px;
}
} }
} }
} }
...@@ -486,6 +532,11 @@ pre.light-well { ...@@ -486,6 +532,11 @@ pre.light-well {
> span { > span {
margin-left: 10px; margin-left: 10px;
} }
svg {
position: relative;
top: 2px;
}
} }
} }
......
...@@ -41,6 +41,14 @@ ...@@ -41,6 +41,14 @@
color: $blue-normal; color: $blue-normal;
border-color: $blue-normal; border-color: $blue-normal;
} }
svg {
height: 13px;
width: 13px;
position: relative;
top: 1px;
margin: 0 3px;
}
} }
.ci-status-icon-success { .ci-status-icon-success {
......
.tag-buttons {
line-height: 40px;
.btn:not(.dropdown-toggle) {
margin-left: 10px;
}
}
...@@ -60,6 +60,6 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -60,6 +60,6 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level) params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled)
end end
end end
class Admin::ServicesController < Admin::ApplicationController class Admin::ServicesController < Admin::ApplicationController
include ServiceParams
before_action :service, only: [:edit, :update] before_action :service, only: [:edit, :update]
def index def index
...@@ -13,7 +15,7 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -13,7 +15,7 @@ class Admin::ServicesController < Admin::ApplicationController
end end
def update def update
if service.update_attributes(application_services_params[:service]) if service.update_attributes(service_params[:service])
redirect_to admin_application_settings_services_path, redirect_to admin_application_settings_services_path,
notice: 'Application settings saved successfully' notice: 'Application settings saved successfully'
else else
...@@ -37,15 +39,4 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -37,15 +39,4 @@ class Admin::ServicesController < Admin::ApplicationController
def service def service
@service ||= Service.where(id: params[:id], template: true).first @service ||= Service.where(id: params[:id], template: true).first
end end
def application_services_params
application_services_params = params.permit(:id,
service: Projects::ServicesController::ALLOWED_PARAMS)
if application_services_params[:service].is_a?(Hash)
Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
application_services_params[:service].delete(param) if application_services_params[:service][param].blank?
end
end
application_services_params
end
end end
...@@ -7,7 +7,8 @@ module CreatesCommit ...@@ -7,7 +7,8 @@ module CreatesCommit
commit_params = @commit_params.merge( commit_params = @commit_params.merge(
source_project: @project, source_project: @project,
source_branch: @ref, source_branch: @ref,
target_branch: @target_branch target_branch: @target_branch,
previous_path: @previous_path
) )
result = service.new(@tree_edit_project, current_user, commit_params).execute result = service.new(@tree_edit_project, current_user, commit_params).execute
......
module ServiceParams
extend ActiveSupport::Concern
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :drone_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :build_events, :wiki_page_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
:jira_issue_transition_id]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
def service_params
dynamic_params = []
dynamic_params.concat(@service.event_channel_names)
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
if service_params[:service].is_a?(Hash)
FILTER_BLANK_PARAMS.each do |param|
service_params[:service].delete(param) if service_params[:service][param].blank?
end
end
service_params
end
end
...@@ -121,7 +121,7 @@ class GroupsController < Groups::ApplicationController ...@@ -121,7 +121,7 @@ class GroupsController < Groups::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock) params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled)
end end
def load_events def load_events
......
...@@ -3,11 +3,6 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -3,11 +3,6 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:index] before_action :authorize_admin_project!, only: [:index]
before_action :no_cache_headers, except: [:index] before_action :no_cache_headers, except: [:index]
def index
@ref = params[:ref] || @project.default_branch || 'master'
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
end
def build def build
badge = Gitlab::Badge::Build.new(project, params[:ref]) badge = Gitlab::Badge::Build.new(project, params[:ref])
......
...@@ -38,6 +38,12 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -38,6 +38,12 @@ class Projects::BlobController < Projects::ApplicationController
end end
def update def update
if params[:file_path].present?
@previous_path = @path
@path = params[:file_path]
@commit_params[:file_path] = @path
end
after_edit_path = after_edit_path =
if from_merge_request && @target_branch == @ref if from_merge_request && @target_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
......
...@@ -15,6 +15,7 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -15,6 +15,7 @@ class Projects::CompareController < Projects::ApplicationController
end end
def show def show
apply_diff_view_cookie!
end end
def diff_for_path def diff_for_path
......
class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
@ref = params[:ref] || @project.default_branch || 'master'
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
end
def update
if @project.update_attributes(update_params)
flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated."
redirect_to namespace_project_pipelines_settings_path(@project.namespace, @project)
else
render 'index'
end
end
private
def create_params
params.require(:pipeline).permit(:ref)
end
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds
)
end
end
...@@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController
when "graphs_commits" when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id) commits_namespace_project_graph_path(@project.namespace, @project, @id)
when "badges" when "badges"
namespace_project_badges_path(@project.namespace, @project, ref: @id) namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id)
else else
namespace_project_commits_path(@project.namespace, @project, @id) namespace_project_commits_path(@project.namespace, @project, @id)
end end
......
class Projects::ServicesController < Projects::ApplicationController class Projects::ServicesController < Projects::ApplicationController
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain, include ServiceParams
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :drone_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :build_events, :wiki_page_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
:jira_issue_transition_id]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
...@@ -33,7 +18,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -33,7 +18,7 @@ class Projects::ServicesController < Projects::ApplicationController
end end
def update def update
if @service.update_attributes(service_params) if @service.update_attributes(service_params[:service])
redirect_to( redirect_to(
edit_namespace_project_service_path(@project.namespace, @project, edit_namespace_project_service_path(@project.namespace, @project,
@service.to_param, notice: @service.to_param, notice:
...@@ -64,12 +49,4 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -64,12 +49,4 @@ class Projects::ServicesController < Projects::ApplicationController
def service def service
@service ||= @project.services.find { |service| service.to_param == params[:id] } @service ||= @project.services.find { |service| service.to_param == params[:id] }
end end
def service_params
service_params = params.require(:service).permit(ALLOWED_PARAMS)
FILTER_BLANK_PARAMS.each do |param|
service_params.delete(param) if service_params[param].blank?
end
service_params
end
end end
...@@ -296,7 +296,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -296,7 +296,7 @@ class ProjectsController < Projects::ApplicationController
:issues_tracker_id, :default_branch, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :only_allow_merge_if_build_succeeds :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled
) )
end end
......
module AvatarsHelper
def author_avatar(commit_or_event, options = {})
user_avatar(options.merge({
user: commit_or_event.author,
user_name: commit_or_event.author_name,
user_email: commit_or_event.author_email,
}))
end
private
def user_avatar(options = {})
avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name]
avatar = image_tag(
avatar_icon(options[:user] || options[:user_email], avatar_size),
class: "avatar has-tooltip hidden-xs s#{avatar_size}",
alt: "#{user_name}'s avatar",
title: user_name
)
if options[:user]
link_to(avatar, user_path(options[:user]))
elsif options[:user_email]
mail_to(options[:user_email], avatar)
end
end
end
...@@ -26,18 +26,20 @@ module CiStatusHelper ...@@ -26,18 +26,20 @@ module CiStatusHelper
icon_name = icon_name =
case status case status
when 'success' when 'success'
'check' 'icon_status_success'
when 'success_with_warnings'
'icon_status_warning'
when 'failed' when 'failed'
'close' 'icon_status_failed'
when 'pending' when 'pending'
'clock-o' 'icon_status_pending'
when 'running' when 'running'
'spinner' 'icon_status_running'
else else
'circle' 'icon_status_cancel'
end end
icon(icon_name + ' fw') custom_icon(icon_name)
end end
def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '') def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
......
...@@ -16,16 +16,6 @@ module CommitsHelper ...@@ -16,16 +16,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer)) commit_person_link(commit, options.merge(source: :committer))
end end
def commit_author_avatar(commit, options = {})
options = options.merge(source: :author)
user = commit.send(options[:source])
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
person_email = user.try(:email) || source_email
image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]} hidden-xs", width: options[:size], alt: "")
end
def image_diff_class(diff) def image_diff_class(diff)
if diff.deleted_file if diff.deleted_file
"deleted" "deleted"
......
module ExternalWikiHelper module ExternalWikiHelper
def get_project_wiki_path(project) def get_project_wiki_path(project)
external_wiki_service = project.services. external_wiki_service = project.external_wiki
find { |service| service.to_param == 'external_wiki' } if external_wiki_service
if external_wiki_service.present? && external_wiki_service.active?
external_wiki_service.properties['external_wiki_url'] external_wiki_service.properties['external_wiki_url']
else else
namespace_project_wiki_path(project.namespace, project, :home) namespace_project_wiki_path(project.namespace, project, :home)
......
module ServicesHelper
def service_event_description(event)
case event
when "push"
"Event will be triggered by a push to the repository"
when "tag_push"
"Event will be triggered when a new tag is pushed to the repository"
when "note"
"Event will be triggered when someone adds a comment"
when "issue"
"Event will be triggered when an issue is created/updated/merged"
when "merge_request"
"Event will be triggered when a merge request is created/updated/merged"
when "build"
"Event will be triggered when a build status changes"
when "wiki_page"
"Event will be triggered when a wiki page is created/updated"
end
end
def service_event_field_name(event)
event = event.pluralize if %w[merge_request issue].include?(event)
"#{event}_events"
end
end
module TimeHelper module TimeHelper
def duration_in_words(finished_at, started_at)
if finished_at && started_at
interval_in_seconds = finished_at.to_i - started_at.to_i
elsif started_at
interval_in_seconds = Time.now.to_i - started_at.to_i
end
time_interval_in_words(interval_in_seconds)
end
def time_interval_in_words(interval_in_seconds) def time_interval_in_words(interval_in_seconds)
minutes = interval_in_seconds / 60 minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60 seconds = interval_in_seconds - minutes * 60
...@@ -25,9 +15,19 @@ module TimeHelper ...@@ -25,9 +15,19 @@ module TimeHelper
end end
def duration_in_numbers(finished_at, started_at) def duration_in_numbers(finished_at, started_at)
diff_in_seconds = finished_at.to_i - started_at.to_i interval = interval_in_seconds(started_at, finished_at)
time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S" time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
Time.at(diff_in_seconds).utc.strftime(time_format) Time.at(interval).utc.strftime(time_format)
end
private
def interval_in_seconds(started_at, finished_at = nil)
if started_at && finished_at
finished_at.to_i - started_at.to_i
elsif started_at
Time.now.to_i - started_at.to_i
end
end end
end end
...@@ -172,7 +172,7 @@ class Ability ...@@ -172,7 +172,7 @@ class Ability
rules << :read_build if project.public_builds? rules << :read_build if project.public_builds?
unless owner || project.team.member?(user) || project_group_member?(project, user) unless owner || project.team.member?(user) || project_group_member?(project, user)
rules << :request_access rules << :request_access if project.request_access_enabled
end end
end end
...@@ -373,7 +373,7 @@ class Ability ...@@ -373,7 +373,7 @@ class Ability
end end
if group.public? || (group.internal? && !user.external?) if group.public? || (group.internal? && !user.external?)
rules << :request_access unless group.users.include?(user) rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
end end
rules.flatten rules.flatten
......
...@@ -97,7 +97,7 @@ module Ci ...@@ -97,7 +97,7 @@ module Ci
end end
def other_actions def other_actions
pipeline.manual_actions.where.not(id: self) pipeline.manual_actions.where.not(name: name)
end end
def playable? def playable?
...@@ -145,11 +145,14 @@ module Ci ...@@ -145,11 +145,14 @@ module Ci
end end
def variables def variables
variables = [] variables = predefined_variables
variables += predefined_variables variables += project.predefined_variables
variables += yaml_variables if yaml_variables variables += pipeline.predefined_variables
variables += project_variables variables += runner.predefined_variables if runner
variables += trigger_variables variables += project.container_registry_variables
variables += yaml_variables
variables += project.secret_variables
variables += trigger_request.user_variables if trigger_request
variables variables
end end
...@@ -409,6 +412,14 @@ module Ci ...@@ -409,6 +412,14 @@ module Ci
self.update(artifacts_expire_at: nil) self.update(artifacts_expire_at: nil)
end end
def when
read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
end
def yaml_variables
read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
end
private private
def update_artifacts_size def update_artifacts_size
...@@ -427,29 +438,30 @@ module Ci ...@@ -427,29 +438,30 @@ module Ci
self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil) self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil)
end end
def project_variables
project.variables.map do |variable|
{ key: variable.key, value: variable.value, public: false }
end
end
def trigger_variables
if trigger_request && trigger_request.variables
trigger_request.variables.map do |key, value|
{ key: key, value: value, public: false }
end
else
[]
end
end
def predefined_variables def predefined_variables
variables = [] variables = [
variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag? { key: 'CI', value: 'true', public: true },
variables << { key: :CI_BUILD_NAME, value: name, public: true } { key: 'GITLAB_CI', value: 'true', public: true },
variables << { key: :CI_BUILD_STAGE, value: stage, public: true } { key: 'CI_BUILD_ID', value: id.to_s, public: true },
variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request { key: 'CI_BUILD_TOKEN', value: token, public: false },
{ key: 'CI_BUILD_REF', value: sha, public: true },
{ key: 'CI_BUILD_BEFORE_SHA', value: before_sha, public: true },
{ key: 'CI_BUILD_REF_NAME', value: ref, public: true },
{ key: 'CI_BUILD_NAME', value: name, public: true },
{ key: 'CI_BUILD_STAGE', value: stage, public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }
]
variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
variables variables
end end
def build_attributes_from_config
return {} unless pipeline.config_processor
pipeline.config_processor.build_attributes(name)
end
end end
end end
...@@ -203,6 +203,12 @@ module Ci ...@@ -203,6 +203,12 @@ module Ci
Note.for_commit_id(sha) Note.for_commit_id(sha)
end end
def predefined_variables
[
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
]
end
private private
def build_builds_for_stages(stages, user, status, trigger_request) def build_builds_for_stages(stages, user, status, trigger_request)
...@@ -211,8 +217,9 @@ module Ci ...@@ -211,8 +217,9 @@ module Ci
# build builds only for the first stage that has builds available. # build builds only for the first stage that has builds available.
# #
stages.any? do |stage| stages.any? do |stage|
CreateBuildsService.new(self) CreateBuildsService.new(self).
.execute(stage, user, status, trigger_request).present? execute(stage, user, status, trigger_request).
any?(&:active?)
end end
end end
......
...@@ -114,6 +114,14 @@ module Ci ...@@ -114,6 +114,14 @@ module Ci
tag_list.any? tag_list.any?
end end
def predefined_variables
[
{ key: 'CI_RUNNER_ID', value: id.to_s, public: true },
{ key: 'CI_RUNNER_DESCRIPTION', value: description, public: true },
{ key: 'CI_RUNNER_TAGS', value: tag_list.to_s, public: true }
]
end
private private
def tag_constraints def tag_constraints
......
...@@ -7,5 +7,13 @@ module Ci ...@@ -7,5 +7,13 @@ module Ci
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
serialize :variables serialize :variables
def user_variables
return [] unless variables
variables.map do |key, value|
{ key: key, value: value, public: false }
end
end
end end
end end
...@@ -661,6 +661,22 @@ class Project < ActiveRecord::Base ...@@ -661,6 +661,22 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end end
def external_wiki
if has_external_wiki.nil?
cache_has_external_wiki # Populate
end
if has_external_wiki
@external_wiki ||= services.external_wikis.first
else
nil
end
end
def cache_has_external_wiki
update_column(:has_external_wiki, services.external_wikis.any?)
end
def build_missing_services def build_missing_services
services_templates = Service.where(template: true) services_templates = Service.where(template: true)
...@@ -1175,4 +1191,34 @@ class Project < ActiveRecord::Base ...@@ -1175,4 +1191,34 @@ class Project < ActiveRecord::Base
def ensure_dir_exist def ensure_dir_exist
gitlab_shell.add_namespace(repository_storage_path, namespace.path) gitlab_shell.add_namespace(repository_storage_path, namespace.path)
end end
def predefined_variables
[
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true },
{ key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.path, public: true },
{ key: 'CI_PROJECT_URL', value: web_url, public: true }
]
end
def container_registry_variables
return [] unless Gitlab.config.registry.enabled
variables = [
{ key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port, public: true }
]
if container_registry_enabled?
variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_repository_url, public: true }
end
variables
end
def secret_variables
variables.map do |variable|
{ key: variable.key, value: variable.value, public: false }
end
end
end end
...@@ -4,6 +4,9 @@ class SlackService < Service ...@@ -4,6 +4,9 @@ class SlackService < Service
validates :webhook, presence: true, url: true, if: :activated? validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties def initialize_properties
# Custom serialized properties initialization
self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
if properties.nil? if properties.nil?
self.properties = {} self.properties = {}
self.notify_only_broken_builds = true self.notify_only_broken_builds = true
...@@ -29,13 +32,15 @@ class SlackService < Service ...@@ -29,13 +32,15 @@ class SlackService < Service
end end
def fields def fields
[ default_fields =
{ type: 'text', name: 'webhook', [
placeholder: 'https://hooks.slack.com/services/...' }, { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
{ type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'username', placeholder: 'username' },
{ type: 'text', name: 'channel', placeholder: '#channel' }, { type: 'text', name: 'channel', placeholder: "#general" },
{ type: 'checkbox', name: 'notify_only_broken_builds' }, { type: 'checkbox', name: 'notify_only_broken_builds' },
] ]
default_fields + build_event_channels
end end
def supported_events def supported_events
...@@ -74,7 +79,10 @@ class SlackService < Service ...@@ -74,7 +79,10 @@ class SlackService < Service
end end
opt = {} opt = {}
opt[:channel] = channel if channel
event_channel = get_channel_field(object_kind) || channel
opt[:channel] = event_channel if event_channel
opt[:username] = username if username opt[:username] = username if username
if message if message
...@@ -83,8 +91,35 @@ class SlackService < Service ...@@ -83,8 +91,35 @@ class SlackService < Service
end end
end end
def event_channel_names
supported_events.map { |event| event_channel_name(event) }
end
def event_field(event)
fields.find { |field| field[:name] == event_channel_name(event) }
end
def global_fields
fields.reject { |field| field[:name].end_with?('channel') }
end
private private
def get_channel_field(event)
field_name = event_channel_name(event)
self.public_send(field_name)
end
def build_event_channels
supported_events.reduce([]) do |channels, event|
channels << { type: 'text', name: event_channel_name(event), placeholder: "#general" }
end
end
def event_channel_name(event)
"#{event}_channel"
end
def project_name def project_name
project.name_with_namespace.gsub(/\s/, '') project.name_with_namespace.gsub(/\s/, '')
end end
......
...@@ -392,6 +392,11 @@ class Repository ...@@ -392,6 +392,11 @@ class Repository
expire_cache if exists? expire_cache if exists?
# expire cache that don't depend on repository data (when expiring)
expire_tags_cache
expire_tag_count_cache
expire_branches_cache
expire_branch_count_cache
expire_root_ref_cache expire_root_ref_cache
expire_emptiness_caches expire_emptiness_caches
expire_exists_cache expire_exists_cache
...@@ -733,6 +738,33 @@ class Repository ...@@ -733,6 +738,33 @@ class Repository
end end
end end
def update_file(user, path, content, branch:, previous_path:, message:)
commit_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
options = {}
options[:committer] = committer
options[:author] = committer
options[:commit] = {
message: message,
branch: ref,
update_ref: false
}
options[:file] = {
content: content,
path: path,
update: true
}
if previous_path
options[:file][:previous_path] = previous_path
Gitlab::Git::Blob.rename(raw_repository, options)
else
Gitlab::Git::Blob.commit(raw_repository, options)
end
end
end
def remove_file(user, path, message, branch) def remove_file(user, path, message, branch)
commit_with_hooks(user, branch) do |ref| commit_with_hooks(user, branch) do |ref|
committer = user_to_committer(user) committer = user_to_committer(user)
......
...@@ -17,6 +17,7 @@ class Service < ActiveRecord::Base ...@@ -17,6 +17,7 @@ class Service < ActiveRecord::Base
after_commit :reset_updated_properties after_commit :reset_updated_properties
after_commit :cache_project_has_external_issue_tracker after_commit :cache_project_has_external_issue_tracker
after_commit :cache_project_has_external_wiki
belongs_to :project, inverse_of: :services belongs_to :project, inverse_of: :services
has_one :service_hook has_one :service_hook
...@@ -25,6 +26,7 @@ class Service < ActiveRecord::Base ...@@ -25,6 +26,7 @@ class Service < ActiveRecord::Base
scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) } scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
scope :issue_trackers, -> { where(category: 'issue_tracker') } scope :issue_trackers, -> { where(category: 'issue_tracker') }
scope :external_wikis, -> { where(type: 'ExternalWikiService') }
scope :active, -> { where(active: true) } scope :active, -> { where(active: true) }
scope :without_defaults, -> { where(default: false) } scope :without_defaults, -> { where(default: false) }
...@@ -80,6 +82,18 @@ class Service < ActiveRecord::Base ...@@ -80,6 +82,18 @@ class Service < ActiveRecord::Base
Gitlab::PushDataBuilder.build_sample(project, user) Gitlab::PushDataBuilder.build_sample(project, user)
end end
def event_channel_names
[]
end
def event_field(event)
nil
end
def global_fields
fields
end
def supported_events def supported_events
%w(push tag_push issue merge_request wiki_page) %w(push tag_push issue merge_request wiki_page)
end end
...@@ -212,4 +226,10 @@ class Service < ActiveRecord::Base ...@@ -212,4 +226,10 @@ class Service < ActiveRecord::Base
project.cache_has_external_issue_tracker project.cache_has_external_issue_tracker
end end
end end
def cache_project_has_external_wiki
if project && !project.destroyed?
project.cache_has_external_wiki
end
end
end end
...@@ -854,7 +854,7 @@ class User < ActiveRecord::Base ...@@ -854,7 +854,7 @@ class User < ActiveRecord::Base
groups.joins(:shared_projects).select(:project_id)] groups.joins(:shared_projects).select(:project_id)]
if min_access_level if min_access_level
scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } } scope = { access_level: Gitlab::Access.all_values.select { |access| access >= min_access_level } }
relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) } relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
end end
......
...@@ -9,12 +9,14 @@ module Files ...@@ -9,12 +9,14 @@ module Files
@commit_message = params[:commit_message] @commit_message = params[:commit_message]
@file_path = params[:file_path] @file_path = params[:file_path]
@previous_path = params[:previous_path]
@file_content = if params[:file_content_encoding] == 'base64' @file_content = if params[:file_content_encoding] == 'base64'
Base64.decode64(params[:file_content]) Base64.decode64(params[:file_content])
else else
params[:file_content] params[:file_content]
end end
# Validate parameters
validate validate
# Create new branch if it different from source_branch # Create new branch if it different from source_branch
......
...@@ -3,7 +3,10 @@ require_relative "base_service" ...@@ -3,7 +3,10 @@ require_relative "base_service"
module Files module Files
class UpdateService < Files::BaseService class UpdateService < Files::BaseService
def commit def commit
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, true) repository.update_file(current_user, @file_path, @file_content,
branch: @target_branch,
previous_path: @previous_path,
message: @commit_message)
end end
end end
end end
...@@ -9,7 +9,7 @@ module Projects ...@@ -9,7 +9,7 @@ module Projects
private private
def save_all def save_all
if [version_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) if [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
Gitlab::ImportExport::Saver.save(project: project, shared: @shared) Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
notify_success notify_success
else else
...@@ -21,6 +21,10 @@ module Projects ...@@ -21,6 +21,10 @@ module Projects
Gitlab::ImportExport::VersionSaver.new(shared: @shared) Gitlab::ImportExport::VersionSaver.new(shared: @shared)
end end
def avatar_saver
Gitlab::ImportExport::AvatarSaver.new(project: project, shared: @shared)
end
def project_tree_saver def project_tree_saver
Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared) Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared)
end end
......
...@@ -14,4 +14,8 @@ class AvatarUploader < CarrierWave::Uploader::Base ...@@ -14,4 +14,8 @@ class AvatarUploader < CarrierWave::Uploader::Base
def reset_events_cache(file) def reset_events_cache(file)
model.reset_events_cache if model.is_a?(User) model.reset_events_cache if model.is_a?(User)
end end
def exists?
model.avatar.file && model.avatar.file.exists?
end
end end
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
.form-group
.col-sm-offset-2.col-sm-10
= render 'shared/allow_request_access', form: f
- if @group.new_record? - if @group.new_record?
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
...@@ -4,11 +4,7 @@ ...@@ -4,11 +4,7 @@
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.2"] do = cache [event, current_application_settings, "v2.2"] do
- if event.author = author_avatar(event, size: 40)
= link_to user_path(event.author) do
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- else
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- if event.created_project? - if event.created_project?
= render "events/event/created_project", event: event = render "events/event/created_project", event: event
......
...@@ -21,6 +21,10 @@ ...@@ -21,6 +21,10 @@
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
.form-group
.col-sm-offset-2.col-sm-10
= render 'shared/allow_request_access', form: f
.form-group .form-group
%hr %hr
= f.label :share_with_group_lock, class: 'control-label' do = f.label :share_with_group_lock, class: 'control-label' do
......
- project = @target_project || @project - project = @target_project || @project
- noteable_class = @noteable.class if @noteable.present?
- if @noteable :javascript
:javascript GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_class, type_id: params[:id])}"
GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}" GitLab.GfmAutoComplete.cachedData = undefined;
GitLab.GfmAutoComplete.cachedData = undefined; GitLab.GfmAutoComplete.setup();
GitLab.GfmAutoComplete.setup();
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do = link_to explore_root_path, title: 'Projects' do
= icon('bookmark fw')
%span %span
Projects Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do = link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
%span %span
Groups Groups
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets' do = link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span %span
Snippets Snippets
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help' do = link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span %span
Help Help
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
%span %span
Triggers Triggers
= nav_link(controller: :badges) do = nav_link(controller: :pipelines_settings) do
= link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do = link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
%span %span
Badges CI/CD Pipelines
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/cropper.js') = page_specific_javascript_tag('lib/cropper.js')
= page_specific_javascript_tag('profile/application.js') = page_specific_javascript_tag('profile/profile_bundle.js')
%fieldset.builds-feature
%h5.prepend-top-0
Builds
- unless @repository.gitlab_ci_yml
.form-group
%p Builds need to be configured before you can begin using Continuous Integration.
= link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
.form-group
%p Get recent application code using the following command:
.radio
= f.label :build_allow_git_fetch_false do
= f.radio_button :build_allow_git_fetch, 'false'
%strong git clone
%br
%span.descr Slower but makes sure you have a clean dir before every build
.radio
= f.label :build_allow_git_fetch_true do
= f.radio_button :build_allow_git_fetch, 'true'
%strong git fetch
%br
%span.descr Faster
.form-group
= f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
= f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
%p.help-block per build in minutes
.form-group
= f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
.input-group
%span.input-group-addon /
= f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
%span.input-group-addon /
%p.help-block
We will use this regular expression to find test coverage output in build trace.
Leave blank if you want to disable this feature
.bs-callout.bs-callout-info
%p Below are examples of regex for existing tools:
%ul
%li
Simplecov (Ruby) -
%code \(\d+.\d+\%\) covered
%li
pytest-cov (Python) -
%code \d+\%\s*$
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
%li
tap --coverage-report=text-summary (Node.js) -
%code ^Statements\s*:\s*([^%]+)
.form-group
.checkbox
= f.label :public_builds do
= f.check_box :public_builds
%strong Public builds
.help-block Allow everyone to access builds for Public and Internal projects
.form-group.append-bottom-0
= f.label :runners_token, "Runners token", class: 'label-light'
= f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
%p.help-block The secure token used to checkout project.
- page_title 'Badges'
- badges_path = namespace_project_badges_path(@project.namespace, @project)
.prepend-top-10
.panel.panel-default
.panel-heading
%b Builds badge &middot;
= @build_badge.to_html
.pull-right
= render 'shared/ref_switcher', destination: 'badges', align_right: true
.panel-body
.row
.col-md-2.text-center
Markdown
.col-md-10.code.js-syntax-highlight
= highlight('.md', @build_badge.to_markdown)
.row
%hr
.row
.col-md-2.text-center
HTML
.col-md-10.code.js-syntax-highlight
= highlight('.html', @build_badge.to_html)
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
= icon('code-fork') = icon('code-fork')
= ref = ref
%span.editor-file-name %span.editor-file-name
= @path - if current_action?(:edit) || current_action?(:update)
= text_field_tag 'file_path', (params[:file_path] || @path),
class: 'form-control new-file-path'
- if current_action?(:new) || current_action?(:create) - if current_action?(:new) || current_action?(:create)
%span.editor-file-name %span.editor-file-name
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
- if @build.duration - if @build.duration
%p.build-detail-row %p.build-detail-row
%span.build-light-text Duration: %span.build-light-text Duration:
#{duration_in_words(@build.finished_at, @build.started_at)} = time_interval_in_words(@build.duration)
- if @build.finished_at - if @build.finished_at
%p.build-detail-row %p.build-detail-row
%span.build-light-text Finished: %span.build-light-text Finished:
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if current_user && can?(current_user, :fork_project, @project) - if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
%div.count-with-arrow %div.count-with-arrow
%span.arrow %span.arrow
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= @project.forks_count = @project.forks_count
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
%div.count-with-arrow %div.count-with-arrow
%span.arrow %span.arrow
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
%p.commit-title %p.commit-title
- if commit = pipeline.commit - if commit = pipeline.commit
= commit_author_avatar(commit, size: 20) = author_avatar(commit, size: 20)
= link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message" = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
- stages_status = pipeline.statuses.latest.stages_status - stages_status = pipeline.statuses.latest.stages_status
- stages.each do |stage| - stages.each do |stage|
%td %td.stage-cell
- status = stages_status[stage] - status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}" - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status - if status
......
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
= cache(cache_key) do = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
= commit_author_avatar(commit, size: 36) = author_avatar(commit, size: 36)
.commit-info-block .commit-info-block
.commit-row-title .commit-row-title
%span.item-title %span.item-title
......
...@@ -32,6 +32,10 @@ ...@@ -32,6 +32,10 @@
%strong %strong
= visibility_level_label(@project.visibility_level) = visibility_level_label(@project.visibility_level)
.light= visibility_level_description(@project.visibility_level, @project) .light= visibility_level_description(@project.visibility_level, @project)
.form-group
= render 'shared/allow_request_access', form: f
.form-group .form-group
= f.label :tag_list, "Tags", class: 'label-light' = f.label :tag_list, "Tags", class: 'label-light'
= f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control" = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
...@@ -86,8 +90,6 @@ ...@@ -86,8 +90,6 @@
%hr %hr
= render 'merge_request_settings', f: f = render 'merge_request_settings', f: f
%hr %hr
= render 'builds_settings', f: f
%hr
%fieldset.features.append-bottom-default %fieldset.features.append-bottom-default
%h5.prepend-top-0 %h5.prepend-top-0
Project avatar Project avatar
......
...@@ -31,11 +31,11 @@ ...@@ -31,11 +31,11 @@
- if current_user && can?(current_user, :fork_project, @project) - if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
......
- page_title "Fork project" - page_title "Fork project"
- if @namespaces.present?
%h3.page-title Fork project
%p.lead
Click to fork the project to a user or group
%hr
.fork-namespaces .row.prepend-top-default
- @namespaces.in_groups_of(6, false) do |group| .col-lg-3
.row %h4.prepend-top-0
- group.each do |namespace| Fork project
.col-md-2.col-sm-3 %p
- if fork = namespace.find_fork_of(@project) A fork is a copy of a project.
.fork-thumbnail
= link_to project_path(fork), title: "Visit project fork", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
= namespace.human_name
%div.text-primary
Already forked
- else
.fork-thumbnail
= link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
= namespace.human_name
%p.light
Fork is a copy of a project repository.
%br %br
Forking a repository allows you to do changes without affecting the original project. Forking a repository allows you to make changes without affecting the original project.
- else .col-lg-9
%h3 No available namespaces to fork the project .fork-namespaces
%p.slead - if @namespaces.present?
You must have permission to create a project in a namespace before forking. %label.label-light
%span
Click to fork the project to a user or group
- @namespaces.in_groups_of(6, false) do |group|
.row
- group.each do |namespace|
- avatar = namespace_icon(namespace, 100)
- if fork = namespace.find_fork_of(@project)
.fork-thumbnail.forked
= link_to project_path(fork) do
- if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar
= icon 'question'
- else
= image_tag avatar
.caption
= namespace.human_name
- else
.fork-thumbnail
= link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), method: "POST" do
- if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar
= icon 'question'
- else
= image_tag avatar
.caption
= namespace.human_name
- else
%label.label-light
%span
No available namespaces to fork the project.
%br
%small
You must have permission to create a project in a namespace before forking.
.save-project-loader.hide .save-project-loader.hide
.center .center
%h2 %h2
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
Forking repository Forking repository
%p Please wait a moment, this page will automatically refresh when ready. %p Please wait a moment, this page will automatically refresh when ready.
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
%td.duration %td.duration
- if generic_commit_status.duration - if generic_commit_status.duration
= icon("clock-o") = icon("clock-o")
#{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)} = time_interval_in_words(generic_commit_status.duration)
%td.timestamp %td.timestamp
- if generic_commit_status.finished_at - if generic_commit_status.finished_at
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/chart.js') = page_specific_javascript_tag('lib/chart.js')
= page_specific_javascript_tag('graphs/application.js') = page_specific_javascript_tag('graphs/graphs_bundle.js')
= nav_link(action: :show) do = nav_link(action: :show) do
= link_to 'Contributors', namespace_project_graph_path = link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do = nav_link(action: :commits) do
......
- page_title "Network", @ref - page_title "Network", @ref
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/raphael.js') = page_specific_javascript_tag('lib/raphael.js')
= page_specific_javascript_tag('network/application.js') = page_specific_javascript_tag('network/network_bundle.js')
= render "projects/commits/head" = render "projects/commits/head"
= render "head" = render "head"
%div{ class: container_class } %div{ class: container_class }
......
- page_title "CI/CD Pipelines"
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
.col-lg-9
%h5.prepend-top-0
Pipelines
= form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project), remote: true, authenticity_token: true do |f|
%fieldset.builds-feature
- unless @repository.gitlab_ci_yml
.form-group
%p Pipelines need to be configured before you can begin using Continuous Integration.
= link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
.form-group
%p Get recent application code using the following command:
.radio
= f.label :build_allow_git_fetch_false do
= f.radio_button :build_allow_git_fetch, 'false'
%strong git clone
%br
%span.descr Slower but makes sure you have a clean dir before every build
.radio
= f.label :build_allow_git_fetch_true do
= f.radio_button :build_allow_git_fetch, 'true'
%strong git fetch
%br
%span.descr Faster
.form-group
= f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
= f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
%p.help-block per build in minutes
.form-group
= f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
.input-group
%span.input-group-addon /
= f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
%span.input-group-addon /
%p.help-block
We will use this regular expression to find test coverage output in build trace.
Leave blank if you want to disable this feature
.bs-callout.bs-callout-info
%p Below are examples of regex for existing tools:
%ul
%li
Simplecov (Ruby) -
%code \(\d+.\d+\%\) covered
%li
pytest-cov (Python) -
%code \d+\%\s*$
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
%li
tap --coverage-report=text-summary (Node.js) -
%code ^Statements\s*:\s*([^%]+)
.form-group
.checkbox
= f.label :public_builds do
= f.check_box :public_builds
%strong Public pipelines
.help-block Allow everyone to access pipelines for Public and Internal projects
.form-group.append-bottom-default
= f.label :runners_token, "Runners token", class: 'label-light'
= f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
%p.help-block The secure token used to checkout project.
= f.submit 'Save changes', class: "btn btn-save"
%hr
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
Builds Badge
.col-lg-9
.prepend-top-10
.panel.panel-default
.panel-heading
%b Builds badge &middot;
= @build_badge.to_html
.pull-right
= render 'shared/ref_switcher', destination: 'badges', align_right: true
.panel-body
.row
.col-md-2.text-center
Markdown
.col-md-10.code.js-syntax-highlight
= highlight('.md', @build_badge.to_markdown)
.row
%hr
.row
.col-md-2.text-center
HTML
.col-md-10.code.js-syntax-highlight
= highlight('.html', @build_badge.to_html)
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
.col-lg-9 .col-lg-9
= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form| = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
= render 'shared/service_settings', form: form = render 'shared/service_settings', form: form
= form.submit 'Save changes', class: 'btn btn-save' = form.submit 'Save changes', class: 'btn btn-save'
&nbsp; &nbsp;
- if @service.valid? && @service.activated? - if @service.valid? && @service.activated?
......
- @no_container = true
- page_title @tag.name, "Tags" - page_title @tag.name, "Tags"
= render "projects/commits/head" = render "projects/commits/head"
.row-content-block %div{ class: container_class }
.pull-right .sub-header-block
- if can?(current_user, :push_code, @project) .pull-right.tag-buttons
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do - if can?(current_user, :push_code, @project)
= icon("pencil") = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do = icon("pencil")
= icon('files-o') = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do = icon('files-o')
= icon('history') = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
- if can? current_user, :download_code, @project = icon('history')
= render 'projects/tags/download', ref: @tag.name, project: @project - if can? current_user, :download_code, @project
- if can?(current_user, :admin_project, @project) = render 'projects/tags/download', ref: @tag.name, project: @project
.pull-right - if can?(current_user, :admin_project, @project)
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do .pull-right
%i.fa.fa-trash-o = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
.title %i.fa.fa-trash-o
%span.item-title= @tag.name .tag-info.append-bottom-10
- if @commit .title
= render 'projects/branches/commit', commit: @commit, project: @project %span.item-title= @tag.name
- else - if @commit
Cant find HEAD commit for this tag = render 'projects/branches/commit', commit: @commit, project: @project
- if @tag.message.present? - else
%pre.body Cant find HEAD commit for this tag
= strip_gpg_signature(@tag.message) - if @tag.message.present?
%pre.body
= strip_gpg_signature(@tag.message)
.append-bottom-default.prepend-top-default .append-bottom-default.prepend-top-default
- if @release.description.present? - if @release.description.present?
.description .description
.wiki .wiki
= preserve do = preserve do
= markdown @release.description = markdown @release.description
- else - else
This tag has no release notes. This tag has no release notes.
.checkbox
= form.label :request_access_enabled do
= form.check_box :request_access_enabled
%strong Allow users to request access
%br
%span.descr Allow users to request access if visibility is public or internal.
...@@ -10,69 +10,28 @@ ...@@ -10,69 +10,28 @@
.col-sm-10 .col-sm-10
= form.check_box :active = form.check_box :active
- if @service.supported_events.length > 1 .form-group
.form-group = form.label :url, "Trigger", class: 'control-label'
= form.label :url, "Trigger", class: 'control-label'
.col-sm-10 .col-sm-10
- if @service.supported_events.include?("push") - @service.supported_events.each do |event|
%div %div
= form.check_box :push_events, class: 'pull-left' = form.check_box service_event_field_name(event), class: 'pull-left'
.prepend-left-20 .prepend-left-20
= form.label :push_events, class: 'list-label' do = form.label service_event_field_name(event), class: 'list-label' do
%strong Push events %strong
%p.light = event.humanize
This url will be triggered by a push to the repository
- if @service.supported_events.include?("tag_push") - field = @service.event_field(event)
%div
= form.check_box :tag_push_events, class: 'pull-left' - if field
.prepend-left-20 %p
= form.label :tag_push_events, class: 'list-label' do = form.text_field field[:name], class: "form-control", placeholder: field[:placeholder]
%strong Tag push events
%p.light
This url will be triggered when a new tag is pushed to the repository
- if @service.supported_events.include?("note")
%div
= form.check_box :note_events, class: 'pull-left'
.prepend-left-20
= form.label :note_events, class: 'list-label' do
%strong Comments
%p.light
This url will be triggered when someone adds a comment
- if @service.supported_events.include?("issue")
%div
= form.check_box :issues_events, class: 'pull-left'
.prepend-left-20
= form.label :issues_events, class: 'list-label' do
%strong Issues events
%p.light
This url will be triggered when an issue is created/updated/merged
- if @service.supported_events.include?("merge_request")
%div
= form.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20
= form.label :merge_requests_events, class: 'list-label' do
%strong Merge Request events
%p.light
This url will be triggered when a merge request is created/updated/merged
- if @service.supported_events.include?("build")
%div
= form.check_box :build_events, class: 'pull-left'
.prepend-left-20
= form.label :build_events, class: 'list-label' do
%strong Build events
%p.light
This url will be triggered when a build status changes
- if @service.supported_events.include?("wiki_page")
%div
= form.check_box :wiki_page_events, class: 'pull-left'
.prepend-left-20
= form.label :wiki_page_events, class: 'list-label' do
%strong Wiki Page events
%p.light
This url will be triggered when a wiki page is created/updated
%p.light
= service_event_description(event)
- @service.fields.each do |field| - @service.global_fields.each do |field|
- type = field[:type] - type = field[:type]
- if type == 'fieldset' - if type == 'fieldset'
......
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="4" cy="4" r="4"/><mask id="d" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="20" cy="4" r="4"/><mask id="e" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="12" cy="30" r="4"/><mask id="f" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(8 3)"><path fill="#7E7E7E" d="M10 19.667c-4.14-1.29-7.389-5.878-7.389-5.878C2.274 13.353 2 12.545 2 12.01V6h4v5.509c0 .276.166.65.367.831 0 0 1.136 1.028 1.746 1.574C9.617 15.261 11.048 16 12.09 16c1.028 0 2.41-.723 3.858-2.048.588-.54 1.84-1.742 1.84-1.742a.784.784 0 0 0 .211-.502V6h4v6.008c0 .548-.259 1.349-.601 1.795 0 0-3.21 4.707-7.399 5.916V27h-4v-7.333z"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#d)" xlink:href="#a"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#e)" xlink:href="#b"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#f)" xlink:href="#c"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#5C5C5C" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<rect width="10" height="1" x="2" y="6.5" fill="#5C5C5C" transform="rotate(45 7 7)" rx=".3"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#D22852" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<path fill="#D22852" d="M7.5,6.5 L7.5,4.30578971 C7.5,4.12531853 7.36809219,4 7.20537567,4 L6.79462433,4 C6.63904572,4 6.5,4.13690672 6.5,4.30578971 L6.5,6.5 L4.30578971,6.5 C4.12531853,6.5 4,6.63190781 4,6.79462433 L4,7.20537567 C4,7.36095428 4.13690672,7.5 4.30578971,7.5 L6.5,7.5 L6.5,9.69421029 C6.5,9.87468147 6.63190781,10 6.79462433,10 L7.20537567,10 C7.36095428,10 7.5,9.86309328 7.5,9.69421029 L7.5,7.5 L9.69421029,7.5 C9.87468147,7.5 10,7.36809219 10,7.20537567 L10,6.79462433 C10,6.63904572 9.86309328,6.5 9.69421029,6.5 L7.5,6.5 Z" transform="rotate(45 7 7)"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#E75E40" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<rect width="1" height="4" x="5" y="5" fill="#E75E40" rx=".3"/>
<rect width="1" height="4" x="8" y="5" fill="#E75E40" rx=".3"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#2D9FD8" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<path fill="#2D9FD8" d="M7,3.00800862 C9.09023405,3.13960661 10.7448145,4.87657932 10.7448145,7 C10.7448145,9.209139 8.95395346,11 6.74481446,11 C5.4560962,11 4.30972054,10.3905589 3.57817301,9.44416214 L7,7 L7,3.00800862 Z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#31AF64" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<g fill="#31AF64" transform="rotate(45 -.13 10.953)">
<rect width="1" height="5" x="2" rx=".3"/>
<rect width="3" height="1" y="4" rx=".3"/>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<g fill="#FF8A24" transform="translate(6 3)">
<rect width="2" height="5" rx=".5"/>
<rect width="2" height="2" y="6" rx=".5"/>
</g>
<use stroke="#FF8A24" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
</g>
</svg>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- page_description @user.bio - page_description @user.bio
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/d3.js') = page_specific_javascript_tag('lib/d3.js')
= page_specific_javascript_tag('users/application.js') = page_specific_javascript_tag('users/users_bundle.js')
- header_title @user.name, user_path(@user) - header_title @user.name, user_path(@user)
- @no_container = true - @no_container = true
......
...@@ -81,10 +81,10 @@ module Gitlab ...@@ -81,10 +81,10 @@ module Gitlab
config.assets.precompile << "print.css" config.assets.precompile << "print.css"
config.assets.precompile << "notify.css" config.assets.precompile << "notify.css"
config.assets.precompile << "mailers/*.css" config.assets.precompile << "mailers/*.css"
config.assets.precompile << "graphs/application.js" config.assets.precompile << "graphs/graphs_bundle.js"
config.assets.precompile << "users/application.js" config.assets.precompile << "users/users_bundle.js"
config.assets.precompile << "network/application.js" config.assets.precompile << "network/network_bundle.js"
config.assets.precompile << "profile/application.js" config.assets.precompile << "profile/profile_bundle.js"
config.assets.precompile << "lib/utils/*.js" config.assets.precompile << "lib/utils/*.js"
config.assets.precompile << "lib/*.js" config.assets.precompile << "lib/*.js"
config.assets.precompile << "u2f.js" config.assets.precompile << "u2f.js"
......
...@@ -4,14 +4,7 @@ ...@@ -4,14 +4,7 @@
require 'gitlab/current_settings' require 'gitlab/current_settings'
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
# If Sentry is enabled and the Rails app is running in production mode, CSP_REPORT_URI = ''
# this will construct the Report URI for Sentry.
if Rails.env.production? && current_application_settings.sentry_enabled
uri = URI.parse(current_application_settings.sentry_dsn)
CSP_REPORT_URI = "#{uri.scheme}://#{uri.host}/api#{uri.path}/csp-report/?sentry_key=#{uri.user}"
else
CSP_REPORT_URI = ''
end
# Content Security Policy Headers # Content Security Policy Headers
# For more information on CSP see: # For more information on CSP see:
...@@ -71,10 +64,7 @@ SecureHeaders::Configuration.default do |config| ...@@ -71,10 +64,7 @@ SecureHeaders::Configuration.default do |config|
upgrade_insecure_requests: true upgrade_insecure_requests: true
} }
# Reports are sent to Sentry if it's enabled. config.csp[:report_uri] = %W(#{CSP_REPORT_URI})
if current_application_settings.sentry_enabled
config.csp[:report_uri] = %W(#{CSP_REPORT_URI})
end
# Allow Bootstrap Linter in development mode. # Allow Bootstrap Linter in development mode.
if Rails.env.development? if Rails.env.development?
......
...@@ -18,7 +18,8 @@ Sidekiq.configure_server do |config| ...@@ -18,7 +18,8 @@ Sidekiq.configure_server do |config|
if cron_jobs[k] && cron_jobs_required_keys.all? { |s| cron_jobs[k].key?(s) } if cron_jobs[k] && cron_jobs_required_keys.all? { |s| cron_jobs[k].key?(s) }
cron_jobs[k]['class'] = cron_jobs[k].delete('job_class') cron_jobs[k]['class'] = cron_jobs[k].delete('job_class')
else else
raise("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.") cron_jobs.delete(k)
Rails.logger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.")
end end
end end
Sidekiq::Cron::Job.load_from_hash! cron_jobs Sidekiq::Cron::Job.load_from_hash! cron_jobs
......
...@@ -89,11 +89,10 @@ Rails.application.routes.draw do ...@@ -89,11 +89,10 @@ Rails.application.routes.draw do
mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put] mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put]
# Help # Help
get 'help' => 'help#index'
get 'help' => 'help#index' get 'help/shortcuts' => 'help#shortcuts'
get 'help/*path' => 'help#show', as: :help_page get 'help/ui' => 'help#ui'
get 'help/shortcuts' get 'help/*path' => 'help#show', as: :help_page
get 'help/ui' => 'help#ui'
# #
# Global snippets # Global snippets
...@@ -733,6 +732,10 @@ Rails.application.routes.draw do ...@@ -733,6 +732,10 @@ Rails.application.routes.draw do
resources :triggers, only: [:index, :create, :destroy] resources :triggers, only: [:index, :create, :destroy]
resources :pipelines, only: [:index, :new, :create, :show] do resources :pipelines, only: [:index, :new, :create, :show] do
collection do
resource :pipelines_settings, path: 'settings', only: [:show, :update]
end
member do member do
post :cancel post :cancel
post :retry post :retry
......
...@@ -7,7 +7,13 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration ...@@ -7,7 +7,13 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration
class ProjectImportDataFake class ProjectImportDataFake
extend AttrEncrypted extend AttrEncrypted
attr_accessor :credentials attr_accessor :credentials
attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt attr_encrypted :credentials,
key: Gitlab::Application.secrets.db_key_base,
marshal: true,
encode: true,
:mode => :per_attribute_iv_and_salt,
insecure_mode: true,
algorithm: 'aes-256-cbc'
end end
def up def up
......
class AddRequestAccessEnabledToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_column_with_default :projects, :request_access_enabled, :boolean, default: true
end
def down
remove_column :projects, :request_access_enabled
end
end
class AddRequestAccessEnabledToGroups < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_column_with_default :namespaces, :request_access_enabled, :boolean, default: true
end
def down
remove_column :namespaces, :request_access_enabled
end
end
class AddHasExternalWikiToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_column :projects, :has_external_wiki, :boolean
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160716115710) do ActiveRecord::Schema.define(version: 20160718153603) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -664,16 +664,17 @@ ActiveRecord::Schema.define(version: 20160716115710) do ...@@ -664,16 +664,17 @@ ActiveRecord::Schema.define(version: 20160716115710) do
add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
create_table "namespaces", force: :cascade do |t| create_table "namespaces", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "path", null: false t.string "path", null: false
t.integer "owner_id" t.integer "owner_id"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "type" t.string "type"
t.string "description", default: "", null: false t.string "description", default: "", null: false
t.string "avatar" t.string "avatar"
t.boolean "share_with_group_lock", default: false t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: true, null: false
end end
add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
...@@ -842,6 +843,8 @@ ActiveRecord::Schema.define(version: 20160716115710) do ...@@ -842,6 +843,8 @@ ActiveRecord::Schema.define(version: 20160716115710) do
t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false
t.boolean "has_external_issue_tracker" t.boolean "has_external_issue_tracker"
t.string "repository_storage", default: "default", null: false t.string "repository_storage", default: "default", null: false
t.boolean "has_external_wiki"
t.boolean "request_access_enabled", default: true, null: false
end end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
......
...@@ -18,25 +18,35 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. ...@@ -18,25 +18,35 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables) ### Predefined variables (Environment Variables)
| Variable | Runner | Description | | Variable | GitLab | Runner | Description |
|-------------------------|-----|--------| |-------------------------|--------|--------|-------------|
| **CI** | 0.4 | Mark that build is executed in CI environment | | **CI** | all | 0.4 | Mark that build is executed in CI environment |
| **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment | | **GITLAB_CI** | all | all | Mark that build is executed in GitLab CI environment |
| **CI_SERVER** | all | Mark that build is executed in CI environment | | **CI_SERVER** | all | all | Mark that build is executed in CI environment |
| **CI_SERVER_NAME** | all | CI server that is used to coordinate builds | | **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate builds |
| **CI_SERVER_VERSION** | all | Not yet defined | | **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule builds |
| **CI_SERVER_REVISION** | all | Not yet defined | | **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule builds |
| **CI_BUILD_REF** | all | The commit revision for which project is built | | **CI_BUILD_ID** | all | all | The unique id of the current build that GitLab CI uses internally |
| **CI_BUILD_TAG** | 0.5 | The commit tag name. Present only when building tags. | | **CI_BUILD_REF** | all | all | The commit revision for which project is built |
| **CI_BUILD_NAME** | 0.5 | The name of the build as defined in `.gitlab-ci.yml` | | **CI_BUILD_TAG** | all | 0.5 | The commit tag name. Present only when building tags. |
| **CI_BUILD_STAGE** | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` | | **CI_BUILD_NAME** | all | 0.5 | The name of the build as defined in `.gitlab-ci.yml` |
| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built | | **CI_BUILD_STAGE** | all | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally | | **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built |
| **CI_BUILD_REPO** | all | The URL to clone the Git repository | | **CI_BUILD_REPO** | all | all | The URL to clone the Git repository |
| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was [triggered] | | **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that build was [triggered] |
| **CI_BUILD_TOKEN** | 1.2 | Token used for authenticating with the GitLab Container Registry | | **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry |
| **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran | | **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built |
| **CI_PROJECT_NAMESPACE**| 8.10 | 0.5 | The project namespace that is currently being built |
| **CI_PROJECT_PATH** | 8.10 | 0.5 | The namespace with project name |
| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the build is run |
| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returnes the address of the registry tied to the specific project |
| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
**Some of the variables are only available when using runner with at least defined version.** **Some of the variables are only available when using runner with at least defined version.**
...@@ -46,18 +56,28 @@ Example values: ...@@ -46,18 +56,28 @@ Example values:
export CI_BUILD_ID="50" export CI_BUILD_ID="50"
export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a" export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
export CI_BUILD_REF_NAME="master" export CI_BUILD_REF_NAME="master"
export CI_BUILD_REPO="https://gitlab.com/gitlab-org/gitlab-ce.git" export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/gitlab-org/gitlab-ce.git"
export CI_BUILD_TAG="1.0.0" export CI_BUILD_TAG="1.0.0"
export CI_BUILD_NAME="spec:other" export CI_BUILD_NAME="spec:other"
export CI_BUILD_STAGE="test" export CI_BUILD_STAGE="test"
export CI_BUILD_TRIGGERED="true" export CI_BUILD_TRIGGERED="true"
export CI_BUILD_TOKEN="abcde-1234ABCD5678ef" export CI_BUILD_TOKEN="abcde-1234ABCD5678ef"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce" export CI_PIPELINE_ID="1000"
export CI_PROJECT_ID="34" export CI_PROJECT_ID="34"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
export CI_PROJECT_NAME="gitlab-ce"
export CI_PROJECT_NAMESPACE="gitlab-org"
export CI_PROJECT_PATH="gitlab-org/gitlab-ce"
export CI_PROJECT_URL="https://gitlab.com/gitlab-org/gitlab-ce"
export CI_REGISTRY="registry.gitlab.com"
export CI_REGISTRY_IMAGE="registry.gitlab.com/gitlab-org/gitlab-ce"
export CI_RUNNER_ID="10"
export CI_RUNNER_DESCRIPTION="my runner"
export CI_RUNNER_TAGS="docker, linux"
export CI_SERVER="yes" export CI_SERVER="yes"
export CI_SERVER_NAME="GitLab CI" export CI_SERVER_NAME="GitLab"
export CI_SERVER_REVISION="" export CI_SERVER_REVISION="8.9.0"
export CI_SERVER_VERSION="" export CI_SERVER_VERSION="70606bf"
``` ```
### YAML-defined variables ### YAML-defined variables
......
...@@ -11,7 +11,8 @@ migrations are written carefully, can be applied online and adhere to the style ...@@ -11,7 +11,8 @@ migrations are written carefully, can be applied online and adhere to the style
Migrations should not require GitLab installations to be taken offline unless Migrations should not require GitLab installations to be taken offline unless
_absolutely_ necessary. If a migration requires downtime this should be _absolutely_ necessary. If a migration requires downtime this should be
clearly mentioned during the review process as well as being documented in the clearly mentioned during the review process as well as being documented in the
monthly release post. monthly release post. For more information see the "Downtime Tagging" section
below.
When writing your migrations, also consider that databases might have stale data When writing your migrations, also consider that databases might have stale data
or inconsistencies and guard for that. Try to make as little assumptions as possible or inconsistencies and guard for that. Try to make as little assumptions as possible
...@@ -20,35 +21,34 @@ about the state of the database. ...@@ -20,35 +21,34 @@ about the state of the database.
Please don't depend on GitLab specific code since it can change in future versions. Please don't depend on GitLab specific code since it can change in future versions.
If needed copy-paste GitLab code into the migration to make it forward compatible. If needed copy-paste GitLab code into the migration to make it forward compatible.
## Comments in the migration ## Downtime Tagging
Each migration you write needs to have the two following pieces of information Every migration must specify if it requires downtime or not, and if it should
as comments. require downtime it must also specify a reason for this. To do so, add the
following two constants to the migration class' body:
### Online, Offline, errors? * `DOWNTIME`: a boolean that when set to `true` indicates the migration requires
downtime.
* `DOWNTIME_REASON`: a String containing the reason for the migration requiring
downtime. This constant **must** be set when `DOWNTIME` is set to `true`.
First, you need to provide information on whether the migration can be applied: For example:
1. online without errors (works on previous version and new one) ```ruby
2. online with errors on old instances after migrating
3. online with errors on new instances while migrating
4. offline (needs to happen without app servers to prevent db corruption)
For example:
```
# Migration type: online without errors (works on previous version and new one)
class MyMigration < ActiveRecord::Migration class MyMigration < ActiveRecord::Migration
... DOWNTIME = true
``` DOWNTIME_REASON = 'This migration requires downtime because ...'
It is always preferable to have a migration run online. If you expect the migration def change
to take particularly long (for instance, if it loops through all notes), ...
this is valuable information to add. end
end
```
If you don't provide the information it means that a migration is safe to run online. It is an error (that is, CI will fail) if the `DOWNTIME` constant is missing
from a migration class.
### Reversibility ## Reversibility
Your migration should be reversible. This is very important, as it should Your migration should be reversible. This is very important, as it should
be possible to downgrade in case of a vulnerability or bugs. be possible to downgrade in case of a vulnerability or bugs.
...@@ -100,7 +100,7 @@ value of `10` you'd write the following: ...@@ -100,7 +100,7 @@ value of `10` you'd write the following:
class MyMigration < ActiveRecord::Migration class MyMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:projects, :foo, :integer, default: 10) add_column_with_default(:projects, :foo, :integer, default: 10)
end end
......
...@@ -26,14 +26,13 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this ...@@ -26,14 +26,13 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
1. Navigate to Settings -> Services -> Slack 1. Navigate to Settings -> Services -> Slack
1. Pick the triggers you want to activate 1. Pick the triggers you want to activate and respective channel (`#general` by default).
1. Fill in your Slack details 1. Fill in your Slack details
- Webhook: Paste the Webhook URL from the step above - Webhook: Paste the Webhook URL from the step above
- Username: Fill this in if you want to change the username of the bot - Username: Fill this in if you want to change the username of the bot
- Channel: Fill this in if you want to change the channel where the messages will be posted
- Mark it as active - Mark it as active
1. Save your settings 1. Save your settings
Have fun :) Have fun :)
......
...@@ -45,7 +45,7 @@ further configuration instructions and details. Contributions are welcome. ...@@ -45,7 +45,7 @@ further configuration instructions and details. Contributions are welcome.
| PivotalTracker | Project Management Software (Source Commits Endpoint) | | PivotalTracker | Project Management Software (Source Commits Endpoint) |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop | | Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
| [Redmine](redmine.md) | Redmine issue tracker | | [Redmine](redmine.md) | Redmine issue tracker |
| Slack | A team communication tool for the 21st century | | [Slack](slack.md) | A team communication tool for the 21st century |
## Services Templates ## Services Templates
......
# Slack Service
Go to your project's **Settings > Services > Slack** and you will see a checkbox with the following events that can be triggered:
* Push
* Issue
* Merge request
* Note
* Tag push
* Build
* Wiki page
Below each of these event checkboxes you will have an input to insert which Slack channel do you want to send that event message,
`#general` channel is default.
![Slack configuration](img/slack_configuration.png)
| Field | Description |
| ----- | ----------- |
| `Webhook` | The incoming webhook url which you have to setup on slack. (https://my.slack.com/services/new/incoming-webhook/) |
| `Username` | Optional username which can be on messages sent to slack. |
| `notify only broken builds` | Notify only about broken builds, when build events are marked to be sent.|
...@@ -90,6 +90,9 @@ GitLab account using the same e-mail address the invitation was sent to. ...@@ -90,6 +90,9 @@ GitLab account using the same e-mail address the invitation was sent to.
## Request access to a project ## Request access to a project
As a project owner you can enable or disable non members to request access to
your project. Go to the project settings and click on **Allow users to request access**.
As a user, you can request to be a member of a project. Go to the project you'd As a user, you can request to be a member of a project. Go to the project you'd
like to be a member of, and click the **Request Access** button on the right like to be a member of, and click the **Request Access** button on the right
side of your screen. side of your screen.
......
...@@ -53,6 +53,9 @@ If necessary, you can increase the access level of an individual user for a spec ...@@ -53,6 +53,9 @@ If necessary, you can increase the access level of an individual user for a spec
## Requesting access to a group ## Requesting access to a group
As a group owner you can enable or disable non members to request access to
your group. Go to the group settings and click on **Allow users to request access**.
As a user, you can request to be a member of a group. Go to the group you'd As a user, you can request to be a member of a group. Go to the group you'd
like to be a member of, and click the **Request Access** button on the right like to be a member of, and click the **Request Access** button on the right
side of your screen. side of your screen.
......
...@@ -27,19 +27,19 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps ...@@ -27,19 +27,19 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
step 'I check all events and submit form' do step 'I check all events and submit form' do
page.check('Active') page.check('Active')
page.check('Push events') page.check('Push')
page.check('Tag push events') page.check('Tag push')
page.check('Comments') page.check('Note')
page.check('Issues events') page.check('Issue')
page.check('Merge Request events') page.check('Merge request')
page.check('Build events') page.check('Build')
click_on 'Save' click_on 'Save'
end end
step 'I fill out Slack settings' do step 'I fill out Slack settings' do
fill_in 'Webhook', with: 'http://localhost' fill_in 'Webhook', with: 'http://localhost'
fill_in 'Username', with: 'test_user' fill_in 'Username', with: 'test_user'
fill_in 'Channel', with: '#test_channel' fill_in 'service_push_channel', with: '#test_channel'
page.check('Notify only broken builds') page.check('Notify only broken builds')
end end
...@@ -56,6 +56,6 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps ...@@ -56,6 +56,6 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
step 'I should see Slack settings saved' do step 'I should see Slack settings saved' do
expect(find_field('Webhook').value).to eq 'http://localhost' expect(find_field('Webhook').value).to eq 'http://localhost'
expect(find_field('Username').value).to eq 'test_user' expect(find_field('Username').value).to eq 'test_user'
expect(find_field('Channel').value).to eq '#test_channel' expect(find('#service_push_channel').value).to eq '#test_channel'
end end
end end
...@@ -4,6 +4,14 @@ ...@@ -4,6 +4,14 @@
class <%= migration_class_name %> < ActiveRecord::Migration class <%= migration_class_name %> < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default" # When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an # you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this # existing transaction. When using "add_concurrent_index" make sure that this
......
...@@ -4,6 +4,14 @@ ...@@ -4,6 +4,14 @@
class <%= migration_class_name %> < ActiveRecord::Migration class <%= migration_class_name %> < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default" # When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an # you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this # existing transaction. When using "add_concurrent_index" make sure that this
......
...@@ -24,7 +24,7 @@ module API ...@@ -24,7 +24,7 @@ module API
pipelines = user_project.pipelines.where(sha: params[:sha]) pipelines = user_project.pipelines.where(sha: params[:sha])
statuses = ::CommitStatus.where(pipeline: pipelines) statuses = ::CommitStatus.where(pipeline: pipelines)
statuses = statuses.latest unless parse_boolean(params[:all]) statuses = statuses.latest unless to_boolean(params[:all])
statuses = statuses.where(ref: params[:ref]) if params[:ref].present? statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
statuses = statuses.where(stage: params[:stage]) if params[:stage].present? statuses = statuses.where(stage: params[:stage]) if params[:stage].present?
statuses = statuses.where(name: params[:name]) if params[:name].present? statuses = statuses.where(name: params[:name]) if params[:name].present?
......
...@@ -5,10 +5,6 @@ module API ...@@ -5,10 +5,6 @@ module API
SUDO_HEADER = "HTTP_SUDO" SUDO_HEADER = "HTTP_SUDO"
SUDO_PARAM = :sudo SUDO_PARAM = :sudo
def parse_boolean(value)
[ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value)
end
def to_boolean(value) def to_boolean(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i return true if value =~ /^(true|t|yes|y|1|on)$/i
return false if value =~ /^(false|f|no|n|0|off)$/i return false if value =~ /^(false|f|no|n|0|off)$/i
...@@ -297,7 +293,7 @@ module API ...@@ -297,7 +293,7 @@ module API
def filter_projects(projects) def filter_projects(projects)
# If the archived parameter is passed, limit results accordingly # If the archived parameter is passed, limit results accordingly
if params[:archived].present? if params[:archived].present?
projects = projects.where(archived: parse_boolean(params[:archived])) projects = projects.where(archived: to_boolean(params[:archived]))
end end
if params[:search].present? if params[:search].present?
......
...@@ -242,7 +242,7 @@ module API ...@@ -242,7 +242,7 @@ module API
should_remove_source_branch: params[:should_remove_source_branch] should_remove_source_branch: params[:should_remove_source_branch]
} }
if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.pipeline && merge_request.pipeline.active? if to_boolean(params[:merge_when_build_succeeds]) && merge_request.pipeline && merge_request.pipeline.active?
::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
execute(merge_request) execute(merge_request)
else else
......
...@@ -8,7 +8,7 @@ module API ...@@ -8,7 +8,7 @@ module API
def map_public_to_visibility_level(attrs) def map_public_to_visibility_level(attrs)
publik = attrs.delete(:public) publik = attrs.delete(:public)
if publik.present? && !attrs[:visibility_level].present? if publik.present? && !attrs[:visibility_level].present?
publik = parse_boolean(publik) publik = to_boolean(publik)
# Since setting the public attribute to private could mean either # Since setting the public attribute to private could mean either
# private or internal, use the more conservative option, private. # private or internal, use the more conservative option, private.
attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
......
...@@ -112,8 +112,7 @@ module Banzai ...@@ -112,8 +112,7 @@ module Banzai
end end
def current_commit def current_commit
@current_commit ||= context[:commit] || @current_commit ||= context[:commit] || ref ? repository.commit(ref) : repository.head_commit
ref ? repository.commit(ref) : repository.head_commit
end end
def relative_url_root def relative_url_root
......
...@@ -44,23 +44,51 @@ module Ci ...@@ -44,23 +44,51 @@ module Ci
end end
def builds_for_ref(ref, tag = false, trigger_request = nil) def builds_for_ref(ref, tag = false, trigger_request = nil)
jobs_for_ref(ref, tag, trigger_request).map do |name, job| jobs_for_ref(ref, tag, trigger_request).map do |name, _|
build_job(name, job) build_attributes(name)
end end
end end
def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil) def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, job| jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
build_job(name, job) build_attributes(name)
end end
end end
def builds def builds
@jobs.map do |name, job| @jobs.map do |name, _|
build_job(name, job) build_attributes(name)
end end
end end
def build_attributes(name)
job = @jobs[name.to_sym] || {}
{
stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
##
# Refactoring note:
# - before script behaves differently than after script
# - after script returns an array of commands
# - before script should be a concatenated command
commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
tag_list: job[:tags] || [],
name: name,
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
yaml_variables: yaml_variables(name),
options: {
image: job[:image] || @image,
services: job[:services] || @services,
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
after_script: job[:after_script] || @after_script,
}.compact
}
end
private private
def initial_parsing def initial_parsing
...@@ -89,33 +117,6 @@ module Ci ...@@ -89,33 +117,6 @@ module Ci
@jobs[name] = { stage: stage }.merge(job) @jobs[name] = { stage: stage }.merge(job)
end end
def build_job(name, job)
{
stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
##
# Refactoring note:
# - before script behaves differently than after script
# - after script returns an array of commands
# - before script should be a concatenated command
commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
tag_list: job[:tags] || [],
name: name,
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
yaml_variables: yaml_variables(name),
options: {
image: job[:image] || @image,
services: job[:services] || @services,
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
after_script: job[:after_script] || @after_script,
}.compact
}
end
def yaml_variables(name) def yaml_variables(name)
variables = global_variables.merge(job_variables(name)) variables = global_variables.merge(job_variables(name))
variables.map do |key, value| variables.map do |key, value|
......
...@@ -8,8 +8,8 @@ module Gitlab ...@@ -8,8 +8,8 @@ module Gitlab
if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev) if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
false false
else else
missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})) missed_ref, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list --max-count=1 #{oldrev} ^#{newrev}))
missed_refs.split("\n").size > 0 missed_ref.present?
end end
end end
end end
......
...@@ -73,8 +73,8 @@ module Gitlab ...@@ -73,8 +73,8 @@ module Gitlab
diff_refs.complete? diff_refs.complete?
end end
def to_json def to_json(opts = nil)
JSON.generate(self.to_h) JSON.generate(self.to_h, opts)
end end
def type def type
......
module Gitlab
# Checks if a set of migrations requires downtime or not.
class DowntimeCheck
# The constant containing the boolean that indicates if downtime is needed
# or not.
DOWNTIME_CONST = :DOWNTIME
# The constant that specifies the reason for the migration requiring
# downtime.
DOWNTIME_REASON_CONST = :DOWNTIME_REASON
# Checks the given migration paths and returns an Array of
# `Gitlab::DowntimeCheck::Message` instances.
#
# migrations - The migration file paths to check.
def check(migrations)
migrations.map do |path|
require(path)
migration_class = class_for_migration_file(path)
unless migration_class.const_defined?(DOWNTIME_CONST)
raise "The migration in #{path} does not specify if it requires " \
"downtime or not"
end
if online?(migration_class)
Message.new(path)
else
reason = downtime_reason(migration_class)
unless reason
raise "The migration in #{path} requires downtime but no reason " \
"was given"
end
Message.new(path, true, reason)
end
end
end
# Checks the given migrations and prints the results to STDOUT/STDERR.
#
# migrations - The migration file paths to check.
def check_and_print(migrations)
check(migrations).each do |message|
puts message.to_s # rubocop: disable Rails/Output
end
end
# Returns the class for the given migration file path.
def class_for_migration_file(path)
File.basename(path, File.extname(path)).split('_', 2).last.camelize.
constantize
end
# Returns true if the given migration can be performed without downtime.
def online?(migration)
migration.const_get(DOWNTIME_CONST) == false
end
# Returns the downtime reason, or nil if none was defined.
def downtime_reason(migration)
if migration.const_defined?(DOWNTIME_REASON_CONST)
migration.const_get(DOWNTIME_REASON_CONST)
else
nil
end
end
end
end
module Gitlab
class DowntimeCheck
class Message
attr_reader :path, :offline, :reason
OFFLINE = "\e[32moffline\e[0m"
ONLINE = "\e[31monline\e[0m"
# path - The file path of the migration.
# offline - When set to `true` the migration will require downtime.
# reason - The reason as to why the migration requires downtime.
def initialize(path, offline = false, reason = nil)
@path = path
@offline = offline
@reason = reason
end
def to_s
label = offline ? OFFLINE : ONLINE
message = "[#{label}]: #{path}"
message += ": #{reason}" if reason
message
end
end
end
end
...@@ -110,6 +110,7 @@ module Gitlab ...@@ -110,6 +110,7 @@ module Gitlab
def deploy_key_can_read_project? def deploy_key_can_read_project?
if deploy_key if deploy_key
return true if project.public?
deploy_key.projects.include?(project) deploy_key.projects.include?(project)
else else
false false
......
...@@ -8,8 +8,8 @@ module Gitlab ...@@ -8,8 +8,8 @@ module Gitlab
@message = message @message = message
end end
def to_json def to_json(opts = nil)
{ status: @status, message: @message }.to_json { status: @status, message: @message }.to_json(opts)
end end
end end
end end
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_shortcuts_path gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.award_menu_url = emojis_path gon.award_menu_url = emojis_path
......
module Gitlab
module ImportExport
class AvatarRestorer
def initialize(project:, shared:)
@project = project
@shared = shared
end
def restore
return true unless avatar_export_file
@project.avatar = File.open(avatar_export_file)
@project.save!
rescue => e
@shared.error(e)
false
end
private
def avatar_export_file
@avatar_export_file ||= Dir["#{avatar_export_path}/*"].first
end
def avatar_export_path
File.join(@shared.export_path, 'avatar')
end
end
end
end
module Gitlab
module ImportExport
class AvatarSaver
include Gitlab::ImportExport::CommandLineUtil
def initialize(project:, shared:)
@project = project
@shared = shared
end
def save
return true unless @project.avatar.exists?
copy_files(avatar_path, avatar_export_path)
rescue => e
@shared.error(e)
false
end
private
def avatar_export_path
File.join(@shared.export_path, 'avatar', @project.avatar_identifier)
end
def avatar_path
@project.avatar.path
end
end
end
end
...@@ -36,6 +36,15 @@ module Gitlab ...@@ -36,6 +36,15 @@ module Gitlab
def git_bin_path def git_bin_path
Gitlab.config.git.bin_path Gitlab.config.git.bin_path
end end
def copy_files(source, destination)
# if we are copying files, create the destination folder
destination_folder = File.file?(source) ? File.dirname(destination) : destination
FileUtils.mkdir_p(destination_folder)
FileUtils.copy_entry(source, destination)
true
end
end end
end end
end end
...@@ -9,7 +9,7 @@ module Gitlab ...@@ -9,7 +9,7 @@ module Gitlab
end end
def execute def execute
if import_file && check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore) if import_file && check_version! && [project_tree, avatar_restorer, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
project_tree.restored_project project_tree.restored_project
else else
raise Projects::ImportService::Error.new(@shared.errors.join(', ')) raise Projects::ImportService::Error.new(@shared.errors.join(', '))
...@@ -35,6 +35,10 @@ module Gitlab ...@@ -35,6 +35,10 @@ module Gitlab
project: @project) project: @project)
end end
def avatar_restorer
Gitlab::ImportExport::AvatarRestorer.new(project: project_tree.restored_project, shared: @shared)
end
def repo_restorer def repo_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path, Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path,
shared: @shared, shared: @shared,
......
module Gitlab module Gitlab
module ImportExport module ImportExport
class UploadsSaver class UploadsSaver
include Gitlab::ImportExport::CommandLineUtil
def initialize(project:, shared:) def initialize(project:, shared:)
@project = project @project = project
@shared = shared @shared = shared
...@@ -17,12 +19,6 @@ module Gitlab ...@@ -17,12 +19,6 @@ module Gitlab
private private
def copy_files(source, destination)
FileUtils.mkdir_p(destination)
FileUtils.copy_entry(source, destination)
true
end
def uploads_export_path def uploads_export_path
File.join(@shared.export_path, 'uploads') File.join(@shared.export_path, 'uploads')
end end
......
desc 'Checks if migrations in a branch require downtime'
task downtime_check: :environment do
# First we'll want to make sure we're comparing with the right upstream
# repository/branch.
current_branch = `git rev-parse --abbrev-ref HEAD`.strip
# Either the developer ran this task directly on the master branch, or they're
# making changes directly on the master branch.
if current_branch == 'master'
if defined?(Gitlab::License)
repo = 'gitlab-ee'
else
repo = 'gitlab-ce'
end
`git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1`
compare_with = 'FETCH_HEAD'
# The developer is working on a different branch, in this case we can just
# compare with the master branch.
else
compare_with = 'master'
end
Rake::Task['gitlab:db:downtime_check'].invoke(compare_with)
end
...@@ -784,7 +784,7 @@ namespace :gitlab do ...@@ -784,7 +784,7 @@ namespace :gitlab do
servers.each do |server| servers.each do |server|
puts "Server: #{server}" puts "Server: #{server}"
Gitlab::LDAP::Adapter.open(server) do |adapter| Gitlab::LDAP::Adapter.open(server) do |adapter|
users = adapter.users(adapter.config.uid, '*', 100) users = adapter.users(adapter.config.uid, '*', limit)
users.each do |user| users.each do |user|
puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}" puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
end end
......
...@@ -46,5 +46,20 @@ namespace :gitlab do ...@@ -46,5 +46,20 @@ namespace :gitlab do
Rake::Task['db:seed_fu'].invoke Rake::Task['db:seed_fu'].invoke
end end
end end
desc 'Checks if migrations require downtime or not'
task :downtime_check, [:ref] => :environment do |_, args|
abort 'You must specify a Git reference to compare with' unless args[:ref]
require 'shellwords'
ref = Shellwords.escape(args[:ref])
migrations = `git diff #{ref}.. --name-only -- db/migrate`.lines.
map { |file| Rails.root.join(file.strip).to_s }.
select { |file| File.file?(file) }
Gitlab::DowntimeCheck.new.check_and_print(migrations)
end
end end
end end
...@@ -63,4 +63,13 @@ describe HelpController do ...@@ -63,4 +63,13 @@ describe HelpController do
end end
end end
end end
describe 'GET #ui' do
context 'for UI Development Kit' do
it 'renders found' do
get :ui
expect(response).to have_http_status(200)
end
end
end
end end
...@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess ...@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess
FactoryGirl.define do FactoryGirl.define do
factory :ci_build, class: Ci::Build do factory :ci_build, class: Ci::Build do
name 'test' name 'test'
stage 'test'
stage_idx 0
ref 'master' ref 'master'
tag false tag false
created_at 'Di 29. Okt 09:50:00 CET 2013' created_at 'Di 29. Okt 09:50:00 CET 2013'
......
...@@ -12,6 +12,13 @@ feature 'Groups > Members > User requests access', feature: true do ...@@ -12,6 +12,13 @@ feature 'Groups > Members > User requests access', feature: true do
visit group_path(group) visit group_path(group)
end end
scenario 'request access feature is disabled' do
group.update_attributes(request_access_enabled: false)
visit group_path(group)
expect(page).not_to have_content 'Request Access'
end
scenario 'user can request access to a group' do scenario 'user can request access to a group' do
perform_enqueued_jobs { click_link 'Request Access' } perform_enqueued_jobs { click_link 'Request Access' }
......
require 'spec_helper'
feature "Pipelines settings", feature: true do
include GitlabRoutingHelper
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:role) { :developer }
background do
login_as(user)
project.team << [user, role]
visit namespace_project_pipelines_settings_path(project.namespace, project)
end
context 'for developer' do
given(:role) { :developer }
scenario 'to be disallowed to view' do
expect(page.status_code).to eq(404)
end
end
context 'for master' do
given(:role) { :master }
scenario 'be allowed to change' do
fill_in('Test coverage parsing', with: 'coverage_regex')
click_on 'Save changes'
expect(page.status_code).to eq(200)
expect(page).to have_field('Test coverage parsing', with: 'coverage_regex')
end
end
end
...@@ -6,7 +6,7 @@ feature 'list of badges' do ...@@ -6,7 +6,7 @@ feature 'list of badges' do
project = create(:project) project = create(:project)
project.team << [user, :master] project.team << [user, :master]
login_as(user) login_as(user)
visit namespace_project_badges_path(project.namespace, project) visit namespace_project_pipelines_settings_path(project.namespace, project)
end end
scenario 'user displays list of badges' do scenario 'user displays list of badges' do
......
...@@ -11,6 +11,13 @@ feature 'Projects > Members > User requests access', feature: true do ...@@ -11,6 +11,13 @@ feature 'Projects > Members > User requests access', feature: true do
visit namespace_project_path(project.namespace, project) visit namespace_project_path(project.namespace, project)
end end
scenario 'request access feature is disabled' do
project.update_attributes(request_access_enabled: false)
visit namespace_project_path(project.namespace, project)
expect(page).not_to have_content 'Request Access'
end
scenario 'user can request access to a project' do scenario 'user can request access to a project' do
perform_enqueued_jobs { click_link 'Request Access' } perform_enqueued_jobs { click_link 'Request Access' }
......
require 'spec_helper'
feature 'Projects > Slack service > Setup events', feature: true do
let(:user) { create(:user) }
let(:service) { SlackService.new }
let(:project) { create(:project, slack_service: service) }
background do
service.fields
service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, build_channel: 6, wiki_page_channel: 7)
project.team << [user, :master]
login_as(user)
end
scenario 'user can filter events by channel' do
visit edit_namespace_project_service_path(project.namespace, project, service)
expect(page.find_field("service_push_channel").value).to have_content '1'
expect(page.find_field("service_issue_channel").value).to have_content '2'
expect(page.find_field("service_merge_request_channel").value).to have_content '3'
expect(page.find_field("service_note_channel").value).to have_content '4'
expect(page.find_field("service_tag_push_channel").value).to have_content '5'
expect(page.find_field("service_build_channel").value).to have_content '6'
expect(page.find_field("service_wiki_page_channel").value).to have_content '7'
end
end
...@@ -7,7 +7,13 @@ describe CiStatusHelper do ...@@ -7,7 +7,13 @@ describe CiStatusHelper do
let(:failed_commit) { double("Ci::Pipeline", status: 'failed') } let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
describe 'ci_icon_for_status' do describe 'ci_icon_for_status' do
it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') } it 'renders to correct svg on success' do
it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') } expect(helper).to receive(:render).with('shared/icons/icon_status_success.svg', anything)
helper.ci_icon_for_status(success_commit.status)
end
it 'renders the correct svg on failure' do
expect(helper).to receive(:render).with('shared/icons/icon_status_failed.svg', anything)
helper.ci_icon_for_status(failed_commit.status)
end
end end
end end
require 'spec_helper' require 'spec_helper'
describe TimeHelper do describe TimeHelper do
describe "#duration_in_words" do describe "#time_interval_in_words" do
it "returns minutes and seconds" do it "returns minutes and seconds" do
intervals_in_words = { intervals_in_words = {
100 => "1 minute 40 seconds", 100 => "1 minute 40 seconds",
...@@ -11,26 +11,23 @@ describe TimeHelper do ...@@ -11,26 +11,23 @@ describe TimeHelper do
} }
intervals_in_words.each do |interval, expectation| intervals_in_words.each do |interval, expectation|
expect(duration_in_words(Time.now + interval, Time.now)).to eq(expectation) expect(time_interval_in_words(interval)).to eq(expectation)
end end
end end
it "calculates interval from now if there is no finished_at" do
expect(duration_in_words(nil, Time.now - 5)).to eq("5 seconds")
end
end end
describe "#time_interval_in_words" do describe "#duration_in_numbers" do
it "returns minutes and seconds" do it "returns minutes and seconds" do
intervals_in_words = { duration_in_numbers = {
100 => "1 minute 40 seconds", [100, 0] => "01:40",
121 => "2 minutes 1 second", [121, 0] => "02:01",
3721 => "62 minutes 1 second", [3721, 0] => "01:02:01",
0 => "0 seconds" [0, 0] => "00:00",
[nil, Time.now.to_i - 42] => "00:42"
} }
intervals_in_words.each do |interval, expectation| duration_in_numbers.each do |interval, expectation|
expect(time_interval_in_words(interval)).to eq(expectation) expect(duration_in_numbers(*interval)).to eq(expectation)
end end
end end
end end
......
...@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do ...@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do
sha: sha, sha: sha,
ref: branch) ref: branch)
create(:ci_build, pipeline: pipeline) create(:ci_build, pipeline: pipeline, stage: 'notify')
end end
def status_node(data, status) def status_node(data, status)
......
...@@ -338,4 +338,28 @@ describe Gitlab::Diff::Position, lib: true do ...@@ -338,4 +338,28 @@ describe Gitlab::Diff::Position, lib: true do
end end
end end
end end
describe "#to_json" do
let(:hash) do
{
old_path: "files/ruby/popen.rb",
new_path: "files/ruby/popen.rb",
old_line: nil,
new_line: 14,
base_sha: nil,
head_sha: nil,
start_sha: nil
}
end
let(:diff_position) { described_class.new(hash) }
it "returns the position as JSON" do
expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys)
end
it "works when nested under another hash" do
expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys)
end
end
end end
require 'spec_helper'
describe Gitlab::DowntimeCheck::Message do
describe '#to_s' do
it 'returns an ANSI formatted String for an offline migration' do
message = described_class.new('foo.rb', true, 'hello')
expect(message.to_s).to eq("[\e[32moffline\e[0m]: foo.rb: hello")
end
it 'returns an ANSI formatted String for an online migration' do
message = described_class.new('foo.rb')
expect(message.to_s).to eq("[\e[31monline\e[0m]: foo.rb")
end
end
end
require 'spec_helper'
describe Gitlab::DowntimeCheck do
subject { described_class.new }
let(:path) { 'foo.rb' }
describe '#check' do
before do
expect(subject).to receive(:require).with(path)
end
context 'when a migration does not specify if downtime is required' do
it 'raises RuntimeError' do
expect(subject).to receive(:class_for_migration_file).
with(path).
and_return(Class.new)
expect { subject.check([path]) }.
to raise_error(RuntimeError, /it requires downtime/)
end
end
context 'when a migration requires downtime' do
context 'when no reason is specified' do
it 'raises RuntimeError' do
stub_const('TestMigration::DOWNTIME', true)
expect(subject).to receive(:class_for_migration_file).
with(path).
and_return(TestMigration)
expect { subject.check([path]) }.
to raise_error(RuntimeError, /no reason was given/)
end
end
context 'when a reason is specified' do
it 'returns an Array of messages' do
stub_const('TestMigration::DOWNTIME', true)
stub_const('TestMigration::DOWNTIME_REASON', 'foo')
expect(subject).to receive(:class_for_migration_file).
with(path).
and_return(TestMigration)
messages = subject.check([path])
expect(messages).to be_an_instance_of(Array)
expect(messages[0]).to be_an_instance_of(Gitlab::DowntimeCheck::Message)
message = messages[0]
expect(message.path).to eq(path)
expect(message.offline).to eq(true)
expect(message.reason).to eq('foo')
end
end
end
end
describe '#check_and_print' do
it 'checks the migrations and prints the results to STDOUT' do
stub_const('TestMigration::DOWNTIME', true)
stub_const('TestMigration::DOWNTIME_REASON', 'foo')
expect(subject).to receive(:require).with(path)
expect(subject).to receive(:class_for_migration_file).
with(path).
and_return(TestMigration)
expect(subject).to receive(:puts).with(an_instance_of(String))
subject.check_and_print([path])
end
end
describe '#class_for_migration_file' do
it 'returns the class for a migration file path' do
expect(subject.class_for_migration_file('123_string.rb')).to eq(String)
end
end
describe '#online?' do
it 'returns true when a migration can be performed online' do
stub_const('TestMigration::DOWNTIME', false)
expect(subject.online?(TestMigration)).to eq(true)
end
it 'returns false when a migration can not be performed online' do
stub_const('TestMigration::DOWNTIME', true)
expect(subject.online?(TestMigration)).to eq(false)
end
end
describe '#downtime_reason' do
context 'when a reason is defined' do
it 'returns the downtime reason' do
stub_const('TestMigration::DOWNTIME_REASON', 'hello')
expect(subject.downtime_reason(TestMigration)).to eq('hello')
end
end
context 'when a reason is not defined' do
it 'returns nil' do
expect(subject.downtime_reason(Class.new)).to be_nil
end
end
end
end
...@@ -44,12 +44,12 @@ describe Gitlab::GitAccess, lib: true do ...@@ -44,12 +44,12 @@ describe Gitlab::GitAccess, lib: true do
end end
describe 'download_access_check' do describe 'download_access_check' do
subject { access.check('git-upload-pack') }
describe 'master permissions' do describe 'master permissions' do
before { project.team << [user, :master] } before { project.team << [user, :master] }
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_truthy } it { expect(subject.allowed?).to be_truthy }
end end
end end
...@@ -58,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do ...@@ -58,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :guest] } before { project.team << [user, :guest] }
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey } it { expect(subject.allowed?).to be_falsey }
end end
end end
...@@ -71,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do ...@@ -71,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do
end end
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey } it { expect(subject.allowed?).to be_falsey }
end end
end end
describe 'without acccess to project' do describe 'without acccess to project' do
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey } it { expect(subject.allowed?).to be_falsey }
end end
end end
...@@ -90,10 +84,31 @@ describe Gitlab::GitAccess, lib: true do ...@@ -90,10 +84,31 @@ describe Gitlab::GitAccess, lib: true do
let(:actor) { key } let(:actor) { key }
context 'pull code' do context 'pull code' do
before { key.projects << project } context 'when project is authorized' do
subject { access.download_access_check } before { key.projects << project }
it { expect(subject.allowed?).to be_truthy } it { expect(subject).to be_allowed }
end
context 'when unauthorized' do
context 'from public project' do
let(:project) { create(:project, :public) }
it { expect(subject).to be_allowed }
end
context 'from internal project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
context 'from private project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
end
end end
end end
end end
...@@ -240,5 +255,40 @@ describe Gitlab::GitAccess, lib: true do ...@@ -240,5 +255,40 @@ describe Gitlab::GitAccess, lib: true do
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
end end
end end
describe 'deploy key permissions' do
let(:key) { create(:deploy_key) }
let(:actor) { key }
context 'push code' do
subject { access.check('git-receive-pack') }
context 'when project is authorized' do
before { key.projects << project }
it { expect(subject).not_to be_allowed }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public) }
it { expect(subject).not_to be_allowed }
end
context 'to internal project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
context 'to private project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
end
end
end
end end
end end
require 'spec_helper'
describe Gitlab::ImportExport::AvatarRestorer, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:project) { create(:empty_project) }
before do
allow_any_instance_of(described_class).to receive(:avatar_export_file)
.and_return(Rails.root + "spec/fixtures/dk.png")
end
after do
project.remove_avatar!
end
it 'restores a project avatar' do
expect(described_class.new(project: project, shared: shared).restore).to be true
end
it 'saves the avatar into the project' do
described_class.new(project: project, shared: shared).restore
expect(project.reload.avatar.file.exists?).to be true
end
end
require 'spec_helper'
describe Gitlab::ImportExport::AvatarSaver, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:project) { create(:empty_project) }
before do
FileUtils.mkdir_p("#{shared.export_path}/avatar/")
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
after do
FileUtils.rm_rf("#{shared.export_path}/avatar")
end
it 'saves a project avatar' do
described_class.new(project: project_with_avatar, shared: shared).save
expect(File).to exist("#{shared.export_path}/avatar/dk.png")
end
it 'is fine not to have an avatar' do
expect(described_class.new(project: project, shared: shared).save).to be true
end
end
...@@ -193,77 +193,185 @@ describe Ci::Build, models: true do ...@@ -193,77 +193,185 @@ describe Ci::Build, models: true do
end end
describe '#variables' do describe '#variables' do
let(:container_registry_enabled) { false }
let(:predefined_variables) do
[
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
{ key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
{ key: 'CI_BUILD_TOKEN', value: build.token, public: false },
{ key: 'CI_BUILD_REF', value: build.sha, public: true },
{ key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
{ key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
{ key: 'CI_BUILD_NAME', value: 'test', public: true },
{ key: 'CI_BUILD_STAGE', value: 'test', public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
]
end
before do
stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
end
subject { build.variables }
context 'returns variables' do context 'returns variables' do
subject { build.variables } before do
build.yaml_variables = []
end
let(:predefined_variables) do it { is_expected.to eq(predefined_variables) }
[ end
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'stage', public: true }, context 'when build is for tag' do
] let(:tag_variable) do
{ key: 'CI_BUILD_TAG', value: 'master', public: true }
end end
let(:yaml_variables) do before do
[ build.update_attributes(tag: true)
{ key: :DB_NAME, value: 'postgres', public: true } end
]
it { is_expected.to include(tag_variable) }
end
context 'when secure variable is defined' do
let(:secure_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end end
before do before do
build.update_attributes(stage: 'stage', yaml_variables: yaml_variables) build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end end
it { is_expected.to eq(predefined_variables + yaml_variables) } it { is_expected.to include(secure_variable) }
end
context 'for tag' do context 'when build is for triggers' do
let(:tag_variable) do let(:trigger) { create(:ci_trigger, project: project) }
[ let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
{ key: :CI_BUILD_TAG, value: 'master', public: true } let(:user_trigger_variable) do
] { key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
end end
let(:predefined_trigger_variable) do
{ key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
end
before do before do
build.update_attributes(tag: true) build.trigger_request = trigger_request
end end
it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) } it { is_expected.to include(user_trigger_variable) }
it { is_expected.to include(predefined_trigger_variable) }
end
context 'when yaml_variables are undefined' do
before do
build.yaml_variables = nil
end end
context 'and secure variables' do context 'use from gitlab-ci.yml' do
let(:secure_variables) do before do
[ stub_ci_pipeline_yaml_file(config)
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
]
end end
before do context 'if config is not found' do
build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value') let(:config) { nil }
it { is_expected.to eq(predefined_variables) }
end end
it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) } context 'if config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
context 'and trigger variables' do it { is_expected.to eq(predefined_variables) }
let(:trigger) { create(:ci_trigger, project: project) } end
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
let(:trigger_variables) do context 'if config has variables' do
[ let(:config) do
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false } YAML.dump({
] test: {
script: 'Hello World',
variables: {
KEY: 'value'
}
}
})
end end
let(:predefined_trigger_variable) do let(:variables) do
[ [{ key: :KEY, value: 'value', public: true }]
{ key: :CI_BUILD_TRIGGERED, value: 'true', public: true }
]
end end
before do it { is_expected.to eq(predefined_variables + variables) }
build.trigger_request = trigger_request end
end end
end
it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) } context 'when container registry is enabled' do
let(:container_registry_enabled) { true }
let(:ci_registry) do
{ key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
end
let(:ci_registry_image) do
{ key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true }
end
context 'and is disabled for project' do
before do
project.update(container_registry_enabled: false)
end
it { is_expected.to include(ci_registry) }
it { is_expected.not_to include(ci_registry_image) }
end
context 'and is enabled for project' do
before do
project.update(container_registry_enabled: true)
end end
it { is_expected.to include(ci_registry) }
it { is_expected.to include(ci_registry_image) }
end end
end end
context 'when runner is assigned to build' do
let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) }
before do
build.update(runner: runner)
end
it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
end
context 'returns variables in valid order' do
before do
allow(build).to receive(:predefined_variables) { ['predefined'] }
allow(project).to receive(:predefined_variables) { ['project'] }
allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
allow(build).to receive(:yaml_variables) { ['yaml'] }
allow(project).to receive(:secret_variables) { ['secret'] }
end
it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
end
end end
describe '#has_tags?' do describe '#has_tags?' do
...@@ -703,6 +811,22 @@ describe Ci::Build, models: true do ...@@ -703,6 +811,22 @@ describe Ci::Build, models: true do
it 'returns other actions' do it 'returns other actions' do
is_expected.to contain_exactly(other_build) is_expected.to contain_exactly(other_build)
end end
context 'when build is retried' do
let!(:new_build) { Ci::Build.retry(build) }
it 'does not return any of them' do
is_expected.not_to include(build, new_build)
end
end
context 'when other build is retried' do
let!(:retried_build) { Ci::Build.retry(other_build) }
it 'returns a retried build' do
is_expected.to contain_exactly(retried_build)
end
end
end end
describe '#play' do describe '#play' do
...@@ -724,4 +848,69 @@ describe Ci::Build, models: true do ...@@ -724,4 +848,69 @@ describe Ci::Build, models: true do
end end
end end
end end
describe '#when' do
subject { build.when }
context 'if is undefined' do
before do
build.when = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'if config is not found' do
let(:config) { nil }
it { is_expected.to eq('on_success') }
end
context 'if config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq('on_success') }
end
context 'if config has when' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
when: 'always'
}
})
end
it { is_expected.to eq('always') }
end
end
end
end
describe '#retryable?' do
context 'when build is running' do
before { build.run! }
it 'should return false' do
expect(build.retryable?).to be false
end
end
context 'when build is finished' do
before { build.success! }
it 'should return true' do
expect(build.retryable?).to be true
end
end
end
end end
...@@ -264,7 +264,7 @@ describe Ci::Pipeline, models: true do ...@@ -264,7 +264,7 @@ describe Ci::Pipeline, models: true do
context 'when listing manual actions' do context 'when listing manual actions' do
let(:yaml) do let(:yaml) do
{ {
stages: ["build", "test", "test_failure", "deploy", "cleanup"], stages: ["build", "test", "staging", "production", "cleanup"],
build: { build: {
stage: "build", stage: "build",
script: "BUILD", script: "BUILD",
...@@ -273,17 +273,12 @@ describe Ci::Pipeline, models: true do ...@@ -273,17 +273,12 @@ describe Ci::Pipeline, models: true do
stage: "test", stage: "test",
script: "TEST", script: "TEST",
}, },
test_failure: { staging: {
stage: "test_failure", stage: "staging",
script: "ON test failure",
when: "on_failure",
},
deploy: {
stage: "deploy",
script: "PUBLISH", script: "PUBLISH",
}, },
production: { production: {
stage: "deploy", stage: "production",
script: "PUBLISH", script: "PUBLISH",
when: "manual", when: "manual",
}, },
...@@ -311,11 +306,18 @@ describe Ci::Pipeline, models: true do ...@@ -311,11 +306,18 @@ describe Ci::Pipeline, models: true do
# succeed stage test # succeed stage test
pipeline.builds.running_or_pending.each(&:success) pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_one # production expect(manual_actions).to be_empty
# succeed stage deploy # succeed stage staging and skip stage production
pipeline.builds.running_or_pending.each(&:success) pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_many # production and clear cache expect(manual_actions).to be_many # production and clear cache
# succeed stage cleanup
pipeline.builds.running_or_pending.each(&:success)
# after processing a pipeline we should have 6 builds, 5 succeeded
expect(pipeline.builds.count).to eq(6)
expect(pipeline.builds.success.count).to eq(4)
end end
def manual_actions def manual_actions
......
...@@ -124,6 +124,7 @@ describe SlackService, models: true do ...@@ -124,6 +124,7 @@ describe SlackService, models: true do
and_return( and_return(
double(:slack_service).as_null_object double(:slack_service).as_null_object
) )
slack.execute(push_sample_data) slack.execute(push_sample_data)
end end
...@@ -136,6 +137,76 @@ describe SlackService, models: true do ...@@ -136,6 +137,76 @@ describe SlackService, models: true do
) )
slack.execute(push_sample_data) slack.execute(push_sample_data)
end end
context "event channels" do
it "uses the right channel for push event" do
slack.update_attributes(push_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(push_sample_data)
end
it "uses the right channel for merge request event" do
slack.update_attributes(merge_request_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(@merge_sample_data)
end
it "uses the right channel for issue event" do
slack.update_attributes(issue_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(@issues_sample_data)
end
it "uses the right channel for wiki event" do
slack.update_attributes(wiki_page_channel: "random")
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(@wiki_page_sample_data)
end
context "note event" do
let(:issue_note) do
create(:note_on_issue, project: project, note: "issue note")
end
it "uses the right channel" do
slack.update_attributes(note_channel: "random")
note_data = Gitlab::NoteDataBuilder.build(issue_note, user)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: "random").
and_return(
double(:slack_service).as_null_object
)
slack.execute(note_data)
end
end
end
end end
describe "Note events" do describe "Note events" do
......
...@@ -458,6 +458,47 @@ describe Project, models: true do ...@@ -458,6 +458,47 @@ describe Project, models: true do
end end
end end
describe "#cache_has_external_wiki" do
let(:project) { create(:project) }
it "stores true if there is an external wiki" do
services = double(:service, external_wikis: [ExternalWikiService.new])
expect(project).to receive(:services).and_return(services)
expect do
project.cache_has_external_wiki
end.to change { project.has_external_wiki }.to(true)
end
it "stores false if there is no external wiki" do
services = double(:service, external_wikis: [])
expect(project).to receive(:services).and_return(services)
expect do
project.cache_has_external_wiki
end.to change { project.has_external_wiki }.to(false)
end
it "changes to true if an external wiki service is created later" do
expect do
project.cache_has_external_wiki
end.to change { project.has_external_wiki }.to(false)
expect do
create(:service, type: "ExternalWikiService", project: project)
end.to change { project.has_external_wiki }.to(true)
end
it "changes to false if an external wiki service is destroyed later" do
service = create(:service, type: "ExternalWikiService", project: project)
expect(project.has_external_wiki).to be_truthy
expect do
service.destroy
end.to change { project.has_external_wiki }.to(false)
end
end
describe '#open_branches' do describe '#open_branches' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -130,6 +130,36 @@ describe Repository, models: true do ...@@ -130,6 +130,36 @@ describe Repository, models: true do
end end
end end
describe :commit_file do
it 'commits change to a file successfully' do
expect do
repository.commit_file(user, 'CHANGELOG', 'Changelog!',
'Updates file content',
'master', true)
end.to change { repository.commits('master').count }.by(1)
blob = repository.blob_at('master', 'CHANGELOG')
expect(blob.data).to eq('Changelog!')
end
end
describe :update_file do
it 'updates filename successfully' do
expect do
repository.update_file(user, 'NEWLICENSE', 'Copyright!',
branch: 'master',
previous_path: 'LICENSE',
message: 'Changes filename')
end.to change { repository.commits('master').count }.by(1)
files = repository.ls_files('master')
expect(files).not_to include('LICENSE')
expect(files).to include('NEWLICENSE')
end
end
describe "search_files" do describe "search_files" do
let(:results) { repository.search_files('feature', 'master') } let(:results) { repository.search_files('feature', 'master') }
subject { results } subject { results }
...@@ -719,6 +749,30 @@ describe Repository, models: true do ...@@ -719,6 +749,30 @@ describe Repository, models: true do
repository.before_delete repository.before_delete
end end
it 'flushes the tags cache' do
expect(repository).to receive(:expire_tags_cache)
repository.before_delete
end
it 'flushes the tag count cache' do
expect(repository).to receive(:expire_tag_count_cache)
repository.before_delete
end
it 'flushes the branches cache' do
expect(repository).to receive(:expire_branches_cache)
repository.before_delete
end
it 'flushes the branch count cache' do
expect(repository).to receive(:expire_branch_count_cache)
repository.before_delete
end
it 'flushes the root ref cache' do it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache) expect(repository).to receive(:expire_root_ref_cache)
...@@ -749,6 +803,30 @@ describe Repository, models: true do ...@@ -749,6 +803,30 @@ describe Repository, models: true do
repository.before_delete repository.before_delete
end end
it 'flushes the tags cache' do
expect(repository).to receive(:expire_tags_cache)
repository.before_delete
end
it 'flushes the tag count cache' do
expect(repository).to receive(:expire_tag_count_cache)
repository.before_delete
end
it 'flushes the branches cache' do
expect(repository).to receive(:expire_branches_cache)
repository.before_delete
end
it 'flushes the branch count cache' do
expect(repository).to receive(:expire_branch_count_cache)
repository.before_delete
end
it 'flushes the root ref cache' do it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache) expect(repository).to receive(:expire_root_ref_cache)
......
...@@ -887,16 +887,25 @@ describe User, models: true do ...@@ -887,16 +887,25 @@ describe User, models: true do
end end
describe '#authorized_projects' do describe '#authorized_projects' do
let!(:user) { create(:user) } context 'with a minimum access level' do
let!(:private_project) { create(:project, :private) } it 'includes projects for which the user is an owner' do
user = create(:user)
project = create(:empty_project, :private, namespace: user.namespace)
before do expect(user.authorized_projects(Gitlab::Access::REPORTER))
private_project.team << [user, Gitlab::Access::MASTER] .to contain_exactly(project)
end end
subject { user.authorized_projects } it 'includes projects for which the user is a master' do
user = create(:user)
project = create(:empty_project, :private)
project.team << [user, Gitlab::Access::MASTER]
it { is_expected.to eq([private_project]) } expect(user.authorized_projects(Gitlab::Access::REPORTER))
.to contain_exactly(project)
end
end
end end
describe '#ci_authorized_runners' do describe '#ci_authorized_runners' do
......
...@@ -73,12 +73,12 @@ describe Ci::API::API do ...@@ -73,12 +73,12 @@ describe Ci::API::API do
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response["variables"]).to eq([ expect(json_response["variables"]).to include(
{ "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
{ "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false } { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }
]) )
end end
it "returns variables for triggers" do it "returns variables for triggers" do
...@@ -92,14 +92,14 @@ describe Ci::API::API do ...@@ -92,14 +92,14 @@ describe Ci::API::API do
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response["variables"]).to eq([ expect(json_response["variables"]).to include(
{ "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
{ "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true }, { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false }, { "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false }
]) )
end end
it "returns dependent builds" do it "returns dependent builds" do
......
...@@ -116,12 +116,9 @@ describe HelpController, "routing" do ...@@ -116,12 +116,9 @@ describe HelpController, "routing" do
expect(get(path)).to route_to('help#show', expect(get(path)).to route_to('help#show',
path: 'workflow/protected_branches/protected_branches1', path: 'workflow/protected_branches/protected_branches1',
format: 'png') format: 'png')
path = '/help/shortcuts'
expect(get(path)).to route_to('help#show',
path: 'shortcuts')
path = '/help/ui' path = '/help/ui'
expect(get(path)).to route_to('help#show', expect(get(path)).to route_to('help#ui')
path: 'ui')
end end
end end
......
...@@ -24,8 +24,11 @@ module ApiHelpers ...@@ -24,8 +24,11 @@ module ApiHelpers
(path.index('?') ? '' : '?') + (path.index('?') ? '' : '?') +
# Append private_token if given a User object # Append private_token if given a User object
(user.respond_to?(:private_token) ? if user.respond_to?(:private_token)
"&private_token=#{user.private_token}" : "") "&private_token=#{user.private_token}"
else
''
end
end end
def ci_api(path, user = nil) def ci_api(path, user = nil)
...@@ -35,8 +38,11 @@ module ApiHelpers ...@@ -35,8 +38,11 @@ module ApiHelpers
(path.index('?') ? '' : '?') + (path.index('?') ? '' : '?') +
# Append private_token if given a User object # Append private_token if given a User object
(user.respond_to?(:private_token) ? if user.respond_to?(:private_token)
"&private_token=#{user.private_token}" : "") "&private_token=#{user.private_token}"
else
''
end
end end
def json_response def json_response
......
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