diff options
Diffstat (limited to 'app')
53 files changed, 325 insertions, 289 deletions
diff --git a/app/assets/javascripts/branches/branches_delete_modal.js b/app/assets/javascripts/branches/branches_delete_modal.js index af8bcdc1794..cbc28374b80 100644 --- a/app/assets/javascripts/branches/branches_delete_modal.js +++ b/app/assets/javascripts/branches/branches_delete_modal.js @@ -7,6 +7,7 @@ class DeleteModal { this.$branchName = $('.js-branch-name', this.$modal); this.$confirmInput = $('.js-delete-branch-input', this.$modal); this.$deleteBtn = $('.js-delete-branch', this.$modal); + this.$notMerged = $('.js-not-merged', this.$modal); this.bindEvents(); } @@ -16,8 +17,10 @@ class DeleteModal { } setModalData(e) { - this.branchName = e.currentTarget.dataset.branchName || ''; - this.deletePath = e.currentTarget.dataset.deletePath || ''; + const branchData = e.currentTarget.dataset; + this.branchName = branchData.branchName || ''; + this.deletePath = branchData.deletePath || ''; + this.isMerged = !!branchData.isMerged; this.updateModal(); } @@ -30,6 +33,7 @@ class DeleteModal { this.$confirmInput.val(''); this.$deleteBtn.attr('href', this.deletePath); this.$deleteBtn.attr('disabled', true); + this.$notMerged.toggleClass('hidden', this.isMerged); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js index 7246ccbb281..720fbc87ea0 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_user.js @@ -15,6 +15,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { params: { per_page: 20, active: true, + group_id: this.getGroupId(), project_id: this.getProjectId(), current_user: true, }, @@ -47,6 +48,10 @@ class DropdownUser extends gl.FilteredSearchDropdown { super.renderContent(forceShowList); } + getGroupId() { + return this.input.getAttribute('data-group-id'); + } + getProjectId() { return this.input.getAttribute('data-project-id'); } diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index ad8254167a2..157280d66e3 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -77,10 +77,11 @@ export const hideMenu = (el) => { export const moveSubItemsToPosition = (el, subItems) => { const boundingRect = el.getBoundingClientRect(); const top = calculateTop(boundingRect, subItems.offsetHeight); + const left = sidebar ? sidebar.offsetWidth : 50; const isAbove = top < boundingRect.top; subItems.classList.add('fly-out-list'); - subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign + subItems.style.transform = `translate3d(${left}px, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign const subItemsRect = subItems.getBoundingClientRect(); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 7d7f91227f9..2538d9c2093 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -127,13 +127,6 @@ import DropdownUtils from './filtered_search/dropdown_utils'; $('.has-tooltip', $value).tooltip({ container: 'body' }); - return $value.find('a').each(function(i) { - return setTimeout((function(_this) { - return function() { - return gl.animate.animate($(_this), 'pulse'); - }; - })(this), 200 * i); - }); }); }; $dropdown.glDropdown({ diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js deleted file mode 100644 index d93c1d0da59..00000000000 --- a/app/assets/javascripts/lib/utils/animate.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, max-len */ -(function() { - (function(w) { - if (w.gl == null) { - w.gl = {}; - } - if (gl.animate == null) { - gl.animate = {}; - } - gl.animate.animate = function($el, animation, options, done) { - if ((options != null ? options.cssStart : void 0) != null) { - $el.css(options.cssStart); - } - $el.removeClass(animation + ' animated').addClass(animation + ' animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() { - $(this).removeClass(animation + ' animated'); - if (done != null) { - done(); - } - if ((options != null ? options.cssEnd : void 0) != null) { - $el.css(options.cssEnd); - } - }); - }; - gl.animate.animateEach = function($els, animation, time, options, done) { - var dfd; - dfd = $.Deferred(); - if (!$els.length) { - dfd.resolve(); - } - $els.each(function(i) { - setTimeout((function(_this) { - return function() { - var $this; - $this = $(_this); - return gl.animate.animate($this, animation, options, function() { - if (i === $els.length - 1) { - dfd.resolve(); - if (done != null) { - return done(); - } - } - }); - }; - })(this), time * i); - }); - return dfd.promise(); - }; - })(window); -}).call(window); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 0bc31a56684..0f84470828a 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -39,7 +39,6 @@ import './commit/file'; import './commit/image_file'; // lib/utils -import './lib/utils/animate'; import './lib/utils/bootstrap_linked_tabs'; import './lib/utils/common_utils'; import './lib/utils/datetime_utility'; diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index cea4f35096a..f2eb2338a1e 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -15,7 +15,6 @@ export default class NewNavSidebar { this.$openSidebar = $('.toggle-mobile-nav'); this.$closeSidebar = $('.close-nav-button'); this.$sidebarToggle = $('.js-toggle-sidebar'); - this.$topLevelLinks = $('.sidebar-top-level-items > li > a'); } bindEvents() { @@ -56,10 +55,6 @@ export default class NewNavSidebar { this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); } NewNavSidebar.setCollapsedCookie(collapsed); - - this.$topLevelLinks.attr('title', function updateTopLevelTitle() { - return collapsed ? this.getAttribute('aria-label') : ''; - }); } render() { diff --git a/app/assets/javascripts/notes/components/issue_note_actions.vue b/app/assets/javascripts/notes/components/issue_note_actions.vue index 60c172321d1..feb3e73194b 100644 --- a/app/assets/javascripts/notes/components/issue_note_actions.vue +++ b/app/assets/javascripts/notes/components/issue_note_actions.vue @@ -86,7 +86,7 @@ <div class="note-actions"> <span v-if="accessLevel" - class="note-role">{{accessLevel}}</span> + class="note-role note-role-access">{{accessLevel}}</span> <div v-if="canAddAwardEmoji" class="note-actions-item"> diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 5ffa67a1220..2f7717760ec 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -328,7 +328,7 @@ border-bottom: 1px solid $border-color; transition: padding $sidebar-transition-duration; text-align: center; - margin-top: $header-height; + margin-top: $new-navbar-height; .container-fluid { position: relative; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index ef58382ba41..48dc25d343b 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -78,16 +78,16 @@ .right-sidebar { border-left: 1px solid $border-color; - height: calc(100% - #{$header-height}); + height: calc(100% - #{$new-navbar-height}); &.affix { position: fixed; - top: $header-height; + top: $new-navbar-height; } } .with-performance-bar .right-sidebar.affix { - top: $header-height + $performance-bar-height; + top: $new-navbar-height + $performance-bar-height; } @mixin maintain-sidebar-dimensions { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 594b0bb0556..75cb9e009bb 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -13,6 +13,7 @@ $sidebar-breakpoint: 1024px; $darken-normal-factor: 7%; $darken-dark-factor: 10%; $darken-border-factor: 5%; +$darken-border-dashed-factor: 25%; $white-light: #fff; $white-normal: #f0f0f0; @@ -134,6 +135,7 @@ $border-white-normal: darken($white-normal, $darken-border-factor); $border-gray-light: darken($gray-light, $darken-border-factor); $border-gray-normal: darken($gray-normal, $darken-border-factor); +$border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor); $border-gray-dark: darken($white-normal, $darken-border-factor); /* diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 952002c83d1..8030854e527 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -105,7 +105,8 @@ $new-sidebar-collapsed-width: 50px; } &.sidebar-icons-only { - width: $new-sidebar-collapsed-width; + width: auto; + min-width: $new-sidebar-collapsed-width; .nav-sidebar-inner-scroll { overflow-x: hidden; @@ -124,6 +125,10 @@ $new-sidebar-collapsed-width: 50px; .fly-out-top-item { display: block; } + + .avatar-container { + margin-right: 0; + } } &.nav-sidebar-expanded { @@ -187,7 +192,7 @@ $new-sidebar-collapsed-width: 50px; .nav-sidebar-inner-scroll { height: 100%; width: 100%; - overflow: auto; + overflow: scroll; } .with-performance-bar .nav-sidebar { @@ -248,7 +253,7 @@ $new-sidebar-collapsed-width: 50px; @media (min-width: $screen-sm-min) { position: fixed; top: 0; - left: $new-sidebar-width; + left: 0; min-width: 150px; margin-top: -1px; padding: 4px 1px; @@ -386,10 +391,6 @@ $new-sidebar-collapsed-width: 50px; } .sidebar-sub-level-items { - @media (min-width: $screen-sm-min) { - left: $new-sidebar-collapsed-width; - } - &:not(.flyout-list) { display: none; } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 50ec5110bf1..359dd388d05 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -64,10 +64,10 @@ color: $gl-text-color; position: sticky; position: -webkit-sticky; - top: $header-height; + top: $new-navbar-height; &.affix { - top: $header-height; + top: $new-navbar-height; } // with sidebar @@ -174,10 +174,10 @@ .with-performance-bar .build-page { .top-bar { - top: $header-height + $performance-bar-height; + top: $new-navbar-height + $performance-bar-height; &.affix { - top: $header-height + $performance-bar-height; + top: $new-navbar-height + $performance-bar-height; } } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 54c3c0173ae..951580ea1fe 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -634,8 +634,16 @@ padding-top: 8px; padding-bottom: 8px; } + + .diff-changed-file { + display: flex; + align-items: center; + } } .diff-file-changes-path { - @include str-truncated(78%); + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index d4dc43035eb..cf5f933a762 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -95,6 +95,8 @@ } .omniauth-container { + font-size: 13px; + p { margin: 0; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 94e4f4334d4..6400b72742c 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -752,7 +752,7 @@ a.deploy-project-label { } li.missing { - border: 1px dashed $border-gray-normal; + border: 1px dashed $border-gray-normal-dashed; border-radius: $border-radius-default; a { diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index e5cba774dcb..a7ab481519d 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -10,9 +10,8 @@ class Admin::DeployKeysController < Admin::ApplicationController end def create - @deploy_key = deploy_keys.new(create_params.merge(user: current_user)) - - if @deploy_key.save + @deploy_key = DeployKeys::CreateService.new(current_user, create_params.merge(public: true)).execute + if @deploy_key.persisted? redirect_to admin_deploy_keys_path else render 'new' diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index dfc8bd0ba81..10e8e54f402 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -3,31 +3,10 @@ class AutocompleteController < ApplicationController skip_before_action :authenticate_user!, only: [:users, :award_emojis] before_action :load_project, only: [:users] - before_action :find_users, only: [:users] + before_action :load_group, only: [:users] def users - @users ||= User.none - @users = @users.active - @users = @users.reorder(:name) - @users = @users.search(params[:search]) if params[:search].present? - @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? - @users = @users.page(params[:page]).per(params[:per_page]) - - if params[:todo_filter].present? && current_user - @users = @users.todo_authors(current_user.id, params[:todo_state_filter]) - end - - if params[:search].blank? - # Include current user if available to filter by "Me" - if params[:current_user].present? && current_user - @users = [current_user, *@users].uniq - end - - if params[:author_id].present? && current_user - author = User.find_by_id(params[:author_id]) - @users = [author, *@users].uniq if author - end - end + @users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute render json: @users, only: [:name, :username, :id], methods: [:avatar_url] end @@ -60,26 +39,14 @@ class AutocompleteController < ApplicationController private - def find_users - @users = - if @project - user_ids = @project.team.users.pluck(:id) - - if params[:author_id].present? - user_ids << params[:author_id] - end - - User.where(id: user_ids) - elsif params[:group_id].present? + def load_group + @group ||= begin + if @project.blank? && params[:group_id].present? group = Group.find(params[:group_id]) return render_404 unless can?(current_user, :read_group, group) - - group.users - elsif current_user - User.all - else - User.none + group end + end end def load_project diff --git a/app/controllers/profiles/gpg_keys_controller.rb b/app/controllers/profiles/gpg_keys_controller.rb index 6779cc6ddac..689c76059f6 100644 --- a/app/controllers/profiles/gpg_keys_controller.rb +++ b/app/controllers/profiles/gpg_keys_controller.rb @@ -7,9 +7,9 @@ class Profiles::GpgKeysController < Profiles::ApplicationController end def create - @gpg_key = current_user.gpg_keys.new(gpg_key_params) + @gpg_key = GpgKeys::CreateService.new(current_user, gpg_key_params).execute - if @gpg_key.save + if @gpg_key.persisted? redirect_to profile_gpg_keys_path else @gpg_keys = current_user.gpg_keys.select(&:persisted?) diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index f9f0e8eef83..89d6d7f1b52 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -11,9 +11,9 @@ class Profiles::KeysController < Profiles::ApplicationController end def create - @key = current_user.keys.new(key_params) + @key = Keys::CreateService.new(current_user, key_params).execute - if @key.save + if @key.persisted? redirect_to profile_key_path(@key) else @keys = current_user.keys.select(&:persisted?) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 193549663ac..3c8eaa24080 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -27,7 +27,7 @@ class Projects::CompareController < Projects::ApplicationController def create if params[:from].blank? || params[:to].blank? - flash[:alert] = "You must select from and to branches" + flash[:alert] = "You must select a Source and a Target revision" from_to_vars = { from: params[:from].presence, to: params[:to].presence diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index c2e621fa190..cf8829ba95b 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create - @key = DeployKey.new(create_params.merge(user: current_user)) + @key = DeployKeys::CreateService.new(current_user, create_params).execute unless @key.valid? && @project.deploy_keys << @key flash[:alert] = @key.errors.full_messages.join(', ').html_safe diff --git a/app/finders/autocomplete_users_finder.rb b/app/finders/autocomplete_users_finder.rb new file mode 100644 index 00000000000..b8f52e31926 --- /dev/null +++ b/app/finders/autocomplete_users_finder.rb @@ -0,0 +1,60 @@ +class AutocompleteUsersFinder + attr_reader :current_user, :project, :group, :search, :skip_users, + :page, :per_page, :author_id, :params + + def initialize(params:, current_user:, project:, group:) + @current_user = current_user + @project = project + @group = group + @search = params[:search] + @skip_users = params[:skip_users] + @page = params[:page] + @per_page = params[:per_page] + @author_id = params[:author_id] + @params = params + end + + def execute + items = find_users + items = items.active + items = items.reorder(:name) + items = items.search(search) if search.present? + items = items.where.not(id: skip_users) if skip_users.present? + items = items.page(page).per(per_page) + + if params[:todo_filter].present? && current_user + items = items.todo_authors(current_user.id, params[:todo_state_filter]) + end + + if search.blank? + # Include current user if available to filter by "Me" + if params[:current_user].present? && current_user + items = [current_user, *items].uniq + end + + if author_id.present? && current_user + author = User.find_by_id(author_id) + items = [author, *items].uniq if author + end + end + + items + end + + private + + def find_users + return users_from_project if project + return group.users if group + return User.all if current_user + + User.none + end + + def users_from_project + user_ids = project.team.users.pluck(:id) + user_ids << author_id if author_id.present? + + User.where(id: user_ids) + end +end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 3308ab0c259..ee701076a14 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -119,8 +119,4 @@ module TabHelper 'active' if current_controller?('oauth/applications') end - - def sidebar_link(href, title: nil, css: nil, &block) - link_to capture(&block), href, title: (title if collapsed_sidebar?), class: css, aria: { label: title } - end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 476db384bbd..8d017b9b3b1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -453,6 +453,10 @@ module Ci .fabricate! end + def latest_builds_with_artifacts + @latest_builds_with_artifacts ||= builds.latest.with_artifacts + end + private def ci_yaml_from_repo diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 51768dd96bc..eae5eee4fee 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -28,10 +28,4 @@ class DeployKey < Key def can_push_to?(project) can_push? && has_access_to?(project) end - - private - - # we don't want to notify the user for deploy keys - def notify_user - end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 9b05f8b1cd5..44e39e21442 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -6,7 +6,10 @@ class Environment < ActiveRecord::Base belongs_to :project, required: true, validate: true - has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :deployments, + -> (env) { where(project_id: env.project_id) }, + dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' before_validation :nullify_external_url diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb index 1633acd4fa9..44deae4234b 100644 --- a/app/models/gpg_key.rb +++ b/app/models/gpg_key.rb @@ -36,7 +36,6 @@ class GpgKey < ActiveRecord::Base before_validation :extract_fingerprint, :extract_primary_keyid after_commit :update_invalid_gpg_signatures, on: :create - after_commit :notify_user, on: :create def primary_keyid super&.upcase @@ -107,8 +106,4 @@ class GpgKey < ActiveRecord::Base # only allows one key self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first end - - def notify_user - NotificationService.new.new_gpg_key(self) - end end diff --git a/app/models/key.rb b/app/models/key.rb index a6b4dcfec0d..4fa6cac2fd0 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -28,7 +28,6 @@ class Key < ActiveRecord::Base delegate :name, :email, to: :user, prefix: true after_commit :add_to_shell, on: :create - after_commit :notify_user, on: :create after_create :post_create_hook after_commit :remove_from_shell, on: :destroy after_destroy :post_destroy_hook @@ -118,8 +117,4 @@ class Key < ActiveRecord::Base "type is forbidden. Must be #{allowed_types}" end - - def notify_user - NotificationService.new.new_key(self) - end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 4a9a23fea1f..e279d8dd8c5 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -231,6 +231,13 @@ class Namespace < ActiveRecord::Base end def force_share_with_group_lock_on_descendants - descendants.update_all(share_with_group_lock: true) + return unless Group.supports_nested_groups? + + # We can't use `descendants.update_all` since Rails will throw away the WITH + # RECURSIVE statement. We also can't use WHERE EXISTS since we can't use + # different table aliases, hence we're just using WHERE IN. Since we have a + # maximum of 20 nested groups this should be fine. + Namespace.where(id: descendants.select(:id)) + .update_all(share_with_group_lock: true) end end diff --git a/app/models/project.rb b/app/models/project.rb index ff5638dd155..94ae0acbe1a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -161,7 +161,7 @@ class Project < ActiveRecord::Base has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true - has_one :project_feature + has_one :project_feature, inverse_of: :project has_one :statistics, class_name: 'ProjectStatistics' # Container repositories need to remove data from the container registry, @@ -190,7 +190,7 @@ class Project < ActiveRecord::Base has_one :auto_devops, class_name: 'ProjectAutoDevops' accepts_nested_attributes_for :variables, allow_destroy: true - accepts_nested_attributes_for :project_feature + accepts_nested_attributes_for :project_feature, update_only: true accepts_nested_attributes_for :import_data accepts_nested_attributes_for :auto_devops @@ -1163,6 +1163,23 @@ class Project < ActiveRecord::Base pipelines.order(id: :desc).find_by(sha: sha, ref: ref) end + def latest_successful_pipeline_for_default_branch + if defined?(@latest_successful_pipeline_for_default_branch) + return @latest_successful_pipeline_for_default_branch + end + + @latest_successful_pipeline_for_default_branch = + pipelines.latest_successful_for(default_branch) + end + + def latest_successful_pipeline_for(ref = nil) + if ref && ref != default_branch + pipelines.latest_successful_for(ref) + else + latest_successful_pipeline_for_default_branch + end + end + def enable_ci project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) end diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index fb1db0255aa..bfb8d703ec9 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -41,6 +41,8 @@ class ProjectFeature < ActiveRecord::Base # http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to belongs_to :project, -> { unscope(where: :pending_delete) } + validates :project, presence: true + validate :repository_children_level default_value_for :builds_access_level, value: ENABLED, allows_nil: false diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 9d37184be2c..6a3118a11b8 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -80,6 +80,6 @@ class PipelinesEmailService < Service end def retrieve_recipients(data) - recipients.to_s.split(',').reject(&:blank?) + recipients.to_s.split(/[,(?:\r?\n) ]+/).reject(&:empty?) end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 035f85a0b46..af9911ea045 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -90,6 +90,12 @@ class Repository ) end + # we need to have this method here because it is not cached in ::Git and + # the method is called multiple times for every request + def has_visible_content? + branch_count > 0 + end + def inspect "#<#{self.class.name}:#{@disk_path}>" end @@ -166,7 +172,7 @@ class Repository end def add_branch(user, branch_name, ref) - branch = raw_repository.add_branch(branch_name, committer: user, target: ref) + branch = raw_repository.add_branch(branch_name, user: user, target: ref) after_create_branch @@ -176,7 +182,7 @@ class Repository end def add_tag(user, tag_name, target, message = nil) - raw_repository.add_tag(tag_name, committer: user, target: target, message: message) + raw_repository.add_tag(tag_name, user: user, target: target, message: message) rescue Gitlab::Git::Repository::InvalidRef false end @@ -184,7 +190,7 @@ class Repository def rm_branch(user, branch_name) before_remove_branch - raw_repository.rm_branch(branch_name, committer: user) + raw_repository.rm_branch(branch_name, user: user) after_remove_branch true @@ -193,7 +199,7 @@ class Repository def rm_tag(user, tag_name) before_remove_tag - raw_repository.rm_tag(tag_name, committer: user) + raw_repository.rm_tag(tag_name, user: user) after_remove_tag true @@ -762,17 +768,23 @@ class Repository multi_action(**options) end - def with_branch(user, *args) - result = Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit| - yield start_commit - end + def with_cache_hooks + result = yield + + return unless result - newrev, should_run_after_create, should_run_after_create_branch = result + after_create if result.repo_created? + after_create_branch if result.branch_created? - after_create if should_run_after_create - after_create_branch if should_run_after_create_branch + result.newrev + end - newrev + def with_branch(user, *args) + with_cache_hooks do + Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit| + yield start_commit + end + end end # rubocop:disable Metrics/ParameterLists @@ -837,30 +849,13 @@ class Repository end end - def merge(user, source, merge_request, options = {}) - with_branch( - user, - merge_request.target_branch) do |start_commit| - our_commit = start_commit.sha - their_commit = source - - raise 'Invalid merge target' unless our_commit - raise 'Invalid merge source' unless their_commit - - merge_index = rugged.merge_commits(our_commit, their_commit) - break if merge_index.conflicts? - - actual_options = options.merge( - parents: [our_commit, their_commit], - tree: merge_index.write_tree(rugged) - ) - - commit_id = create_commit(actual_options) - merge_request.update(in_progress_merge_commit_sha: commit_id) - commit_id + def merge(user, source_sha, merge_request, message) + with_cache_hooks do + raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id| + merge_request.update(in_progress_merge_commit_sha: commit_id) + nil # Return value does not matter. + end end - rescue Gitlab::Git::CommitError # when merge_index.conflicts? - false end def revert( @@ -1151,12 +1146,6 @@ class Repository Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags)) end - def create_commit(params = {}) - params[:message].delete!("\r") - - Rugged::Commit.create(rugged, params) - end - def last_commit_for_path_by_gitaly(sha, path) c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path) commit(c) diff --git a/app/services/deploy_keys/create_service.rb b/app/services/deploy_keys/create_service.rb new file mode 100644 index 00000000000..16de3d08df2 --- /dev/null +++ b/app/services/deploy_keys/create_service.rb @@ -0,0 +1,7 @@ +module DeployKeys + class CreateService < Keys::BaseService + def execute + DeployKey.create(params.merge(user: user)) + end + end +end diff --git a/app/services/gpg_keys/create_service.rb b/app/services/gpg_keys/create_service.rb new file mode 100644 index 00000000000..e822a89c4d3 --- /dev/null +++ b/app/services/gpg_keys/create_service.rb @@ -0,0 +1,9 @@ +module GpgKeys + class CreateService < Keys::BaseService + def execute + key = user.gpg_keys.create(params) + notification_service.new_gpg_key(key) if key.persisted? + key + end + end +end diff --git a/app/services/keys/base_service.rb b/app/services/keys/base_service.rb new file mode 100644 index 00000000000..545832d0bd4 --- /dev/null +++ b/app/services/keys/base_service.rb @@ -0,0 +1,13 @@ +module Keys + class BaseService + attr_accessor :user, :params + + def initialize(user, params) + @user, @params = user, params + end + + def notification_service + NotificationService.new + end + end +end diff --git a/app/services/keys/create_service.rb b/app/services/keys/create_service.rb new file mode 100644 index 00000000000..e2e5a6c46c5 --- /dev/null +++ b/app/services/keys/create_service.rb @@ -0,0 +1,9 @@ +module Keys + class CreateService < ::Keys::BaseService + def execute + key = user.keys.create(params) + notification_service.new_key(key) if key.persisted? + key + end + end +end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index b2b6c5627fb..07cbd8f92a9 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -38,15 +38,9 @@ module MergeRequests private def commit - committer = repository.user_to_committer(current_user) + message = params[:commit_message] || merge_request.merge_commit_message - options = { - message: params[:commit_message] || merge_request.merge_commit_message, - author: committer, - committer: committer - } - - commit_id = repository.merge(current_user, source, merge_request, options) + commit_id = repository.merge(current_user, source, merge_request, message) raise MergeError, 'Conflicts detected during merge' unless commit_id diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index cb4ffcab778..13e292a18bf 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -24,7 +24,10 @@ module Projects success else - error('Project could not be updated!') + model_errors = project.errors.full_messages.to_sentence + error_message = model_errors.presence || 'Project could not be updated!' + + error(error_message) end end diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index bfd7dd25a7d..546cec4d565 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -7,6 +7,8 @@ %span.light - has_icon = provider_has_icon?(provider) = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}" - %fieldset.prepend-top-10 - = check_box_tag :remember_me - = label_tag :remember_me, 'Remember me' + %fieldset.prepend-top-10.checkbox.remember-me + %label + = check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox' + %span + Remember me diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index fcebb385a65..615238b94ad 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -7,7 +7,7 @@ .sidebar-context-title Admin Area %ul.sidebar-top-level-items = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do - = sidebar_link admin_root_path, title: _('Overview'), css: 'shortcuts-tree' do + = link_to admin_root_path, class: 'shortcuts-tree' do .nav-icon-container = custom_icon('overview') %span.nav-item-name @@ -53,7 +53,7 @@ ConvDev Index = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do - = sidebar_link admin_system_info_path, title: _('Monitoring') do + = link_to admin_system_info_path do .nav-icon-container = custom_icon('monitoring') %span.nav-item-name @@ -87,7 +87,7 @@ Requests Profiles = nav_link(controller: :broadcast_messages) do - = sidebar_link admin_broadcast_messages_path, title: _('Messages') do + = link_to admin_broadcast_messages_path do .nav-icon-container = custom_icon('messages') %span.nav-item-name @@ -99,7 +99,7 @@ #{ _('Messages') } = nav_link(controller: [:hooks, :hook_logs]) do - = sidebar_link admin_hooks_path, title: _('Hooks') do + = link_to admin_hooks_path do .nav-icon-container = custom_icon('system_hooks') %span.nav-item-name @@ -111,7 +111,7 @@ #{ _('System Hooks') } = nav_link(controller: :applications) do - = sidebar_link admin_applications_path, title: _('Applications') do + = link_to admin_applications_path do .nav-icon-container = custom_icon('applications') %span.nav-item-name @@ -123,7 +123,7 @@ #{ _('Applications') } = nav_link(controller: :abuse_reports) do - = sidebar_link admin_abuse_reports_path, title: _("Abuse Reports") do + = link_to admin_abuse_reports_path do .nav-icon-container = custom_icon('abuse_reports') %span.nav-item-name @@ -138,7 +138,7 @@ - if akismet_enabled? = nav_link(controller: :spam_logs) do - = sidebar_link admin_spam_logs_path, title: _("Spam Logs") do + = link_to admin_spam_logs_path do .nav-icon-container = custom_icon('spam_logs') %span.nav-item-name @@ -150,7 +150,7 @@ #{ _('Spam Logs') } = nav_link(controller: :deploy_keys) do - = sidebar_link admin_deploy_keys_path, title: _('Deploy Keys') do + = link_to admin_deploy_keys_path do .nav-icon-container = custom_icon('key') %span.nav-item-name @@ -162,7 +162,7 @@ #{ _('Deploy Keys') } = nav_link(controller: :services) do - = sidebar_link admin_application_settings_services_path, title: _('Service Templates') do + = link_to admin_application_settings_services_path do .nav-icon-container = custom_icon('service_templates') %span.nav-item-name @@ -174,7 +174,7 @@ #{ _('Service Templates') } = nav_link(controller: :labels) do - = sidebar_link admin_labels_path, title: _('Labels') do + = link_to admin_labels_path do .nav-icon-container = custom_icon('labels') %span.nav-item-name @@ -186,7 +186,7 @@ #{ _('Labels') } = nav_link(controller: :appearances) do - = sidebar_link admin_appearances_path, title: _('Appearances') do + = link_to admin_appearances_path do .nav-icon-container = custom_icon('appearance') %span.nav-item-name @@ -198,7 +198,7 @@ #{ _('Appearance') } = nav_link(controller: :application_settings) do - = sidebar_link admin_application_settings_path, title: _('Settings') do + = link_to admin_application_settings_path do .nav-icon-container = custom_icon('settings') %span.nav-item-name diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index e01dfa7c854..cb44c012f56 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -11,7 +11,7 @@ = @group.name %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do - = sidebar_link group_path(@group), title: _('Group overview') do + = link_to group_path(@group) do .nav-icon-container = custom_icon('project') %span.nav-item-name @@ -34,7 +34,7 @@ Activity = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do - = sidebar_link issues_group_path(@group), title: _('Issues') do + = link_to issues_group_path(@group) do .nav-icon-container = custom_icon('issues') %span.nav-item-name @@ -64,7 +64,7 @@ Milestones = nav_link(path: 'groups#merge_requests') do - = sidebar_link merge_requests_group_path(@group), title: _('Merge Requests') do + = link_to merge_requests_group_path(@group) do .nav-icon-container = custom_icon('mr_bold') %span.nav-item-name @@ -77,19 +77,19 @@ #{ _('Merge Requests') } %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count) = nav_link(path: 'group_members#index') do - = sidebar_link group_group_members_path(@group), title: _('Members') do + = link_to group_group_members_path(@group) do .nav-icon-container = custom_icon('members') %span.nav-item-name Members %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do - = link_to merge_requests_group_path(@group) do + = link_to group_group_members_path(@group) do %strong.fly-out-top-item-name #{ _('Members') } - if current_user && can?(current_user, :admin_group, @group) = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do - = sidebar_link edit_group_path(@group), title: _('Settings') do + = link_to edit_group_path(@group) do .nav-icon-container = custom_icon('settings') %span.nav-item-name diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index 4c26d107ea7..2c402591f62 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -7,7 +7,7 @@ .sidebar-context-title User Settings %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do - = sidebar_link profile_path, title: _('Profile Settings') do + = link_to profile_path do .nav-icon-container = custom_icon('profile') %span.nav-item-name @@ -18,7 +18,7 @@ %strong.fly-out-top-item-name #{ _('Profile') } = nav_link(controller: [:accounts, :two_factor_auths]) do - = sidebar_link profile_account_path, title: _('Account') do + = link_to profile_account_path do .nav-icon-container = custom_icon('account') %span.nav-item-name @@ -30,7 +30,7 @@ #{ _('Account') } - if current_application_settings.user_oauth_applications? = nav_link(controller: 'oauth/applications') do - = sidebar_link applications_profile_path, title: _('Applications') do + = link_to applications_profile_path do .nav-icon-container = custom_icon('applications') %span.nav-item-name @@ -41,7 +41,7 @@ %strong.fly-out-top-item-name #{ _('Applications') } = nav_link(controller: :chat_names) do - = sidebar_link profile_chat_names_path, title: _('Chat') do + = link_to profile_chat_names_path do .nav-icon-container = custom_icon('chat') %span.nav-item-name @@ -52,7 +52,7 @@ %strong.fly-out-top-item-name #{ _('Chat') } = nav_link(controller: :personal_access_tokens) do - = sidebar_link profile_personal_access_tokens_path, title: _('Access Tokens') do + = link_to profile_personal_access_tokens_path do .nav-icon-container = custom_icon('access_tokens') %span.nav-item-name @@ -63,7 +63,7 @@ %strong.fly-out-top-item-name #{ _('Access Tokens') } = nav_link(controller: :emails) do - = sidebar_link profile_emails_path, title: _('Emails') do + = link_to profile_emails_path do .nav-icon-container = custom_icon('emails') %span.nav-item-name @@ -75,7 +75,7 @@ #{ _('Emails') } - unless current_user.ldap_user? = nav_link(controller: :passwords) do - = sidebar_link edit_profile_password_path, title: _('Password') do + = link_to edit_profile_password_path do .nav-icon-container = custom_icon('lock') %span.nav-item-name @@ -86,7 +86,7 @@ %strong.fly-out-top-item-name #{ _('Password') } = nav_link(controller: :notifications) do - = sidebar_link profile_notifications_path, title: _('Notifications') do + = link_to profile_notifications_path do .nav-icon-container = custom_icon('notifications') %span.nav-item-name @@ -97,7 +97,7 @@ %strong.fly-out-top-item-name #{ _('Notifications') } = nav_link(controller: :keys) do - = sidebar_link profile_keys_path, title: _('SSH Keys') do + = link_to profile_keys_path do .nav-icon-container = custom_icon('key') %span.nav-item-name @@ -108,7 +108,7 @@ %strong.fly-out-top-item-name #{ _('SSH Keys') } = nav_link(controller: :gpg_keys) do - = sidebar_link profile_gpg_keys_path, title: _('GPG Keys') do + = link_to profile_gpg_keys_path do .nav-icon-container = custom_icon('key_2') %span.nav-item-name @@ -119,7 +119,7 @@ %strong.fly-out-top-item-name #{ _('GPG Keys') } = nav_link(controller: :preferences) do - = sidebar_link profile_preferences_path, title: _('Preferences') do + = link_to profile_preferences_path do .nav-icon-container = custom_icon('preferences') %span.nav-item-name @@ -130,7 +130,7 @@ %strong.fly-out-top-item-name #{ _('Preferences') } = nav_link(path: 'profiles#audit_log') do - = sidebar_link audit_log_profile_path, title: _('Authentication log') do + = link_to audit_log_profile_path do .nav-icon-container = custom_icon('authentication_log') %span.nav-item-name diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 5f7a2d86c0f..29f1fc6b354 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -9,7 +9,7 @@ = @project.name %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do - = sidebar_link project_path(@project), title: _('Project overview'), css: 'shortcuts-project' do + = link_to project_path(@project), class: 'shortcuts-project' do .nav-icon-container = custom_icon('project') %span.nav-item-name @@ -36,7 +36,7 @@ - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do - = sidebar_link project_tree_path(@project), title: _('Repository'), css: 'shortcuts-tree' do + = link_to project_tree_path(@project), class: 'shortcuts-tree' do .nav-icon-container = custom_icon('doc_text') %span.nav-item-name @@ -82,7 +82,7 @@ - if project_nav_tab? :container_registry = nav_link(controller: %w[projects/registry/repositories]) do - = sidebar_link project_container_registry_index_path(@project), title: _('Container Registry'), css: 'shortcuts-container-registry' do + = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do .nav-icon-container = custom_icon('container_registry') %span.nav-item-name @@ -90,7 +90,7 @@ - if project_nav_tab? :issues = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do - = sidebar_link project_issues_path(@project), title: _('Issues'), css: 'shortcuts-issues' do + = link_to project_issues_path(@project), class: 'shortcuts-issues' do .nav-icon-container = custom_icon('issues') %span.nav-item-name @@ -144,7 +144,7 @@ - if project_nav_tab? :merge_requests = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do - = sidebar_link project_merge_requests_path(@project), title: _('Merge Requests'), css: 'shortcuts-merge_requests' do + = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do .nav-icon-container = custom_icon('mr_bold') %span.nav-item-name @@ -161,7 +161,7 @@ - if project_nav_tab? :pipelines = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do - = sidebar_link project_pipelines_path(@project), title: _('CI / CD'), css: 'shortcuts-pipelines' do + = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do .nav-icon-container = custom_icon('pipeline') %span.nav-item-name @@ -205,7 +205,7 @@ - if project_nav_tab? :wiki = nav_link(controller: :wikis) do - = sidebar_link get_project_wiki_path(@project), title: _('Wiki'), css: 'shortcuts-wiki' do + = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do .nav-icon-container = custom_icon('wiki') %span.nav-item-name @@ -218,7 +218,7 @@ - if project_nav_tab? :snippets = nav_link(controller: :snippets) do - = sidebar_link project_snippets_path(@project), title: _('Snippets'), css: 'shortcuts-snippets' do + = link_to project_snippets_path(@project), class: 'shortcuts-snippets' do .nav-icon-container = custom_icon('snippets') %span.nav-item-name @@ -231,7 +231,7 @@ - if project_nav_tab? :settings = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do - = sidebar_link edit_project_path(@project), title: _('Settings'), css: 'shortcuts-tree' do + = link_to edit_project_path(@project), class: 'shortcuts-tree' do .nav-icon-container = custom_icon('settings') %span.nav-item-name diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 19712a8f1be..05c1d2b383c 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -43,7 +43,8 @@ data: { toggle: "modal", target: "#modal-delete-branch", delete_path: project_branch_path(@project, branch.name), - branch_name: branch.name } } + branch_name: branch.name, + is_merged: ("true" if @repository.merged_to_root_ref?(branch.name)) } } = icon("trash-o") - else %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled", diff --git a/app/views/projects/branches/_delete_protected_modal.html.haml b/app/views/projects/branches/_delete_protected_modal.html.haml index c5888afa54d..f00a0ee6925 100644 --- a/app/views/projects/branches/_delete_protected_modal.html.haml +++ b/app/views/projects/branches/_delete_protected_modal.html.haml @@ -6,13 +6,18 @@ %h3.page-title Delete protected branch = surround "'", "'?" do - %span.js-branch-name>[branch name] + %span.js-branch-name.ref-name>[branch name] .modal-body %p You’re about to permanently delete the protected branch = succeed '.' do - %strong.js-branch-name [branch name] + %strong.js-branch-name.ref-name [branch name] + %p.js-not-merged + - default_branch = capture do + %span.ref-name= @repository.root_ref + = s_("Branches|This branch hasn’t been merged into %{default_branch}.").html_safe % { default_branch: default_branch } + = s_("Branches|To avoid data loss, consider merging this branch before deleting it.") %p Once you confirm and press = succeed ',' do diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 883922dbf04..9d85e027ac9 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,4 +1,4 @@ -- pipeline = local_assigns.fetch(:pipeline) { project.pipelines.latest_successful_for(ref) } +- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) } - if !project.empty_repo? && can?(current_user, :download_code, project) .project-action-button.dropdown.inline> @@ -26,18 +26,16 @@ %i.fa.fa-download %span= _('Download tar') - - if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = project.pipeline_for(ref) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do - %i.fa.fa-download - %span - #{ s_('DownloadArtifacts|Download') } '#{job.name}' + - if pipeline && pipeline.latest_builds_with_artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + - latest_pipeline = project.pipeline_for(ref) + %li + .unclickable= ci_status_for_statuseable(latest_pipeline) + %li.dropdown-header Previous Artifacts + - pipeline.latest_builds_with_artifacts.each do |job| + %li + = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do + %i.fa.fa-download + %span + #{s_('DownloadArtifacts|Download')} '#{job.name}' diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 94b7db5eb25..a518fced2b4 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -2,22 +2,22 @@ .clearfix - if params[:to] && params[:from] .compare-switch-container - = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Switch base of comparison'} - .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown - .input-group.inline-input-group - %span.input-group-addon from - = hidden_field_tag :from, params[:from] - = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do - .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag' - = render 'shared/ref_dropdown' - .compare-ellipsis.inline ... + = link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions' .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .input-group.inline-input-group - %span.input-group-addon to + %span.input-group-addon Source = hidden_field_tag :to, params[:to] = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do .dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag' = render 'shared/ref_dropdown' + .compare-ellipsis.inline ... + .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown + .input-group.inline-input-group + %span.input-group-addon Target + = hidden_field_tag :from, params[:from] + = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag' + = render 'shared/ref_dropdown' = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if @merge_request.present? diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 2632fea6eba..1ce3ad0c0fd 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -7,13 +7,19 @@ .sub-header-block Compare Git revisions. %br - Fill input field with commit SHA like - %code.ref-name 4eedf23 - or branch/tag name like - %code.ref-name master - and press compare button for the commits list and a code diff. + Choose a branch/tag (e.g. + = succeed ')' do + %code.ref-name master + or enter a commit SHA (e.g. + = succeed ')' do + %code.ref-name 4eedf23 + to see what's changed or to create a merge request. %br - Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field. + Changes are shown as if the + %b source + revision was being merged into the + %b target + revision. .prepend-top-20 = render "form" diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index ad2d355ab4a..2de2cf9e38c 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -21,9 +21,9 @@ %ul - diff_files.each do |diff_file| %li - %a{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path } + %a.diff-changed-file{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path } = icon("#{diff_file_changed_icon(diff_file)} fw", class: "#{diff_file_changed_icon_color(diff_file)} append-right-5") - %span.diff-file-changes-path= diff_file.new_path + %span.diff-file-changes-path.append-right-5= diff_file.new_path .pull-right %span.cgreen< +#{diff_file.added_lines} diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index de85615c672..e660fce652f 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -11,7 +11,7 @@ .col-sm-10 .checkbox = f.check_box :access_level, {}, 'ref_protected', 'not_protected' - %span.light This runner will only run on pipelines trigged on protected branches + %span.light This runner will only run on pipelines triggered on protected branches .form-group = label :run_untagged, 'Run untagged jobs', class: 'control-label' .col-sm-10 diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index c4ed7f6e750..d3f0aa2d339 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -11,13 +11,13 @@ - if params[:author_id].present? = hidden_field_tag(:author_id, params[:author_id]) = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit", - placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user.try(:username), current_user: true, project_id: @project.try(:id), selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) + placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) .filter-item.inline - if params[:assignee_id].present? = hidden_field_tag(:assignee_id, params[:assignee_id]) = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) + placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true |