diff options
Diffstat (limited to 'app')
82 files changed, 925 insertions, 471 deletions
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index b2b8e1b7ffb..90c09619f8c 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -38,3 +38,14 @@ class @Admin $('li.group_member').bind 'ajax:success', -> Turbolinks.visit(location.href) + + showBlacklistType = -> + if $("input[name='blacklist_type']:checked").val() == 'file' + $('.blacklist-file').show() + $('.blacklist-raw').hide() + else + $('.blacklist-file').hide() + $('.blacklist-raw').show() + + $("input[name='blacklist_type']").click showBlacklistType + showBlacklistType() diff --git a/app/assets/javascripts/files_comment_button.js.coffee b/app/assets/javascripts/files_comment_button.js.coffee index db0bf7082a9..5ab82c39fcd 100644 --- a/app/assets/javascripts/files_comment_button.js.coffee +++ b/app/assets/javascripts/files_comment_button.js.coffee @@ -7,7 +7,6 @@ class @FilesCommentButton UNFOLDABLE_LINE_CLASS = 'js-unfold' EMPTY_CELL_CLASS = 'empty-cell' OLD_LINE_CLASS = 'old_line' - NEW_CLASS = 'new' LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content" TEXT_FILE_SELECTOR = '.text-file' DEBOUNCE_TIMEOUT_DURATION = 100 @@ -18,6 +17,8 @@ class @FilesCommentButton debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION $(document) + .off 'mouseover', LINE_COLUMN_CLASSES + .off 'mouseleave', LINE_COLUMN_CLASSES .on 'mouseover', LINE_COLUMN_CLASSES, debounce .on 'mouseleave', LINE_COLUMN_CLASSES, @destroy @@ -64,20 +65,20 @@ class @FilesCommentButton getLineContent: (hoveredElement) -> return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS - $(".#{LINE_CONTENT_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) + if @VIEW_TYPE is 'inline' + return $(hoveredElement).closest(LINE_HOLDER_CLASS).find ".#{LINE_CONTENT_CLASS}" + else + return $(hoveredElement).next ".#{LINE_CONTENT_CLASS}" getButtonParent: (hoveredElement) -> if @VIEW_TYPE is 'inline' return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS - $(".#{OLD_LINE_CLASS}", hoveredElement.parent()) + hoveredElement.parent().find ".#{OLD_LINE_CLASS}" else return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS - $(".#{LINE_NUMBER_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) - - diffTypeClass: (hoveredElement) -> - if hoveredElement.hasClass(NEW_CLASS) then '.new' else '.old' + $(hoveredElement).prev ".#{LINE_NUMBER_CLASS}" isMovingToSameType: (e) -> newButtonParent = @getButtonParent $(e.toElement) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 951530e03a5..7086ece29b8 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -210,9 +210,22 @@ class GitLabDropdown data: => return @fullData callback: (data) => - currentIndex = -1 @parseData data + unless @filterInput.val() is '' + selector = '.dropdown-content li:not(.divider):visible' + + if @dropdown.find('.dropdown-toggle-page').length + selector = ".dropdown-page-one #{selector}" + + $(selector, @dropdown) + .first() + .find('a') + .addClass('is-focused') + + currentIndex = 0 + + # Event listeners @dropdown.on "shown.bs.dropdown", @opened diff --git a/app/assets/javascripts/graphs/application.js.coffee b/app/assets/javascripts/graphs/graphs_bundle.js.coffee index e0f681acf0b..e0f681acf0b 100644 --- a/app/assets/javascripts/graphs/application.js.coffee +++ b/app/assets/javascripts/graphs/graphs_bundle.js.coffee diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 779f536d9f0..963a0550c35 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -55,10 +55,13 @@ class @MergeRequestWidget $('.mr-state-widget').replaceWith(data) ciLabelForStatus: (status) -> - if status is 'success' - 'passed' - else - status + switch status + when 'success' + 'passed' + when 'success_with_warnings' + 'passed with warnings' + else + status pollCIStatus: -> @fetchBuildStatusInterval = setInterval ( => @@ -116,7 +119,7 @@ class @MergeRequestWidget showCIStatus: (state) -> return if not state? $('.ci_widget').hide() - allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"] + allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"] if state in allowed_states $('.ci_widget.ci-' + state).show() switch state @@ -124,7 +127,7 @@ class @MergeRequestWidget @setMergeButtonClass('btn-danger') when "running" @setMergeButtonClass('btn-warning') - when "success" + when "success", "success_with_warnings" @setMergeButtonClass('btn-create') else $('.ci_widget.ci-error').show() diff --git a/app/assets/javascripts/network/application.js.coffee b/app/assets/javascripts/network/network_bundle.js.coffee index f75f63869c5..f75f63869c5 100644 --- a/app/assets/javascripts/network/application.js.coffee +++ b/app/assets/javascripts/network/network_bundle.js.coffee diff --git a/app/assets/javascripts/profile/application.js.coffee b/app/assets/javascripts/profile/profile_bundle.js.coffee index 91cacfece46..91cacfece46 100644 --- a/app/assets/javascripts/profile/application.js.coffee +++ b/app/assets/javascripts/profile/profile_bundle.js.coffee diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee index 12340bbce54..0c95301e380 100644 --- a/app/assets/javascripts/right_sidebar.js.coffee +++ b/app/assets/javascripts/right_sidebar.js.coffee @@ -120,6 +120,9 @@ class @Sidebar i.show() sidebarCollapseClicked: (e) -> + + return if $(e.currentTarget).hasClass('dont-change-state') + sidebar = e.data e.preventDefault() $block = $(@).closest('.block') diff --git a/app/assets/javascripts/users/application.js.coffee b/app/assets/javascripts/users/users_bundle.js.coffee index 91cacfece46..91cacfece46 100644 --- a/app/assets/javascripts/users/application.js.coffee +++ b/app/assets/javascripts/users/users_bundle.js.coffee diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index ad94e457cfd..7ce203d2ec7 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -232,7 +232,9 @@ .nav-block { .controls { float: right; - margin-top: 11px; + margin-top: 8px; + padding-bottom: 7px; + border-bottom: 1px solid $border-color; } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 5d3273ea64d..96565da1bc9 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -98,13 +98,30 @@ .md { &.md-preview-holder { - code { - white-space: pre-wrap; - word-break: keep-all; - } - + // Reset ul style types since we're nested inside a ul already @include bulleted-list; } + + // On diffs code should wrap nicely and not overflow + code { + white-space: pre-wrap; + word-break: keep-all; + } + + hr { + // Darken 'whitesmoke' a bit to make it more visible in note bodies + border-color: darken(#f5f5f5, 8%); + margin: 10px 0; + } + + // Border around images in issue and MR comments. + img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px 0; + // Ensure that image does not exceed viewport + max-height: calc(100vh - 100px); + } } .toolbar-group { diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index a2145956eb5..5c336bb1c7e 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -176,3 +176,11 @@ } } } + +// hide event scope (namespace + project) where it is not necessary +.project-activity { + .event-scope { + display: none; + } +} + diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index ded437625ee..7a50bc9c832 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -269,7 +269,7 @@ .issuable-header-btn { background: $gray-normal; border: 1px solid $border-gray-normal; - + &:hover { background: $gray-dark; border: 1px solid $border-gray-dark; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 5254faf723d..4e806e60e7e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -70,6 +70,14 @@ color: $gl-success; } + &.ci-success_with_warnings { + color: $gl-success; + + i { + color: $gl-warning; + } + } + &.ci-skipped { background-color: #eee; color: #888; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index ac8c02b59dc..a2b5437e503 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -91,34 +91,11 @@ ul.notes { // Reset ul style types since we're nested inside a ul already @include bulleted-list; - // On diffs code should wrap nicely and not overflow - code { - white-space: pre-wrap; - } - ul.task-list { ul:not(.task-list) { padding-left: 1.3em; } } - - hr { - // Darken 'whitesmoke' a bit to make it more visible in note bodies - border-color: darken(#f5f5f5, 8%); - margin: 10px 0; - } - - code { - word-break: keep-all; - } - - // Border around images in issue and MR comments. - img:not(.emoji) { - border: 1px solid $table-border-gray; - padding: 5px; - margin: 5px 0; - max-height: calc(100vh - 100px); - } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 63853335fb6..cc3aef5199e 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -333,18 +333,53 @@ a.deploy-project-label { } .fork-namespaces { - .fork-thumbnail { - text-align: center; - margin-bottom: $gl-padding; - - .caption { - padding: $gl-padding 0; - min-height: 30px; - } + .row { + -webkit-flex-wrap: wrap; + display: -webkit-flex; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + + .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 { - @include border-radius(50%); - max-width: 100px; + img { + @include border-radius(50%); + max-width: 100px; + } } } } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index a22d4b6f6be..99f64b5e4cc 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -15,7 +15,8 @@ border-color: $gl-danger; } - &.ci-success { + &.ci-success, + &.ci-success_with_warnings { color: $gl-success; border-color: $gl-success; } @@ -57,9 +58,12 @@ .ci-status-icon-failed { color: $gl-danger; } - .ci-status-icon-pending { + + .ci-status-icon-pending, + .ci-status-icon-success_with_warning { color: $gl-warning; } + .ci-status-icon-running { color: $blue-normal; } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 23ba83aba0e..9e1dc15de84 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -64,6 +64,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController params[:application_setting][:disabled_oauth_sign_in_sources] = AuthHelper.button_based_providers.map(&:to_s) - Array(enabled_oauth_sign_in_sources) + params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] params.require(:application_setting).permit( :default_projects_limit, @@ -83,7 +84,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :default_project_visibility, :default_snippet_visibility, :default_group_visibility, - :restricted_signup_domains_raw, + :domain_whitelist_raw, + :domain_blacklist_enabled, + :domain_blacklist_raw, + :domain_blacklist_file, :version_check_enabled, :admin_notification_email, :user_oauth_applications, diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 94b5aaa71d0..f3a88a8e6c8 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -60,6 +60,6 @@ class Admin::GroupsController < Admin::ApplicationController end 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 diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 46133588332..7c37f3155da 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -1,4 +1,6 @@ class Admin::ServicesController < Admin::ApplicationController + include ServiceParams + before_action :service, only: [:edit, :update] def index @@ -13,7 +15,7 @@ class Admin::ServicesController < Admin::ApplicationController end def update - if service.update_attributes(application_services_params[:service]) + if service.update_attributes(service_params[:service]) redirect_to admin_application_settings_services_path, notice: 'Application settings saved successfully' else @@ -37,15 +39,4 @@ class Admin::ServicesController < Admin::ApplicationController def service @service ||= Service.where(id: params[:id], template: true).first 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 diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb new file mode 100644 index 00000000000..471d15af913 --- /dev/null +++ b/app/controllers/concerns/service_params.rb @@ -0,0 +1,35 @@ +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 diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index a04bf7df722..6780a6d4d87 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -121,7 +121,7 @@ class GroupsController < Groups::ApplicationController end 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 def load_events diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index d3dd98c8a4e..f7b44099b78 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -30,7 +30,7 @@ class HelpController < ApplicationController end # Allow access to images in the doc folder - format.any(:png, :gif, :jpeg) do + format.any(:png, :gif, :jpeg, :mp4) do # Note: We are purposefully NOT using `Rails.root.join` path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}") diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 824aa41db51..a9f482c8787 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -3,11 +3,6 @@ class Projects::BadgesController < Projects::ApplicationController before_action :authorize_admin_project!, only: [: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 badge = Gitlab::Badge::Build.new(project, params[:ref]) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 5f3ee71444d..10749d0fef8 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -15,6 +15,7 @@ class Projects::CompareController < Projects::ApplicationController end def show + apply_diff_view_cookie! end def diff_for_path diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index df659bb8c3b..7beeb7d97d0 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -286,6 +286,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController status = pipeline.status coverage = pipeline.try(:coverage) + status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings? + status ||= "preparing" else ci_service = @merge_request.source_project.ci_service diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb new file mode 100644 index 00000000000..85ba706e5cd --- /dev/null +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -0,0 +1,30 @@ +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 diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index d79f16e6a5a..3602b3d5e58 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController when "graphs_commits" commits_namespace_project_graph_path(@project.namespace, @project, @id) when "badges" - namespace_project_badges_path(@project.namespace, @project, ref: @id) + namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id) else namespace_project_commits_path(@project.namespace, @project, @id) end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 1b91882048e..6a227d85f6f 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -1,20 +1,5 @@ class Projects::ServicesController < Projects::ApplicationController - 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] + include ServiceParams # Authorize before_action :authorize_admin_project! @@ -33,7 +18,7 @@ class Projects::ServicesController < Projects::ApplicationController end def update - if @service.update_attributes(service_params) + if @service.update_attributes(service_params[:service]) redirect_to( edit_namespace_project_service_path(@project.namespace, @project, @service.to_param, notice: @@ -64,12 +49,4 @@ class Projects::ServicesController < Projects::ApplicationController def service @service ||= @project.services.find { |service| service.to_param == params[:id] } 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 diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index caed064dfbc..e617be8f9fb 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -1,6 +1,6 @@ class Projects::UploadsController < Projects::ApplicationController skip_before_action :reject_blocked!, :project, - :repository, if: -> { action_name == 'show' && image? } + :repository, if: -> { action_name == 'show' && image_or_video? } before_action :authorize_upload_file!, only: [:create] @@ -24,7 +24,7 @@ class Projects::UploadsController < Projects::ApplicationController def show return render_404 if uploader.nil? || !uploader.file.exists? - disposition = uploader.image? ? 'inline' : 'attachment' + disposition = uploader.image_or_video? ? 'inline' : 'attachment' send_file uploader.file.path, disposition: disposition end @@ -49,7 +49,7 @@ class Projects::UploadsController < Projects::ApplicationController @uploader end - def image? - uploader && uploader.file.exists? && uploader.image? + def image_or_video? + uploader && uploader.file.exists? && uploader.image_or_video? end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 4e5bcff9cf8..ec7a2e63b9a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -296,7 +296,7 @@ class ProjectsController < Projects::ApplicationController :issues_tracker_id, :default_branch, :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, - :public_builds, :only_allow_merge_if_build_succeeds + :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled ) end diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb new file mode 100644 index 00000000000..6ff40c6b461 --- /dev/null +++ b/app/helpers/avatars_helper.rb @@ -0,0 +1,30 @@ +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 diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 59a8365d60b..ffac28f78a0 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -15,8 +15,11 @@ module CiStatusHelper end def ci_label_for_status(status) - if status == 'success' + case status + when 'success' 'passed' + when 'success_with_warnings' + 'passed with warnings' else status end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 474041eccbb..052ce56809e 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -16,16 +16,6 @@ module CommitsHelper commit_person_link(commit, options.merge(source: :committer)) 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) if diff.deleted_file "deleted" diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb new file mode 100644 index 00000000000..2dd0bf5d71e --- /dev/null +++ b/app/helpers/services_helper.rb @@ -0,0 +1,25 @@ +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 diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index 1926f03af07..790001222f1 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -1,5 +1,6 @@ module TimeHelper def time_interval_in_words(interval_in_seconds) + interval_in_seconds = interval_in_seconds.to_i minutes = interval_in_seconds / 60 seconds = interval_in_seconds - minutes * 60 diff --git a/app/models/ability.rb b/app/models/ability.rb index 6fd18f2ee24..f33c8d61d3f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -172,7 +172,7 @@ class Ability rules << :read_build if project.public_builds? unless owner || project.team.member?(user) || project_group_member?(project, user) - rules << :request_access + rules << :request_access if project.request_access_enabled end end @@ -373,7 +373,7 @@ class Ability end 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 rules.flatten diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index c6f77cc055f..8c19d9dc9c8 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -4,12 +4,20 @@ class ApplicationSetting < ActiveRecord::Base add_authentication_token_field :health_check_access_token CACHE_KEY = 'application_setting.last' + DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace + | # or + \s # any whitespace character + | # or + [\r\n] # any number of newline characters + }x serialize :restricted_visibility_levels serialize :import_sources serialize :disabled_oauth_sign_in_sources, Array - serialize :restricted_signup_domains, Array - attr_accessor :restricted_signup_domains_raw + serialize :domain_whitelist, Array + serialize :domain_blacklist, Array + + attr_accessor :domain_whitelist_raw, :domain_blacklist_raw validates :session_expire_delay, presence: true, @@ -62,6 +70,10 @@ class ApplicationSetting < ActiveRecord::Base validates :enabled_git_access_protocol, inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true } + validates :domain_blacklist, + presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' }, + if: :domain_blacklist_enabled? + validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? value.each do |level| @@ -129,7 +141,7 @@ class ApplicationSetting < ActiveRecord::Base session_expire_delay: Settings.gitlab['session_expire_delay'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], + domain_whitelist: Settings.gitlab['domain_whitelist'], import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], @@ -150,20 +162,30 @@ class ApplicationSetting < ActiveRecord::Base ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) end - def restricted_signup_domains_raw - self.restricted_signup_domains.join("\n") unless self.restricted_signup_domains.nil? + def domain_whitelist_raw + self.domain_whitelist.join("\n") unless self.domain_whitelist.nil? + end + + def domain_blacklist_raw + self.domain_blacklist.join("\n") unless self.domain_blacklist.nil? + end + + def domain_whitelist_raw=(values) + self.domain_whitelist = [] + self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR) + self.domain_whitelist.reject! { |d| d.empty? } + self.domain_whitelist + end + + def domain_blacklist_raw=(values) + self.domain_blacklist = [] + self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR) + self.domain_blacklist.reject! { |d| d.empty? } + self.domain_blacklist end - def restricted_signup_domains_raw=(values) - self.restricted_signup_domains = [] - self.restricted_signup_domains = values.split( - /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace - | # or - \s # any whitespace character - | # or - [\r\n] # any number of newline characters - /x) - self.restricted_signup_domains.reject! { |d| d.empty? } + def domain_blacklist_file=(file) + self.domain_blacklist_raw = file.read end def runners_registration_token diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e02351ce339..cbfa14e81f1 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -12,7 +12,7 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } - scope :with_artifacts, ->() { where.not(artifacts_file: nil) } + scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :manual_actions, ->() { where(when: :manual) } @@ -97,7 +97,7 @@ module Ci end def other_actions - pipeline.manual_actions.where.not(id: self) + pipeline.manual_actions.where.not(name: name) end def playable? @@ -145,7 +145,15 @@ module Ci end def variables - predefined_variables + yaml_variables + project_variables + trigger_variables + variables = predefined_variables + variables += project.predefined_variables + variables += pipeline.predefined_variables + variables += runner.predefined_variables if runner + variables += project.container_registry_variables + variables += yaml_variables + variables += project.secret_variables + variables += trigger_request.user_variables if trigger_request + variables end def merge_request @@ -430,28 +438,23 @@ module Ci self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil) 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 - variables = [] - variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag? - variables << { key: :CI_BUILD_NAME, value: name, public: true } - variables << { key: :CI_BUILD_STAGE, value: stage, public: true } - variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request + variables = [ + { key: 'CI', value: 'true', public: true }, + { key: 'GITLAB_CI', value: 'true', public: true }, + { key: 'CI_BUILD_ID', value: id.to_s, public: true }, + { 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 end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index aca8607f4e8..bce6a992af6 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -20,6 +20,11 @@ module Ci after_touch :update_state after_save :keep_around_commits + # ref can't be HEAD or SHA, can only be branch/tag name + scope :latest_successful_for, ->(ref = default_branch) do + where(ref: ref).success.order(id: :desc).limit(1) + end + def self.truncate_sha(sha) sha[0...8] end @@ -146,6 +151,10 @@ module Ci end end + def has_warnings? + builds.latest.ignored.any? + end + def config_processor return nil unless ci_yaml_file return @config_processor if defined?(@config_processor) @@ -198,6 +207,12 @@ module Ci Note.for_commit_id(sha) end + def predefined_variables + [ + { key: 'CI_PIPELINE_ID', value: id.to_s, public: true } + ] + end + private def build_builds_for_stages(stages, user, status, trigger_request) @@ -206,8 +221,9 @@ module Ci # build builds only for the first stage that has builds available. # stages.any? do |stage| - CreateBuildsService.new(self) - .execute(stage, user, status, trigger_request).present? + CreateBuildsService.new(self). + execute(stage, user, status, trigger_request). + any?(&:active?) end end @@ -226,7 +242,7 @@ module Ci def keep_around_commits return unless project - + project.repository.keep_around(self.sha) project.repository.keep_around(self.before_sha) end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index b64ec79ec2b..49f05f881a2 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -114,6 +114,14 @@ module Ci tag_list.any? 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 def tag_constraints diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index fcf2b6dc5e2..fc674871743 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -7,5 +7,13 @@ module Ci has_many :builds, class_name: 'Ci::Build' serialize :variables + + def user_variables + return [] unless variables + + variables.map do |key, value| + { key: key, value: value, public: false } + end + end end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 535db26240a..2d185c28809 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -16,7 +16,11 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } + scope :latest, -> do + max_id = unscope(:select).select("max(#{quoted_table_name}.id)") + + where(id: max_id.group(:name, :commit_id)) + end scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } diff --git a/app/models/issue.rb b/app/models/issue.rb index 60abd47409e..60af8c15340 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -52,10 +52,50 @@ class Issue < ActiveRecord::Base attributes end + class << self + private + + # Returns the project that the current scope belongs to if any, nil otherwise. + # + # Examples: + # - my_project.issues.without_due_date.owner_project => my_project + # - Issue.all.owner_project => nil + def owner_project + # No owner if we're not being called from an association + return unless all.respond_to?(:proxy_association) + + owner = all.proxy_association.owner + + # Check if the association is or belongs to a project + if owner.is_a?(Project) + owner + else + begin + owner.association(:project).target + rescue ActiveRecord::AssociationNotFoundError + nil + end + end + end + end + def self.visible_to_user(user) return where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank? return all if user.admin? + # Check if we are scoped to a specific project's issues + if owner_project + if owner_project.authorized_for_user?(user, Gitlab::Access::REPORTER) + # If the project is authorized for the user, they can see all issues in the project + return all + else + # else only non confidential and authored/assigned to them + return where('issues.confidential IS NULL OR issues.confidential IS FALSE + OR issues.author_id = :user_id OR issues.assignee_id = :user_id', + user_id: user.id) + end + end + where(' issues.confidential IS NULL OR issues.confidential IS FALSE diff --git a/app/models/project.rb b/app/models/project.rb index d08b737e813..f09d915f20e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -429,6 +429,17 @@ class Project < ActiveRecord::Base repository.commit(ref) end + # ref can't be HEAD, can only be branch/tag name or SHA + def latest_successful_builds_for(ref = default_branch) + pipeline = pipelines.latest_successful_for(ref).to_sql + join_sql = "INNER JOIN (#{pipeline}) pipelines" + + " ON pipelines.id = #{Ci::Build.quoted_table_name}.commit_id" + builds.joins(join_sql).latest.with_artifacts + # TODO: Whenever we dropped support for MySQL, we could change to: + # pipeline = pipelines.latest_successful_for(ref) + # builds.where(pipeline: pipeline).latest.with_artifacts + end + def merge_base_commit(first_commit_id, second_commit_id) sha = repository.merge_base(first_commit_id, second_commit_id) repository.commit(sha) if sha @@ -1180,4 +1191,74 @@ class Project < ActiveRecord::Base def ensure_dir_exist gitlab_shell.add_namespace(repository_storage_path, namespace.path) 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 + + # Checks if `user` is authorized for this project, with at least the + # `min_access_level` (if given). + # + # If you change the logic of this method, please also update `User#authorized_projects` + def authorized_for_user?(user, min_access_level = nil) + return false unless user + + return true if personal? && namespace_id == user.namespace_id + + authorized_for_user_by_group?(user, min_access_level) || + authorized_for_user_by_members?(user, min_access_level) || + authorized_for_user_by_shared_projects?(user, min_access_level) + end + + private + + def authorized_for_user_by_group?(user, min_access_level) + member = user.group_members.find_by(source_id: group) + + member && (!min_access_level || member.access_level >= min_access_level) + end + + def authorized_for_user_by_members?(user, min_access_level) + member = members.find_by(user_id: user) + + member && (!min_access_level || member.access_level >= min_access_level) + end + + def authorized_for_user_by_shared_projects?(user, min_access_level) + shared_projects = user.group_members.joins(group: :shared_projects). + where(project_group_links: { project_id: self }) + + if min_access_level + members_scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } } + shared_projects = shared_projects.where(members: members_scope) + end + + shared_projects.any? + end end diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index cf9e4d5a8b6..abbc780dc1a 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -4,6 +4,9 @@ class SlackService < Service validates :webhook, presence: true, url: true, if: :activated? def initialize_properties + # Custom serialized properties initialization + self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) } + if properties.nil? self.properties = {} self.notify_only_broken_builds = true @@ -29,13 +32,15 @@ class SlackService < Service end def fields - [ - { type: 'text', name: 'webhook', - placeholder: 'https://hooks.slack.com/services/...' }, - { type: 'text', name: 'username', placeholder: 'username' }, - { type: 'text', name: 'channel', placeholder: '#channel' }, - { type: 'checkbox', name: 'notify_only_broken_builds' }, - ] + default_fields = + [ + { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' }, + { type: 'text', name: 'username', placeholder: 'username' }, + { type: 'text', name: 'channel', placeholder: "#general" }, + { type: 'checkbox', name: 'notify_only_broken_builds' }, + ] + + default_fields + build_event_channels end def supported_events @@ -74,7 +79,10 @@ class SlackService < Service end 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 if message @@ -83,8 +91,35 @@ class SlackService < Service 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 + 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 project.name_with_namespace.gsub(/\s/, '') end diff --git a/app/models/service.rb b/app/models/service.rb index 4821096379c..40cd9b861f0 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -26,7 +26,7 @@ class Service < ActiveRecord::Base scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) } scope :issue_trackers, -> { where(category: 'issue_tracker') } - scope :external_wikis, -> { where(type: 'ExternalWikiService') } + scope :external_wikis, -> { where(type: 'ExternalWikiService').active } scope :active, -> { where(active: true) } scope :without_defaults, -> { where(default: false) } @@ -82,6 +82,18 @@ class Service < ActiveRecord::Base Gitlab::PushDataBuilder.build_sample(project, user) end + def event_channel_names + [] + end + + def event_field(event) + nil + end + + def global_fields + fields + end + def supported_events %w(push tag_push issue merge_request wiki_page) end diff --git a/app/models/user.rb b/app/models/user.rb index 975e935fa20..db747434959 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -111,7 +111,7 @@ class User < ActiveRecord::Base validates :avatar, file_size: { maximum: 200.kilobytes.to_i } before_validation :generate_password, on: :create - before_validation :restricted_signup_domains, on: :create + before_validation :signup_domain_valid?, on: :create before_validation :sanitize_attrs before_validation :set_notification_email, if: ->(user) { user.email_changed? } before_validation :set_public_email, if: ->(user) { user.public_email_changed? } @@ -412,6 +412,8 @@ class User < ActiveRecord::Base end # Returns projects user is authorized to access. + # + # If you change the logic of this method, please also update `Project#authorized_for_user` def authorized_projects(min_access_level = nil) Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})") end @@ -760,29 +762,6 @@ class User < ActiveRecord::Base Project.where(id: events) end - def restricted_signup_domains - email_domains = current_application_settings.restricted_signup_domains - - unless email_domains.blank? - match_found = email_domains.any? do |domain| - escaped = Regexp.escape(domain).gsub('\*', '.*?') - regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE - email_domain = Mail::Address.new(self.email).domain - email_domain =~ regexp - end - - unless match_found - self.errors.add :email, - 'is not whitelisted. ' + - 'Email domains valid for registration are: ' + - email_domains.join(', ') - return false - end - end - - true - end - def can_be_removed? !solo_owned_groups.present? end @@ -881,4 +860,40 @@ class User < ActiveRecord::Base self.can_create_group = false self.projects_limit = 0 end + + def signup_domain_valid? + valid = true + error = nil + + if current_application_settings.domain_blacklist_enabled? + blocked_domains = current_application_settings.domain_blacklist + if domain_matches?(blocked_domains, self.email) + error = 'is not from an allowed domain.' + valid = false + end + end + + allowed_domains = current_application_settings.domain_whitelist + unless allowed_domains.blank? + if domain_matches?(allowed_domains, self.email) + valid = true + else + error = "is not whitelisted. Email domains valid for registration are: #{allowed_domains.join(', ')}" + valid = false + end + end + + self.errors.add(:email, error) unless valid + + valid + end + + def domain_matches?(email_domains, email) + signup_domain = Mail::Address.new(email).domain + email_domains.any? do |domain| + escaped = Regexp.escape(domain).gsub('\*', '.*?') + regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE + signup_domain =~ regexp + end + end end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 1af9e9b0edb..2f5f49f7de7 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -33,16 +33,15 @@ class FileUploader < CarrierWave::Uploader::Base end def to_h - filename = image? ? self.file.basename : self.file.filename + filename = image_or_video? ? self.file.basename : self.file.filename escaped_filename = filename.gsub("]", "\\]") markdown = "[#{escaped_filename}](#{self.secure_url})" - markdown.prepend("!") if image? + markdown.prepend("!") if image_or_video? { alt: filename, url: self.secure_url, - is_image: image?, markdown: markdown } end diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb index 5ef440f3367..b10ad71d052 100644 --- a/app/uploaders/uploader_helper.rb +++ b/app/uploaders/uploader_helper.rb @@ -1,16 +1,37 @@ # Extra methods for uploader module UploaderHelper + IMAGE_EXT = %w[png jpg jpeg gif bmp tiff] + # We recommend using the .mp4 format over .mov. Videos in .mov format can + # still be used but you really need to make sure they are served with the + # proper MIME type video/mp4 and not video/quicktime or your videos won't play + # on IE >= 9. + # http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html + VIDEO_EXT = %w[mp4 m4v mov webm ogv] + def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false + extension_match?(IMAGE_EXT) + end + + def video? + extension_match?(VIDEO_EXT) + end + + def image_or_video? + image? || video? + end + + def extension_match?(extensions) + return false unless file + + extension = + if file.respond_to?(:extension) + file.extension + else + # Not all CarrierWave storages respond to :extension + File.extname(file.path).delete('.') + end + + extensions.include?(extension.downcase) end def file_storage? diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 538d8176ce7..23b52d08df7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -109,7 +109,7 @@ Newly registered users will by default be external %fieldset - %legend Sign-in Restrictions + %legend Sign-up Restrictions .form-group .col-sm-offset-2.col-sm-10 .checkbox @@ -123,6 +123,49 @@ = f.check_box :send_user_confirmation_email Send confirmation email on sign-up .form-group + = f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :domain_whitelist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8 + .help-block ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com + .form-group + = f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'control-label col-sm-2' + .col-sm-10 + .checkbox + = f.label :domain_blacklist_enabled do + = f.check_box :domain_blacklist_enabled + Enable domain blacklist for sign ups + .form-group + .col-sm-offset-2.col-sm-10 + .radio + = label_tag :blacklist_type_file do + = radio_button_tag :blacklist_type, :file + .option-title + Upload blacklist file + .radio + = label_tag :blacklist_type_raw do + = radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank? + .option-title + Enter blacklist manually + .form-group.blacklist-file + = f.label :domain_blacklist_file, 'Blacklist file', class: 'control-label col-sm-2' + .col-sm-10 + = f.file_field :domain_blacklist_file, class: 'form-control', accept: '.txt,.conf' + .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries. + .form-group.blacklist-raw + = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8 + .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com + + .form-group + = f.label :after_sign_up_text, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :after_sign_up_text, class: 'form-control', rows: 4 + .help-block Markdown enabled + + %fieldset + %legend Sign-in Restrictions + .form-group .col-sm-offset-2.col-sm-10 .checkbox = f.label :signin_enabled do @@ -148,11 +191,6 @@ = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0' .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication .form-group - = f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2' - .col-sm-10 - = f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control' - .help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com - .form-group = f.label :home_page_url, 'Home page URL', class: 'control-label col-sm-2' .col-sm-10 = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block' @@ -168,11 +206,6 @@ = f.text_area :sign_in_text, class: 'form-control', rows: 4 .help-block Markdown enabled .form-group - = f.label :after_sign_up_text, class: 'control-label col-sm-2' - .col-sm-10 - = f.text_area :after_sign_up_text, class: 'form-control', rows: 4 - .help-block Markdown enabled - .form-group = f.label :help_page_text, class: 'control-label col-sm-2' .col-sm-10 = f.text_area :help_page_text, class: 'form-control', rows: 4 @@ -352,4 +385,4 @@ .form-actions - = f.submit 'Save', class: 'btn btn-save' + = f.submit 'Save', class: 'btn btn-save'
\ No newline at end of file diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 0cc405401cf..5f7fdfdb011 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -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 + .form-group + .col-sm-offset-2.col-sm-10 + = render 'shared/allow_request_access', form: f + - if @group.new_record? .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index e4629bae0e6..5c318cd3b8b 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -4,11 +4,7 @@ #{time_ago_with_tooltip(event.created_at)} = cache [event, current_application_settings, "v2.2"] do - - if event.author - = 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:'' + = author_avatar(event, size: 40) - if event.created_project? = render "events/event/created_project", event: event diff --git a/app/views/events/_event_scope.html.haml b/app/views/events/_event_scope.html.haml new file mode 100644 index 00000000000..8f7da7d8c4f --- /dev/null +++ b/app/views/events/_event_scope.html.haml @@ -0,0 +1,7 @@ +%span.event-scope + = event_preposition(event) + - if event.project + = link_to_project event.project + - else + = event.project_name + diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index 2e2403347c1..bba6e0d2c20 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -1,6 +1,6 @@ .event-title %span.author_name= link_to_author event - %span.event_label{class: event.action_name} + %span{class: event.action_name} - if event.target = event.action_name %strong @@ -10,12 +10,7 @@ - else = event_action_name(event) - = event_preposition(event) - - - if event.project - = link_to_project event.project - - else - = event.project_name + = render "events/event_scope", event: event - if event.target.respond_to?(:title) .event-body diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml index 5a2a469ba62..aba64dd17d0 100644 --- a/app/views/events/event/_created_project.html.haml +++ b/app/views/events/event/_created_project.html.haml @@ -1,6 +1,6 @@ .event-title %span.author_name= link_to_author event - %span.event_label{class: event.action_name} + %span{class: event.action_name} = event_action_name(event) - if event.project diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 830fec0b4ab..f08c96df309 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -1,14 +1,9 @@ .event-title %span.author_name= link_to_author event - %span.event_label - = event.action_name - = event_note_title_html(event) - at + = event.action_name + = event_note_title_html(event) - - if event.project - = link_to_project event.project - - else - = event.project_name + = render "events/event_scope", event: event .event-body .event-note diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index ea54ef226ec..44fff49d99c 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -2,14 +2,14 @@ .event-title %span.author_name= link_to_author event - %span.event_label.pushed #{event.action_name} #{event.ref_type} + %span.pushed #{event.action_name} #{event.ref_type} - if event.rm_ref? %strong= event.ref_name - else %strong = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title) - at - = link_to_project project + + = render "events/event_scope", event: event - if event.push_with_commits? .event-body diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 92cd4c553d0..decb89b2fd6 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -22,6 +22,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 .form-group + .col-sm-offset-2.col-sm-10 + = render 'shared/allow_request_access', form: f + + .form-group %hr = f.label :share_with_group_lock, class: 'control-label' do Share with group lock diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml index 3b40006a0cc..e5bda7b3a6f 100644 --- a/app/views/layouts/nav/_explore.html.haml +++ b/app/views/layouts/nav/_explore.html.haml @@ -1,21 +1,17 @@ %ul.nav.nav-sidebar = 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 - = icon('bookmark fw') %span Projects = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to explore_groups_path, title: 'Groups' do - = icon('group fw') %span Groups = nav_link(controller: :snippets) do = link_to explore_snippets_path, title: 'Snippets' do - = icon('clipboard fw') %span Snippets = nav_link(controller: :help) do = link_to help_path, title: 'Help' do - = icon('question-circle fw') %span Help diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 51a54b4f262..52a5bdc1a1b 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -39,7 +39,7 @@ = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do %span Triggers - = nav_link(controller: :badges) do - = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do + = nav_link(controller: :pipelines_settings) do + = link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do %span - Badges + CI/CD Pipelines diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml index 003884a5bd9..943ebdaeffe 100644 --- a/app/views/profiles/_head.html.haml +++ b/app/views/profiles/_head.html.haml @@ -1,3 +1,3 @@ - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/cropper.js') - = page_specific_javascript_tag('profile/application.js') + = page_specific_javascript_tag('profile/profile_bundle.js') diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 48b0dd6b121..ac50ce83f6a 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -5,7 +5,8 @@ %i.fa.fa-rss = render 'shared/event_filter' -.content_list{:"data-href" => activity_project_path(@project)} + +.content_list.project-activity{:"data-href" => activity_project_path(@project)} = spinner :javascript diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml deleted file mode 100644 index fff30f11d82..00000000000 --- a/app/views/projects/_builds_settings.html.haml +++ /dev/null @@ -1,65 +0,0 @@ -%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. diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml deleted file mode 100644 index ac80951dd4f..00000000000 --- a/app/views/projects/badges/index.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -- page_title 'Badges' -- badges_path = namespace_project_badges_path(@project.namespace, @project) - -.prepend-top-10 - .panel.panel-default - .panel-heading - %b Builds badge · - = @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) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index cb0ca7bc8e3..2f7d54f0bdd 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -27,7 +27,7 @@ %p.commit-title - 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" - else Cant find HEAD commit for this branch diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index c8c7b858baa..ab9afb06afb 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -9,7 +9,8 @@ = cache(cache_key) do %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-row-title %span.item-title diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 65d68aa2985..f70dba224fa 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -17,6 +17,6 @@ - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do - if deployment.last? - Retry + Re-deploy - else Rollback diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 57af167180b..921155e970b 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -32,6 +32,10 @@ %strong = visibility_level_label(@project.visibility_level) .light= visibility_level_description(@project.visibility_level, @project) + + .form-group + = render 'shared/allow_request_access', form: f + .form-group = f.label :tag_list, "Tags", class: 'label-light' = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control" @@ -86,8 +90,6 @@ %hr = render 'merge_request_settings', f: f %hr - = render 'builds_settings', f: f - %hr %fieldset.features.append-bottom-default %h5.prepend-top-0 Project avatar diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index 73a7fc0e1ac..5242bc72b71 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -1,45 +1,54 @@ - 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 - - @namespaces.in_groups_of(6, false) do |group| - .row - - group.each do |namespace| - .col-md-2.col-sm-3 - - if fork = namespace.find_fork_of(@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. +.row.prepend-top-default + .col-lg-3 + %h4.prepend-top-0 + Fork project + %p + A fork is a copy of a project. %br - Forking a repository allows you to do changes without affecting the original project. -- else - %h3 No available namespaces to fork the project - %p.slead - You must have permission to create a project in a namespace before forking. + Forking a repository allows you to make changes without affecting the original project. + .col-lg-9 + .fork-namespaces + - if @namespaces.present? + %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 - .center - %h2 - %i.fa.fa-spinner.fa-spin - Forking repository - %p Please wait a moment, this page will automatically refresh when ready. + .save-project-loader.hide + .center + %h2 + %i.fa.fa-spinner.fa-spin + Forking repository + %p Please wait a moment, this page will automatically refresh when ready. diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index ca347406dfe..45e51389c00 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -3,7 +3,7 @@ - content_for :page_specific_javascripts do = 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 = link_to 'Contributors', namespace_project_graph_path = nav_link(action: :commits) do diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 489c632ae22..6ef640bb654 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,6 +1,6 @@ - if @pipeline .mr-widget-heading - - %w[success skipped canceled failed running pending].each do |status| + - %w[success success_with_warnings skipped canceled failed running pending].each do |status| .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) } = ci_icon_for_status(status) %span diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 091af4df4a1..b2ece44d966 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,7 +1,7 @@ - page_title "Network", @ref - content_for :page_specific_javascripts do = 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 "head" %div{ class: container_class } diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index c72d0140bb9..facdfcc9447 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -89,9 +89,9 @@ = link_to "#", class: 'btn js-toggle-button import_git' do %i.fa.fa-git %span Repo by URL - %div + %div{ class: 'import_gitlab_project' } - if gitlab_project_import_enabled? - = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do %i.fa.fa-gitlab %span GitLab export @@ -130,29 +130,29 @@ $(".modal").hide(); }); - $('.import_gitlab_project').bind('click', function() { - var _href = $("a.import_gitlab_project").attr("href"); - $(".import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val()); + $('.btn_import_gitlab_project').bind('click', function() { + var _href = $("a.btn_import_gitlab_project").attr("href"); + $(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val()); }); - $('.import_gitlab_project').attr('disabled',true) - $('.import_gitlab_project').attr('title', 'Project path required.'); + $('.btn_import_gitlab_project').attr('disabled',true) + $('.import_gitlab_project').attr('title', 'Project path and name required.'); $('.import_gitlab_project').click(function( event ) { - if($('.import_gitlab_project').attr('disabled')) { + if($('.btn_import_gitlab_project').attr('disabled')) { event.preventDefault(); - new Flash("Please enter a path for the project to be imported to."); + new Flash("Please enter path and name for the project to be imported to."); } }); $('#project_path').keyup(function(){ if($(this).val().length !=0) { - $('.import_gitlab_project').attr('disabled', false); + $('.btn_import_gitlab_project').attr('disabled', false); $('.import_gitlab_project').attr('title',''); $(".flash-container").html("") } else { - $('.import_gitlab_project').attr('disabled',true); - $('.import_gitlab_project').attr('title', 'Project path required.'); + $('.btn_import_gitlab_project').attr('disabled',true); + $('.import_gitlab_project').attr('title', 'Project path and name required.'); } }); diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index af0046886fb..71da8ac9d7c 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -30,7 +30,7 @@ = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do = icon('trash-o') .note-body{class: note_editable ? 'js-task-list-container' : ''} - .note-text + .note-text.md = preserve do = note.note_html = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml new file mode 100644 index 00000000000..228bad36ebd --- /dev/null +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -0,0 +1,103 @@ +- 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 · + = @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) diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 166dc4a01fc..752fbc21a11 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -8,6 +8,7 @@ .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| = render 'shared/service_settings', form: form + = form.submit 'Save changes', class: 'btn btn-save' - if @service.valid? && @service.activated? diff --git a/app/views/shared/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml new file mode 100644 index 00000000000..53a99a736c0 --- /dev/null +++ b/app/views/shared/_allow_request_access.html.haml @@ -0,0 +1,6 @@ +.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. diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 4eaf7c2a025..5254d265918 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -10,69 +10,28 @@ .col-sm-10 = form.check_box :active -- if @service.supported_events.length > 1 - .form-group - = form.label :url, "Trigger", class: 'control-label' - .col-sm-10 - - if @service.supported_events.include?("push") - %div - = form.check_box :push_events, class: 'pull-left' - .prepend-left-20 - = form.label :push_events, class: 'list-label' do - %strong Push events - %p.light - This url will be triggered by a push to the repository - - if @service.supported_events.include?("tag_push") - %div - = form.check_box :tag_push_events, class: 'pull-left' - .prepend-left-20 - = form.label :tag_push_events, class: 'list-label' do - %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 +.form-group + = form.label :url, "Trigger", class: 'control-label' + + .col-sm-10 + - @service.supported_events.each do |event| + %div + = form.check_box service_event_field_name(event), class: 'pull-left' + .prepend-left-20 + = form.label service_event_field_name(event), class: 'list-label' do + %strong + = event.humanize + + - field = @service.event_field(event) + + - if field + %p + = form.text_field field[:name], class: "form-control", placeholder: field[:placeholder] + %p.light + = service_event_description(event) -- @service.fields.each do |field| +- @service.global_fields.each do |field| - type = field[:type] - if type == 'fieldset' diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index e020a7d4d00..8e2fcbdfab8 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -156,7 +156,7 @@ - project_ref = cross_project_reference(@project, issuable) .block.project-reference - .sidebar-collapsed-icon + .sidebar-collapsed-icon.dont-change-state = clipboard_button(clipboard_text: project_ref) .cross-project-reference.hide-collapsed %span diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index db2b4885861..c7f39868e71 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -2,7 +2,7 @@ - page_description @user.bio - content_for :page_specific_javascripts do = 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) - @no_container = true diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 8551288e2f2..0b6a01a3200 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -28,12 +28,12 @@ class EmailsOnPushWorker :push end - merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha) - diff_refs = nil compare = nil reverse_compare = false + if action == :push + merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha) compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) diff_refs = Gitlab::Diff::DiffRefs.new( |