summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md5
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/branches/branches_delete_modal.js8
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js5
-rw-r--r--app/assets/javascripts/fly_out_nav.js3
-rw-r--r--app/assets/javascripts/new_sidebar.js5
-rw-r--r--app/assets/javascripts/notes/components/issue_note_actions.vue2
-rw-r--r--app/assets/stylesheets/framework/nav.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss6
-rw-r--r--app/assets/stylesheets/new_sidebar.scss15
-rw-r--r--app/assets/stylesheets/pages/builds.scss8
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb5
-rw-r--r--app/controllers/profiles/gpg_keys_controller.rb4
-rw-r--r--app/controllers/profiles/keys_controller.rb4
-rw-r--r--app/controllers/projects/compare_controller.rb2
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/helpers/tab_helper.rb4
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/deploy_key.rb6
-rw-r--r--app/models/gpg_key.rb5
-rw-r--r--app/models/key.rb5
-rw-r--r--app/models/namespace.rb9
-rw-r--r--app/models/project.rb21
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/services/deploy_keys/create_service.rb7
-rw-r--r--app/services/gpg_keys/create_service.rb9
-rw-r--r--app/services/keys/base_service.rb13
-rw-r--r--app/services/keys/create_service.rb9
-rw-r--r--app/services/projects/update_service.rb5
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml24
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml10
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml24
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml18
-rw-r--r--app/views/projects/branches/_branch.html.haml3
-rw-r--r--app/views/projects/branches/_delete_protected_modal.html.haml9
-rw-r--r--app/views/projects/buttons/_download.html.haml30
-rw-r--r--app/views/projects/compare/_form.html.haml20
-rw-r--r--app/views/projects/compare/index.html.haml18
-rw-r--r--app/views/projects/runners/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_filter.html.haml4
-rw-r--r--changelogs/unreleased/20824-scope-users-to-members-in-group-issuable-list.yml5
-rw-r--r--changelogs/unreleased/21331-improve-confusing-compare-page.yml5
-rw-r--r--changelogs/unreleased/35917_create_services_for_keys.yml4
-rw-r--r--changelogs/unreleased/fix-sidebar-with-scrollbars.yml5
-rw-r--r--changelogs/unreleased/issue_37640.yml6
-rw-r--r--changelogs/unreleased/memoize-the-latest-builds-of-a-pipeline.yml5
-rw-r--r--changelogs/unreleased/projects-controller-show.yml5
-rw-r--r--changelogs/unreleased/winh-protected-branch-modal-merged.yml5
-rw-r--r--db/post_migrate/20170913180600_fix_projects_without_project_feature.rb33
-rw-r--r--doc/administration/reply_by_email.md31
-rw-r--r--doc/ci/environments.md100
-rw-r--r--doc/ci/img/environments_monitoring.pngbin243491 -> 76086 bytes
-rw-r--r--doc/ci/quick_start/README.md8
-rw-r--r--doc/ci/runners/README.md14
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/ci/triggers/README.md6
-rw-r--r--doc/ci/variables/README.md18
-rw-r--r--doc/ci/yaml/README.md13
-rw-r--r--doc/user/permissions.md2
-rw-r--r--doc/user/project/pipelines/settings.md2
-rw-r--r--[-rwxr-xr-x]doc/user/project/repository/img/compare_branches.pngbin35999 -> 206831 bytes
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/gitlab/database/read_only_relation.rb16
-rw-r--r--lib/gitlab/group_hierarchy.rb15
-rw-r--r--spec/features/groups/merge_requests_spec.rb2
-rw-r--r--spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb22
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js2
-rw-r--r--spec/javascripts/fly_out_nav_spec.js9
-rw-r--r--spec/lib/gitlab/group_hierarchy_spec.rb15
-rw-r--r--spec/models/ci/pipeline_spec.rb20
-rw-r--r--spec/models/gpg_key_spec.rb12
-rw-r--r--spec/models/key_spec.rb12
-rw-r--r--spec/models/project_spec.rb56
-rw-r--r--spec/services/deploy_keys/create_service_spec.rb12
-rw-r--r--spec/services/gpg_keys/create_service_spec.rb21
-rw-r--r--spec/services/keys/create_service_spec.rb21
-rw-r--r--spec/services/notification_service_spec.rb1
-rw-r--r--spec/services/projects/update_service_spec.rb23
-rw-r--r--spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb (renamed from spec/support/project_features_apply_to_issuables_shared_examples.rb)0
80 files changed, 642 insertions, 248 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1f25171e8a6..dfb2ce0099a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -286,7 +286,10 @@ might be edited to make them small and simple.
Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
-For changes in the interface, it can be helpful to create a mockup first.
+For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
+be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
+need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself.
+
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
diff --git a/Gemfile.lock b/Gemfile.lock
index bcbe6b4f394..e10db81d0c9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -357,7 +357,7 @@ GEM
rake
grape_logging (1.7.0)
grape
- grpc (1.4.5)
+ grpc (1.6.0)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
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/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/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/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/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/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/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/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/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/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..910e0d8b5ec 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,7 +77,7 @@
#{ _('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
@@ -89,7 +89,7 @@
#{ _('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'
&nbsp;
= 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/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
diff --git a/changelogs/unreleased/20824-scope-users-to-members-in-group-issuable-list.yml b/changelogs/unreleased/20824-scope-users-to-members-in-group-issuable-list.yml
new file mode 100644
index 00000000000..245b8129de8
--- /dev/null
+++ b/changelogs/unreleased/20824-scope-users-to-members-in-group-issuable-list.yml
@@ -0,0 +1,5 @@
+---
+title: Return only group's members in user dropdowns on issuables list pages
+merge_request: 14249
+author:
+type: changed
diff --git a/changelogs/unreleased/21331-improve-confusing-compare-page.yml b/changelogs/unreleased/21331-improve-confusing-compare-page.yml
new file mode 100644
index 00000000000..469cc04930b
--- /dev/null
+++ b/changelogs/unreleased/21331-improve-confusing-compare-page.yml
@@ -0,0 +1,5 @@
+---
+title: Make the labels in the Compare form less confusing
+merge_request: 14225
+author:
+type: changed
diff --git a/changelogs/unreleased/35917_create_services_for_keys.yml b/changelogs/unreleased/35917_create_services_for_keys.yml
new file mode 100644
index 00000000000..e7cad5a11d5
--- /dev/null
+++ b/changelogs/unreleased/35917_create_services_for_keys.yml
@@ -0,0 +1,4 @@
+---
+title: creation of keys moved to services
+merge_request: 13331
+author: haseebeqx
diff --git a/changelogs/unreleased/fix-sidebar-with-scrollbars.yml b/changelogs/unreleased/fix-sidebar-with-scrollbars.yml
new file mode 100644
index 00000000000..e0b3851b97f
--- /dev/null
+++ b/changelogs/unreleased/fix-sidebar-with-scrollbars.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed the sidebar scrollbar overlapping links
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_37640.yml b/changelogs/unreleased/issue_37640.yml
new file mode 100644
index 00000000000..d806ed64bed
--- /dev/null
+++ b/changelogs/unreleased/issue_37640.yml
@@ -0,0 +1,6 @@
+---
+title: Fix project feature being deleted when updating project with invalid visibility
+ level
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/memoize-the-latest-builds-of-a-pipeline.yml b/changelogs/unreleased/memoize-the-latest-builds-of-a-pipeline.yml
new file mode 100644
index 00000000000..5a7cd42b888
--- /dev/null
+++ b/changelogs/unreleased/memoize-the-latest-builds-of-a-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: "Memoize the latest builds of a pipeline on a project's homepage"
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/projects-controller-show.yml b/changelogs/unreleased/projects-controller-show.yml
new file mode 100644
index 00000000000..25f4a72710b
--- /dev/null
+++ b/changelogs/unreleased/projects-controller-show.yml
@@ -0,0 +1,5 @@
+---
+title: Memoize pipelines for project download buttons
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/winh-protected-branch-modal-merged.yml b/changelogs/unreleased/winh-protected-branch-modal-merged.yml
new file mode 100644
index 00000000000..63f1f424a5d
--- /dev/null
+++ b/changelogs/unreleased/winh-protected-branch-modal-merged.yml
@@ -0,0 +1,5 @@
+---
+title: Display whether branch has been merged when deleting protected branch
+merge_request: 14220
+author:
+type: changed
diff --git a/db/post_migrate/20170913180600_fix_projects_without_project_feature.rb b/db/post_migrate/20170913180600_fix_projects_without_project_feature.rb
new file mode 100644
index 00000000000..bfa9ad80c7d
--- /dev/null
+++ b/db/post_migrate/20170913180600_fix_projects_without_project_feature.rb
@@ -0,0 +1,33 @@
+class FixProjectsWithoutProjectFeature < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ # Deletes corrupted project features
+ sql = "DELETE FROM project_features WHERE project_id IS NULL"
+ execute(sql)
+
+ # Creates missing project features with private visibility
+ sql =
+ %Q{
+ INSERT INTO project_features(project_id, repository_access_level, issues_access_level, merge_requests_access_level, wiki_access_level,
+ builds_access_level, snippets_access_level, created_at, updated_at)
+ SELECT projects.id as project_id,
+ 10 as repository_access_level,
+ 10 as issues_access_level,
+ 10 as merge_requests_access_level,
+ 10 as wiki_access_level,
+ 10 as builds_access_level ,
+ 10 as snippets_access_level,
+ projects.created_at,
+ projects.updated_at
+ FROM projects
+ LEFT OUTER JOIN project_features ON project_features.project_id = projects.id
+ WHERE (project_features.id IS NULL)
+ }
+
+ execute(sql)
+ end
+
+ def down
+ end
+end
diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md
index e99a7ee29cc..1304476e678 100644
--- a/doc/administration/reply_by_email.md
+++ b/doc/administration/reply_by_email.md
@@ -77,6 +77,33 @@ and use [an application password](https://support.google.com/mail/answer/185833)
To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the
[Postfix setup documentation](reply_by_email_postfix_setup.md).
+### Security Concerns
+
+**WARNING:** Be careful when choosing the domain used for receiving incoming
+email.
+
+For the sake of example, suppose your top-level company domain is `hooli.com`.
+All employees in your company have an email address at that domain via Google
+Apps, and your company's private Slack instance requires a valid `@hooli.com`
+email address in order to sign up.
+
+If you also host a public-facing GitLab instance at `hooli.com` and set your
+incoming email domain to `hooli.com`, an attacker could abuse the "Create new
+issue by email" feature by using a project's unique address as the email when
+signing up for Slack, which would send a confirmation email, which would create
+a new issue on the project owned by the attacker, allowing them to click the
+confirmation link and validate their account on your company's private Slack
+instance.
+
+We recommend receiving incoming email on a subdomain, such as
+`incoming.hooli.com`, and ensuring that you do not employ any services that
+authenticate solely based on access to an email domain such as `*.hooli.com.`
+Alternatively, use a dedicated domain for GitLab email communications such as
+`hooli-gitlab.com`.
+
+See GitLab issue [#30366](https://gitlab.com/gitlab-org/gitlab-ce/issues/30366)
+for a real-world example of this exploit.
+
### Omnibus package installations
1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the
@@ -141,7 +168,7 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the
# The IDLE command timeout.
gitlab_rails['incoming_email_idle_timeout'] = 60
```
-
+
```ruby
# Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes mailbox incoming@exchange.example.com
gitlab_rails['incoming_email_enabled'] = true
@@ -253,7 +280,7 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the
# The IDLE command timeout.
idle_timeout: 60
```
-
+
```yaml
# Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes mailbox incoming@exchange.example.com
incoming_email:
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index c1362b7bd5b..acd5682841a 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -240,55 +240,18 @@ Remember that if your environment's name is `production` (all lowercase), then
it will get recorded in [Cycle Analytics](../user/project/cycle_analytics.md).
Double the benefit!
-## Web terminals
-
->**Note:**
-Web terminals were added in GitLab 8.15 and are only available to project
-masters and owners.
-
-If you deploy to your environments with the help of a deployment service (e.g.,
-the [Kubernetes service][kubernetes-service], GitLab can open
-a terminal session to your environment! This is a very powerful feature that
-allows you to debug issues without leaving the comfort of your web browser. To
-enable it, just follow the instructions given in the service documentation.
-
-Once enabled, your environments will gain a "terminal" button:
-
-![Terminal button on environment index](img/environments_terminal_button_on_index.png)
-
-You can also access the terminal button from the page for a specific environment:
-
-![Terminal button for an environment](img/environments_terminal_button_on_show.png)
-
-Wherever you find it, clicking the button will take you to a separate page to
-establish the terminal session:
-
-![Terminal page](img/environments_terminal_page.png)
-
-This works just like any other terminal - you'll be in the container created
-by your deployment, so you can run shell commands and get responses in real
-time, check the logs, try out configuration or code tweaks, etc. You can open
-multiple terminals to the same environment - they each get their own shell
-session - and even a multiplexer like `screen` or `tmux`!
-
->**Note:**
-Container-based deployments often lack basic tools (like an editor), and may
-be stopped or restarted at any time. If this happens, you will lose all your
-changes! Treat this as a debugging tool, not a comprehensive online IDE.
-
----
-
-While this is fine for deploying to some stable environments like staging or
-production, what happens for branches? So far we haven't defined anything
-regarding deployments for branches other than `master`. Dynamic environments
-will help us achieve that.
-
## Dynamic environments
As the name suggests, it is possible to create environments on the fly by just
declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is
the basis of [Review apps](review_apps/index.md).
+>**Note:**
+The `name` and `url` parameters can use any of the defined CI variables,
+including predefined, secure variables and `.gitlab-ci.yml`
+[`variables`](yaml/README.md#variables).
+You however cannot use variables defined under `script` or on the Runner's side.
+
GitLab Runner exposes various [environment variables][variables] when a job runs,
and as such, you can use them as environment names. Let's add another job in
our example which will deploy to all branches except `master`:
@@ -434,7 +397,8 @@ Let's briefly see where URL that's defined in the environments is exposed.
## Making use of the environment URL
-The environment URL is exposed in a few places within GitLab.
+The [environment URL](yaml/README.md#environments-url) is exposed in a few
+places within GitLab.
| In a merge request widget as a link | In the Environments view as a button | In the Deployments view as a button |
| -------------------- | ------------ | ----------- |
@@ -598,7 +562,7 @@ exist, you should see something like:
>**Notes:**
>
-- For the monitor dashboard to appear, you need to:
+- For the monitoring dashboard to appear, you need to:
- Have enabled the [Prometheus integration][prom]
- Configured Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/metrics.md)
- With GitLab 9.2, all deployments to an environment are shown directly on the
@@ -608,8 +572,7 @@ If you have enabled [Prometheus for monitoring system and response metrics](http
Once configured, GitLab will attempt to retrieve [supported performance metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus_library/metrics.html) for any
environment which has had a successful deployment. If monitoring data was
-successfully retrieved, a Monitoring button will appear on the environment's
-detail page.
+successfully retrieved, a Monitoring button will appear for each environment.
![Environment Detail with Metrics](img/prometheus_environment_detail_with_metrics.png)
@@ -623,6 +586,49 @@ version of the app, all without leaving GitLab.
![Monitoring dashboard](img/environments_monitoring.png)
+## Web terminals
+
+>**Note:**
+Web terminals were added in GitLab 8.15 and are only available to project
+masters and owners.
+
+If you deploy to your environments with the help of a deployment service (e.g.,
+the [Kubernetes service][kubernetes-service], GitLab can open
+a terminal session to your environment! This is a very powerful feature that
+allows you to debug issues without leaving the comfort of your web browser. To
+enable it, just follow the instructions given in the service documentation.
+
+Once enabled, your environments will gain a "terminal" button:
+
+![Terminal button on environment index](img/environments_terminal_button_on_index.png)
+
+You can also access the terminal button from the page for a specific environment:
+
+![Terminal button for an environment](img/environments_terminal_button_on_show.png)
+
+Wherever you find it, clicking the button will take you to a separate page to
+establish the terminal session:
+
+![Terminal page](img/environments_terminal_page.png)
+
+This works just like any other terminal - you'll be in the container created
+by your deployment, so you can run shell commands and get responses in real
+time, check the logs, try out configuration or code tweaks, etc. You can open
+multiple terminals to the same environment - they each get their own shell
+session - and even a multiplexer like `screen` or `tmux`!
+
+>**Note:**
+Container-based deployments often lack basic tools (like an editor), and may
+be stopped or restarted at any time. If this happens, you will lose all your
+changes! Treat this as a debugging tool, not a comprehensive online IDE.
+
+---
+
+While this is fine for deploying to some stable environments like staging or
+production, what happens for branches? So far we haven't defined anything
+regarding deployments for branches other than `master`. Dynamic environments
+will help us achieve that.
+
## Checkout deployments locally
Since 8.13, a reference in the git repository is saved for each deployment, so
diff --git a/doc/ci/img/environments_monitoring.png b/doc/ci/img/environments_monitoring.png
index d9c46ea4c95..dcffdd1fdb8 100644
--- a/doc/ci/img/environments_monitoring.png
+++ b/doc/ci/img/environments_monitoring.png
Binary files differ
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 88e53ff40e8..2d56b2540ef 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -106,7 +106,7 @@ What is important is that each job is run independently from each other.
If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
-a "CI Lint" button to go to this page under **Pipelines ➔ Pipelines** and
+a "CI Lint" button to go to this page under **CI/CD ➔ Pipelines** and
**Pipelines ➔ Jobs** in your project.
For more information and a complete `.gitlab-ci.yml` syntax, please read
@@ -155,7 +155,7 @@ Find more information about different Runners in the
[Runners](../runners/README.md) documentation.
You can find whether any Runners are assigned to your project by going to
-**Settings ➔ Pipelines**. Setting up a Runner is easy and straightforward. The
+**Settings ➔ CI/CD**. Setting up a Runner is easy and straightforward. The
official Runner supported by GitLab is written in Go and its documentation
can be found at <https://docs.gitlab.com/runner/>.
@@ -168,7 +168,7 @@ Follow the links above to set up your own Runner or use a Shared Runner as
described in the next section.
Once the Runner has been set up, you should see it on the Runners page of your
-project, following **Settings ➔ Pipelines**.
+project, following **Settings ➔ CI/CD**.
![Activated runners](img/runners_activated.png)
@@ -181,7 +181,7 @@ These are special virtual machines that run on GitLab's infrastructure and can
build any project.
To enable the **Shared Runners** you have to go to your project's
-**Settings ➔ Pipelines** and click **Enable shared runners**.
+**Settings ➔ CI/CD** and click **Enable shared runners**.
[Read more on Shared Runners](../runners/README.md).
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index bac8e972754..8b51d112a2c 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -35,7 +35,7 @@ are:
A Runner that is specific only runs for the specified project(s). A shared Runner
can run jobs for every project that has enabled the option **Allow shared Runners**
-under **Settings ➔ Pipelines**.
+under **Settings ➔ CI/CD**.
Projects with high demand of CI activity can also benefit from using specific
Runners. By having dedicated Runners you are guaranteed that the Runner is not
@@ -61,7 +61,7 @@ You can only register a shared Runner if you are an admin of the GitLab instance
Shared Runners are enabled by default as of GitLab 8.2, but can be disabled
with the **Disable shared Runners** button which is present under each project's
-**Settings ➔ Pipelines** page. Previous versions of GitLab defaulted shared
+**Settings ➔ CI/CD** page. Previous versions of GitLab defaulted shared
Runners to disabled.
## Registering a specific Runner
@@ -76,7 +76,7 @@ Registering a specific can be done in two ways:
To create a specific Runner without having admin rights to the GitLab instance,
visit the project you want to make the Runner work for in GitLab:
-1. Go to **Settings ➔ Pipelines** to obtain the token
+1. Go to **Settings ➔ CI/CD** to obtain the token
1. [Register the Runner][register]
### Making an existing shared Runner specific
@@ -101,7 +101,7 @@ can be changed afterwards under each Runner's settings.
To lock/unlock a Runner:
-1. Visit your project's **Settings ➔ Pipelines**
+1. Visit your project's **Settings ➔ CI/CD**
1. Find the Runner you wish to lock/unlock and make sure it's enabled
1. Click the pencil button
1. Check the **Lock to current projects** option
@@ -115,7 +115,7 @@ you can enable the Runner also on any other project where you have Master permis
To enable/disable a Runner in your project:
-1. Visit your project's **Settings ➔ Pipelines**
+1. Visit your project's **Settings ➔ CI/CD**
1. Find the Runner you wish to enable/disable
1. Click **Enable for this project** or **Disable for this project**
@@ -136,7 +136,7 @@ Whenever a Runner is protected, the Runner picks only jobs created on
To protect/unprotect Runners:
-1. Visit your project's **Settings ➔ Pipelines**
+1. Visit your project's **Settings ➔ CI/CD**
1. Find a Runner you want to protect/unprotect and make sure it's enabled
1. Click the pencil button besides the Runner name
1. Check the **Protected** option
@@ -220,7 +220,7 @@ each Runner's settings.
To make a Runner pick tagged/untagged jobs:
-1. Visit your project's **Settings ➔ Pipelines**
+1. Visit your project's **Settings ➔ CI/CD**
1. Find the Runner you wish and make sure it's enabled
1. Click the pencil button
1. Check the **Run untagged jobs** option
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index cdb9858e179..e5a2bbd1773 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -34,7 +34,7 @@ instructions to [generate an SSH key](../../ssh/README.md). Do not add a
passphrase to the SSH key, or the `before_script` will prompt for it.
Then, create a new **Secret Variable** in your project settings on GitLab
-following **Settings > Pipelines** and look for the "Secret Variables" section.
+following **Settings > CI/CD** and look for the "Secret Variables" section.
As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the
content of your _private_ key that you created earlier.
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 7ec7136d8c6..56a16f77e7f 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -19,7 +19,7 @@ A unique trigger token can be obtained when [adding a new trigger](#adding-a-new
## Adding a new trigger
You can add a new trigger by going to your project's
-**Settings ➔ Pipelines** under **Triggers**. The **Add trigger** button will
+**Settings ➔ CI/CD** under **Triggers**. The **Add trigger** button will
create a new token which you can then use to trigger a rerun of this
particular project's pipeline.
@@ -43,7 +43,7 @@ From now on the trigger will be run as you.
## Revoking a trigger
You can revoke a trigger any time by going at your project's
-**Settings ➔ Pipelines** under **Triggers** and hitting the **Revoke** button.
+**Settings ➔ CI/CD** under **Triggers** and hitting the **Revoke** button.
The action is irreversible.
## Triggering a pipeline
@@ -64,7 +64,7 @@ POST /projects/:id/trigger/pipeline
The required parameters are the [trigger's `token`](#authentication-tokens)
and the Git `ref` on which the trigger will be performed. Valid refs are the
branch and the tag. The `:id` of a project can be found by
-[querying the API](../../api/projects.md) or by visiting the **Pipelines**
+[querying the API](../../api/projects.md) or by visiting the **CI/CD**
settings page which provides self-explanatory examples.
When a rerun of a pipeline is triggered, the information is exposed in GitLab's
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 6513b31826a..ebcb92b5db1 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -158,17 +158,17 @@ script:
settings. Follow the discussion in issue [#13784][ce-13784] for masking the
secret variables.
-GitLab CI allows you to define per-project or per-group **secret variables**
-that are set in the build environment. The secret variables are stored out of
-the repository (`.gitlab-ci.yml`) and are securely passed to GitLab Runner
-making them available in the build environment. It's the recommended method to
-use for storing things like passwords, secret keys and credentials.
+GitLab CI allows you to define per-project or per-group secret variables
+that are set in the pipeline environment. The secret variables are stored out of
+the repository (not in `.gitlab-ci.yml`) and are securely passed to GitLab Runner
+making them available during a pipeline run. It's the recommended method to
+use for storing things like passwords, SSH keys and credentials.
Project-level secret variables can be added by going to your project's
-**Settings ➔ Pipelines**, then finding the section called **Secret variables**.
+**Settings > CI/CD**, then finding the section called **Secret variables**.
Likewise, group-level secret variables can be added by going to your group's
-**Settings ➔ Pipelines**, then finding the section called **Secret variables**.
+**Settings > CI/CD**, then finding the section called **Secret variables**.
Any variables of [subgroups] will be inherited recursively.
Once you set them, they will be available for all subsequent pipelines. You can also
@@ -185,8 +185,8 @@ protected, it would only be securely passed to pipelines running on the
protected variables.
Protected variables can be added by going to your project's
-**Settings ➔ Pipelines**, then finding the section called
-**Secret variables**, and check *Protected*.
+**Settings > CI/CD**, then finding the section called
+**Secret variables**, and check "Protected".
Once you set them, they will be available for all subsequent pipelines.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 78733b9cc4b..f69d71a5c39 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -727,6 +727,9 @@ deployment to the `production` environment.
- Before GitLab 8.11, the name of an environment could be defined as a string like
`environment: production`. The recommended way now is to define it under the
`name` keyword.
+- The `name` parameter can use any of the defined CI variables,
+ including predefined, secure variables and `.gitlab-ci.yml` [`variables`](#variables).
+ You however cannot use variables defined under `script`.
The `environment` name can contain:
@@ -762,6 +765,9 @@ deploy to production:
- Introduced in GitLab 8.11.
- Before GitLab 8.11, the URL could be added only in GitLab's UI. The
recommended way now is to define it in `.gitlab-ci.yml`.
+- The `url` parameter can use any of the defined CI variables,
+ including predefined, secure variables and `.gitlab-ci.yml` [`variables`](#variables).
+ You however cannot use variables defined under `script`.
This is an optional value that when set, it exposes buttons in various places
in GitLab which when clicked take you to the defined URL.
@@ -841,10 +847,9 @@ The `stop_review_app` job is **required** to have the following keywords defined
**Notes:**
- [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
- The `$CI_ENVIRONMENT_SLUG` was [introduced][ce-7983] in GitLab 8.15.
-
-`environment` can also represent a configuration hash with `name` and `url`.
-These parameters can use any of the defined [CI variables](#variables)
-(including predefined, secure variables and `.gitlab-ci.yml` variables).
+- The `name` and `url` parameters can use any of the defined CI variables,
+ including predefined, secure variables and `.gitlab-ci.yml` [`variables`](#variables).
+ You however cannot use variables defined under `script`.
For example:
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 0c17905aa8c..44ee994a26b 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -253,7 +253,7 @@ only.
[^1]: On public and internal projects, all users are able to perform this action.
[^2]: Guest users can only view the confidential issues they created themselves
-[^3]: If **Public pipelines** is enabled in **Project Settings > Pipelines**
+[^3]: If **Public pipelines** is enabled in **Project Settings > CI/CD**
[^4]: Not allowed for Guest, Reporter, Developer, Master, or Owner
[^5]: Only if user is not external one.
[^6]: Only if user is a member of the project.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index dbc1305101f..56f58fd755a 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -1,7 +1,7 @@
# Pipelines settings
To reach the pipelines settings navigate to your project's
-**Settings ➔ Pipelines**.
+**Settings ➔ CI/CD**.
The following settings can be configured per project.
diff --git a/doc/user/project/repository/img/compare_branches.png b/doc/user/project/repository/img/compare_branches.png
index 353bd72ef4e..d7ab587f030 100755..100644
--- a/doc/user/project/repository/img/compare_branches.png
+++ b/doc/user/project/repository/img/compare_branches.png
Binary files differ
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 1825c90a23b..bdebda58d3f 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -88,7 +88,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user && can?(current_user, :read_user, user)
- opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : {}
+ opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : { with: Entities::User }
present user, opts
end
diff --git a/lib/gitlab/database/read_only_relation.rb b/lib/gitlab/database/read_only_relation.rb
new file mode 100644
index 00000000000..4571ad122ce
--- /dev/null
+++ b/lib/gitlab/database/read_only_relation.rb
@@ -0,0 +1,16 @@
+module Gitlab
+ module Database
+ # Module that can be injected into a ActiveRecord::Relation to make it
+ # read-only.
+ module ReadOnlyRelation
+ [:delete, :delete_all, :update, :update_all].each do |method|
+ define_method(method) do |*args|
+ raise(
+ ActiveRecord::ReadOnlyRecord,
+ "This relation is marked as read-only"
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/group_hierarchy.rb
index 5a31e56cb30..635f52131f9 100644
--- a/lib/gitlab/group_hierarchy.rb
+++ b/lib/gitlab/group_hierarchy.rb
@@ -22,7 +22,7 @@ module Gitlab
def base_and_ancestors
return ancestors_base unless Group.supports_nested_groups?
- base_and_ancestors_cte.apply_to(model.all)
+ read_only(base_and_ancestors_cte.apply_to(model.all))
end
# Returns a relation that includes the descendants_base set of groups
@@ -30,7 +30,7 @@ module Gitlab
def base_and_descendants
return descendants_base unless Group.supports_nested_groups?
- base_and_descendants_cte.apply_to(model.all)
+ read_only(base_and_descendants_cte.apply_to(model.all))
end
# Returns a relation that includes the base groups, their ancestors,
@@ -67,11 +67,13 @@ module Gitlab
union = SQL::Union.new([model.unscoped.from(ancestors_table),
model.unscoped.from(descendants_table)])
- model
+ relation = model
.unscoped
.with
.recursive(ancestors.to_arel, descendants.to_arel)
.from("(#{union.to_sql}) #{model.table_name}")
+
+ read_only(relation)
end
private
@@ -107,5 +109,12 @@ module Gitlab
def groups_table
model.arel_table
end
+
+ def read_only(relation)
+ # relations using a CTE are not safe to use with update_all as it will
+ # throw away the CTE, hence we mark them as read-only.
+ relation.extend(Gitlab::Database::ReadOnlyRelation)
+ relation
+ end
end
end
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index 2577d98df6f..7ce6a61d50c 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -25,7 +25,7 @@ feature 'Group merge requests page' do
end
it 'ignores archived merge request count badges in navbar' do
- expect( page.find('[aria-label="Merge Requests"] span.badge.count').text).to eq("1")
+ expect(first(:link, text: 'Merge Requests').find('.badge').text).to eq("1")
end
it 'ignores archived merge request count badges in state-filters' do
diff --git a/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
new file mode 100644
index 00000000000..5ed4f3ad2bc
--- /dev/null
+++ b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+feature 'Groups > User sees users dropdowns in issuables list' do
+ let(:entity) { create(:group) }
+ let(:user_in_dropdown) { create(:user) }
+ let!(:user_not_in_dropdown) { create(:user) }
+ let!(:project) { create(:project, group: entity) }
+
+ before do
+ entity.add_developer(user_in_dropdown)
+ end
+
+ it_behaves_like 'issuable user dropdown behaviors' do
+ let(:issuable) { create(:issue, project: project) }
+ let(:issuables_path) { issues_group_path(entity) }
+ end
+
+ it_behaves_like 'issuable user dropdown behaviors' do
+ let(:issuable) { create(:merge_request, source_project: project) }
+ let(:issuables_path) { merge_requests_group_path(entity) }
+ end
+end
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index b3c9bca64cc..02415485d19 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -10,6 +10,7 @@ describe('Dropdown User', () => {
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'getGroupId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
dropdownUser = new gl.DropdownUser({
@@ -38,6 +39,7 @@ describe('Dropdown User', () => {
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'getGroupId').and.callFake(() => {});
});
it('should return endpoint', () => {
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index f8b37c0edde..f4b4d7980a4 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -271,12 +271,19 @@ describe('Fly out sidebar navigation', () => {
});
it('sets transform of sub-items', () => {
+ const sidebar = document.createElement('div');
const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ sidebar.style.width = '200px';
+
+ document.body.appendChild(sidebar);
+
+ setSidebar(sidebar);
showSubLevelItems(el);
expect(
subItems.style.transform,
- ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`);
+ ).toBe(`translate3d(200px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`);
});
it('sets is-above when element is above', () => {
diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb
index 08010c2d0e2..8dc83a6db7f 100644
--- a/spec/lib/gitlab/group_hierarchy_spec.rb
+++ b/spec/lib/gitlab/group_hierarchy_spec.rb
@@ -23,6 +23,11 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect(relation).to include(parent, child1, child2)
end
+
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
end
describe '#base_and_descendants' do
@@ -43,6 +48,11 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect(relation).to include(parent, child1, child2)
end
+
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
end
describe '#all_groups' do
@@ -73,5 +83,10 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect(relation).to include(child2)
end
+
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 95da97b7bc5..77f0be6b120 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1439,4 +1439,24 @@ describe Ci::Pipeline, :mailer do
it_behaves_like 'not sending any notification'
end
end
+
+ describe '#latest_builds_with_artifacts' do
+ let!(:pipeline) { create(:ci_pipeline, :success) }
+
+ let!(:build) do
+ create(:ci_build, :success, :artifacts, pipeline: pipeline)
+ end
+
+ it 'returns the latest builds' do
+ expect(pipeline.latest_builds_with_artifacts).to eq([build])
+ end
+
+ it 'memoizes the returned relation' do
+ query_count = ActiveRecord::QueryRecorder
+ .new { 2.times { pipeline.latest_builds_with_artifacts.to_a } }
+ .count
+
+ expect(query_count).to eq(1)
+ end
+ end
end
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 9c99c3e5c08..fadc8bfeb61 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -140,18 +140,6 @@ describe GpgKey do
end
end
- describe 'notification', :mailer do
- let(:user) { create(:user) }
-
- it 'sends a notification' do
- perform_enqueued_jobs do
- create(:gpg_key, user: user)
- end
-
- should_email(user)
- end
- end
-
describe '#revoke' do
it 'invalidates all associated gpg signatures and destroys the key' do
gpg_key = create :gpg_key
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 96baeaff0a4..dbc4aba8547 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -169,16 +169,4 @@ describe Key, :mailer do
expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key)
end
end
-
- describe 'notification' do
- let(:user) { create(:user) }
-
- it 'sends a notification' do
- perform_enqueued_jobs do
- create(:key, user: user)
- end
-
- should_email(user)
- end
- end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 48fc77423ff..78226c6c3fa 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2682,4 +2682,60 @@ describe Project do
end
end
end
+
+ describe '#latest_successful_builds_for' do
+ let(:project) { build(:project) }
+
+ before do
+ allow(project).to receive(:default_branch).and_return('master')
+ end
+
+ context 'without a ref' do
+ it 'returns a pipeline for the default branch' do
+ expect(project)
+ .to receive(:latest_successful_pipeline_for_default_branch)
+
+ project.latest_successful_pipeline_for
+ end
+ end
+
+ context 'with the ref set to the default branch' do
+ it 'returns a pipeline for the default branch' do
+ expect(project)
+ .to receive(:latest_successful_pipeline_for_default_branch)
+
+ project.latest_successful_pipeline_for(project.default_branch)
+ end
+ end
+
+ context 'with a ref that is not the default branch' do
+ it 'returns the latest successful pipeline for the given ref' do
+ expect(project.pipelines).to receive(:latest_successful_for).with('foo')
+
+ project.latest_successful_pipeline_for('foo')
+ end
+ end
+ end
+
+ describe '#latest_successful_pipeline_for_default_branch' do
+ let(:project) { build(:project) }
+
+ before do
+ allow(project).to receive(:default_branch).and_return('master')
+ end
+
+ it 'memoizes and returns the latest successful pipeline for the default branch' do
+ pipeline = double(:pipeline)
+
+ expect(project.pipelines).to receive(:latest_successful_for)
+ .with(project.default_branch)
+ .and_return(pipeline)
+ .once
+
+ 2.times do
+ expect(project.latest_successful_pipeline_for_default_branch)
+ .to eq(pipeline)
+ end
+ end
+ end
end
diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb
new file mode 100644
index 00000000000..7a604c0cadd
--- /dev/null
+++ b/spec/services/deploy_keys/create_service_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe DeployKeys::CreateService do
+ let(:user) { create(:user) }
+ let(:params) { attributes_for(:deploy_key) }
+
+ subject { described_class.new(user, params) }
+
+ it "creates a deploy key" do
+ expect { subject.execute }.to change { DeployKey.where(params.merge(user: user)).count }.by(1)
+ end
+end
diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb
new file mode 100644
index 00000000000..20382a3a618
--- /dev/null
+++ b/spec/services/gpg_keys/create_service_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe GpgKeys::CreateService do
+ let(:user) { create(:user) }
+ let(:params) { attributes_for(:gpg_key) }
+
+ subject { described_class.new(user, params) }
+
+ context 'notification', :mailer do
+ it 'sends a notification' do
+ perform_enqueued_jobs do
+ subject.execute
+ end
+ should_email(user)
+ end
+ end
+
+ it 'creates a gpg key' do
+ expect { subject.execute }.to change { user.gpg_keys.where(params).count }.by(1)
+ end
+end
diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb
new file mode 100644
index 00000000000..bcb436c1e46
--- /dev/null
+++ b/spec/services/keys/create_service_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Keys::CreateService do
+ let(:user) { create(:user) }
+ let(:params) { attributes_for(:key) }
+
+ subject { described_class.new(user, params) }
+
+ context 'notification', :mailer do
+ it 'sends a notification' do
+ perform_enqueued_jobs do
+ subject.execute
+ end
+ should_email(user)
+ end
+ end
+
+ it 'creates a key' do
+ expect { subject.execute }.to change { user.keys.where(params).count }.by(1)
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 3e493148b32..f4b36eb7eeb 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -84,7 +84,6 @@ describe NotificationService, :mailer do
let!(:key) { create(:personal_key, key_options) }
it { expect(notification.new_key(key)).to be_truthy }
- it { should_email(key.user) }
describe 'never emails the ghost user' do
let(:key_options) { { user: User.ghost } }
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 92cc9a37795..c551083ac90 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -57,6 +57,21 @@ describe Projects::UpdateService, '#execute' do
end
end
end
+
+ context 'When project visibility is higher than parent group' do
+ let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+ before do
+ project.update(namespace: group, visibility_level: group.visibility_level)
+ end
+
+ it 'does not update project visibility level' do
+ result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' })
+ expect(project.reload).to be_internal
+ end
+ end
end
describe 'when updating project that has forks' do
@@ -148,7 +163,7 @@ describe Projects::UpdateService, '#execute' do
result = update_project(project, admin, path: 'existing')
expect(result).to include(status: :error)
- expect(result[:message]).to match('Project could not be updated!')
+ expect(result[:message]).to match('There is already a repository with that name on disk')
expect(project).not_to be_valid
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk')
@@ -159,8 +174,10 @@ describe Projects::UpdateService, '#execute' do
it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })
- expect(result).to eq({ status: :error,
- message: 'Project could not be updated!' })
+ expect(result).to eq({
+ status: :error,
+ message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
+ })
end
end
diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
new file mode 100644
index 00000000000..c92c7f603d6
--- /dev/null
+++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
@@ -0,0 +1,21 @@
+shared_examples 'issuable user dropdown behaviors' do
+ include FilteredSearchHelpers
+
+ before do
+ issuable # ensure we have at least one issuable
+ sign_in(user_in_dropdown)
+ end
+
+ %w[author assignee].each do |dropdown|
+ describe "#{dropdown} dropdown", :js do
+ it 'only includes members of the project/group' do
+ visit issuables_path
+
+ filtered_search.set("#{dropdown}:")
+
+ expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).to have_content(user_in_dropdown.name)
+ expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).not_to have_content(user_not_in_dropdown.name)
+ end
+ end
+ end
+end
diff --git a/spec/support/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
index 639b0924197..639b0924197 100644
--- a/spec/support/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb