Commit 9ca7c94f authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge remote-tracking branch 'origin/master' into 18608-lock-issues

parents 1b52d8a4 0a60758b
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-phantomjs-2.1-node-7.1-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-phantomjs-2.1-node-8.x-yarn-1.0-postgresql-9.6"
.default-cache: &default-cache .default-cache: &default-cache
key: "ruby-233-with-yarn" key: "ruby-233-with-yarn"
...@@ -191,6 +191,9 @@ review-docs-deploy: ...@@ -191,6 +191,9 @@ review-docs-deploy:
stage: build stage: build
environment: environment:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$CI_COMMIT_REF_SLUG-built-from-ce-ee.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup on_stop: review-docs-cleanup
script: script:
- gem install gitlab --no-doc - gem install gitlab --no-doc
...@@ -543,7 +546,7 @@ karma: ...@@ -543,7 +546,7 @@ karma:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *pull-cache <<: *pull-cache
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-60.0-node-7.1-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-61.0-node-8.x-yarn-1.0-postgresql-9.6"
stage: test stage: test
variables: variables:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
......
...@@ -286,7 +286,10 @@ might be edited to make them small and simple. ...@@ -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. 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 If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab. discuss whether it is interesting to include this in GitLab.
......
...@@ -357,7 +357,7 @@ GEM ...@@ -357,7 +357,7 @@ GEM
rake rake
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.4.5) grpc (1.6.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
haml (4.0.7) haml (4.0.7)
......
...@@ -7,6 +7,7 @@ class DeleteModal { ...@@ -7,6 +7,7 @@ class DeleteModal {
this.$branchName = $('.js-branch-name', this.$modal); this.$branchName = $('.js-branch-name', this.$modal);
this.$confirmInput = $('.js-delete-branch-input', this.$modal); this.$confirmInput = $('.js-delete-branch-input', this.$modal);
this.$deleteBtn = $('.js-delete-branch', this.$modal); this.$deleteBtn = $('.js-delete-branch', this.$modal);
this.$notMerged = $('.js-not-merged', this.$modal);
this.bindEvents(); this.bindEvents();
} }
...@@ -16,8 +17,10 @@ class DeleteModal { ...@@ -16,8 +17,10 @@ class DeleteModal {
} }
setModalData(e) { setModalData(e) {
this.branchName = e.currentTarget.dataset.branchName || ''; const branchData = e.currentTarget.dataset;
this.deletePath = e.currentTarget.dataset.deletePath || ''; this.branchName = branchData.branchName || '';
this.deletePath = branchData.deletePath || '';
this.isMerged = !!branchData.isMerged;
this.updateModal(); this.updateModal();
} }
...@@ -30,6 +33,7 @@ class DeleteModal { ...@@ -30,6 +33,7 @@ class DeleteModal {
this.$confirmInput.val(''); this.$confirmInput.val('');
this.$deleteBtn.attr('href', this.deletePath); this.$deleteBtn.attr('href', this.deletePath);
this.$deleteBtn.attr('disabled', true); this.$deleteBtn.attr('disabled', true);
this.$notMerged.toggleClass('hidden', this.isMerged);
} }
} }
......
...@@ -15,6 +15,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -15,6 +15,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
params: { params: {
per_page: 20, per_page: 20,
active: true, active: true,
group_id: this.getGroupId(),
project_id: this.getProjectId(), project_id: this.getProjectId(),
current_user: true, current_user: true,
}, },
...@@ -47,6 +48,10 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -47,6 +48,10 @@ class DropdownUser extends gl.FilteredSearchDropdown {
super.renderContent(forceShowList); super.renderContent(forceShowList);
} }
getGroupId() {
return this.input.getAttribute('data-group-id');
}
getProjectId() { getProjectId() {
return this.input.getAttribute('data-project-id'); return this.input.getAttribute('data-project-id');
} }
......
...@@ -77,10 +77,11 @@ export const hideMenu = (el) => { ...@@ -77,10 +77,11 @@ export const hideMenu = (el) => {
export const moveSubItemsToPosition = (el, subItems) => { export const moveSubItemsToPosition = (el, subItems) => {
const boundingRect = el.getBoundingClientRect(); const boundingRect = el.getBoundingClientRect();
const top = calculateTop(boundingRect, subItems.offsetHeight); const top = calculateTop(boundingRect, subItems.offsetHeight);
const left = sidebar ? sidebar.offsetWidth : 50;
const isAbove = top < boundingRect.top; const isAbove = top < boundingRect.top;
subItems.classList.add('fly-out-list'); 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(); const subItemsRect = subItems.getBoundingClientRect();
......
...@@ -127,13 +127,6 @@ import DropdownUtils from './filtered_search/dropdown_utils'; ...@@ -127,13 +127,6 @@ import DropdownUtils from './filtered_search/dropdown_utils';
$('.has-tooltip', $value).tooltip({ $('.has-tooltip', $value).tooltip({
container: 'body' 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({ $dropdown.glDropdown({
......
/* 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);
...@@ -39,7 +39,6 @@ import './commit/file'; ...@@ -39,7 +39,6 @@ import './commit/file';
import './commit/image_file'; import './commit/image_file';
// lib/utils // lib/utils
import './lib/utils/animate';
import './lib/utils/bootstrap_linked_tabs'; import './lib/utils/bootstrap_linked_tabs';
import './lib/utils/common_utils'; import './lib/utils/common_utils';
import './lib/utils/datetime_utility'; import './lib/utils/datetime_utility';
......
...@@ -15,7 +15,6 @@ export default class NewNavSidebar { ...@@ -15,7 +15,6 @@ export default class NewNavSidebar {
this.$openSidebar = $('.toggle-mobile-nav'); this.$openSidebar = $('.toggle-mobile-nav');
this.$closeSidebar = $('.close-nav-button'); this.$closeSidebar = $('.close-nav-button');
this.$sidebarToggle = $('.js-toggle-sidebar'); this.$sidebarToggle = $('.js-toggle-sidebar');
this.$topLevelLinks = $('.sidebar-top-level-items > li > a');
} }
bindEvents() { bindEvents() {
...@@ -56,10 +55,6 @@ export default class NewNavSidebar { ...@@ -56,10 +55,6 @@ export default class NewNavSidebar {
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
} }
NewNavSidebar.setCollapsedCookie(collapsed); NewNavSidebar.setCollapsedCookie(collapsed);
this.$topLevelLinks.attr('title', function updateTopLevelTitle() {
return collapsed ? this.getAttribute('aria-label') : '';
});
} }
render() { render() {
......
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
<div class="note-actions"> <div class="note-actions">
<span <span
v-if="accessLevel" v-if="accessLevel"
class="note-role">{{accessLevel}}</span> class="note-role note-role-access">{{accessLevel}}</span>
<div <div
v-if="canAddAwardEmoji" v-if="canAddAwardEmoji"
class="note-actions-item"> class="note-actions-item">
......
...@@ -328,7 +328,7 @@ ...@@ -328,7 +328,7 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
text-align: center; text-align: center;
margin-top: $header-height; margin-top: $new-navbar-height;
.container-fluid { .container-fluid {
position: relative; position: relative;
......
...@@ -78,16 +78,16 @@ ...@@ -78,16 +78,16 @@
.right-sidebar { .right-sidebar {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
height: calc(100% - #{$header-height}); height: calc(100% - #{$new-navbar-height});
&.affix { &.affix {
position: fixed; position: fixed;
top: $header-height; top: $new-navbar-height;
} }
} }
.with-performance-bar .right-sidebar.affix { .with-performance-bar .right-sidebar.affix {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
} }
@mixin maintain-sidebar-dimensions { @mixin maintain-sidebar-dimensions {
......
...@@ -13,6 +13,7 @@ $sidebar-breakpoint: 1024px; ...@@ -13,6 +13,7 @@ $sidebar-breakpoint: 1024px;
$darken-normal-factor: 7%; $darken-normal-factor: 7%;
$darken-dark-factor: 10%; $darken-dark-factor: 10%;
$darken-border-factor: 5%; $darken-border-factor: 5%;
$darken-border-dashed-factor: 25%;
$white-light: #fff; $white-light: #fff;
$white-normal: #f0f0f0; $white-normal: #f0f0f0;
...@@ -134,6 +135,7 @@ $border-white-normal: darken($white-normal, $darken-border-factor); ...@@ -134,6 +135,7 @@ $border-white-normal: darken($white-normal, $darken-border-factor);
$border-gray-light: darken($gray-light, $darken-border-factor); $border-gray-light: darken($gray-light, $darken-border-factor);
$border-gray-normal: darken($gray-normal, $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); $border-gray-dark: darken($white-normal, $darken-border-factor);
/* /*
......
...@@ -105,7 +105,8 @@ $new-sidebar-collapsed-width: 50px; ...@@ -105,7 +105,8 @@ $new-sidebar-collapsed-width: 50px;
} }
&.sidebar-icons-only { &.sidebar-icons-only {
width: $new-sidebar-collapsed-width; width: auto;
min-width: $new-sidebar-collapsed-width;
.nav-sidebar-inner-scroll { .nav-sidebar-inner-scroll {
overflow-x: hidden; overflow-x: hidden;
...@@ -124,6 +125,10 @@ $new-sidebar-collapsed-width: 50px; ...@@ -124,6 +125,10 @@ $new-sidebar-collapsed-width: 50px;
.fly-out-top-item { .fly-out-top-item {
display: block; display: block;
} }
.avatar-container {
margin-right: 0;
}
} }
&.nav-sidebar-expanded { &.nav-sidebar-expanded {
...@@ -187,7 +192,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -187,7 +192,7 @@ $new-sidebar-collapsed-width: 50px;
.nav-sidebar-inner-scroll { .nav-sidebar-inner-scroll {
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: auto; overflow: scroll;
} }
.with-performance-bar .nav-sidebar { .with-performance-bar .nav-sidebar {
...@@ -248,7 +253,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -248,7 +253,7 @@ $new-sidebar-collapsed-width: 50px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
position: fixed; position: fixed;
top: 0; top: 0;
left: $new-sidebar-width; left: 0;
min-width: 150px; min-width: 150px;
margin-top: -1px; margin-top: -1px;
padding: 4px 1px; padding: 4px 1px;
...@@ -386,10 +391,6 @@ $new-sidebar-collapsed-width: 50px; ...@@ -386,10 +391,6 @@ $new-sidebar-collapsed-width: 50px;
} }
.sidebar-sub-level-items { .sidebar-sub-level-items {
@media (min-width: $screen-sm-min) {
left: $new-sidebar-collapsed-width;
}
&:not(.flyout-list) { &:not(.flyout-list) {
display: none; display: none;
} }
......
...@@ -64,10 +64,10 @@ ...@@ -64,10 +64,10 @@
color: $gl-text-color; color: $gl-text-color;
position: sticky; position: sticky;
position: -webkit-sticky; position: -webkit-sticky;
top: $header-height; top: $new-navbar-height;
&.affix { &.affix {
top: $header-height; top: $new-navbar-height;
} }
// with sidebar // with sidebar
...@@ -174,10 +174,10 @@ ...@@ -174,10 +174,10 @@
.with-performance-bar .build-page { .with-performance-bar .build-page {
.top-bar { .top-bar {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
&.affix { &.affix {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
} }
} }
} }
......
...@@ -634,8 +634,16 @@ ...@@ -634,8 +634,16 @@
padding-top: 8px; padding-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
} }
.diff-changed-file {
display: flex;
align-items: center;
}
} }
.diff-file-changes-path { .diff-file-changes-path {
@include str-truncated(78%); flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
...@@ -95,6 +95,8 @@ ...@@ -95,6 +95,8 @@
} }
.omniauth-container { .omniauth-container {
font-size: 13px;
p { p {
margin: 0; margin: 0;
} }
......
...@@ -752,7 +752,7 @@ a.deploy-project-label { ...@@ -752,7 +752,7 @@ a.deploy-project-label {
} }
li.missing { li.missing {
border: 1px dashed $border-gray-normal; border: 1px dashed $border-gray-normal-dashed;
border-radius: $border-radius-default; border-radius: $border-radius-default;
a { a {
......
...@@ -10,9 +10,8 @@ class Admin::DeployKeysController < Admin::ApplicationController ...@@ -10,9 +10,8 @@ class Admin::DeployKeysController < Admin::ApplicationController
end end
def create def create
@deploy_key = deploy_keys.new(create_params.merge(user: current_user)) @deploy_key = DeployKeys::CreateService.new(current_user, create_params.merge(public: true)).execute
if @deploy_key.persisted?
if @deploy_key.save
redirect_to admin_deploy_keys_path redirect_to admin_deploy_keys_path
else else
render 'new' render 'new'
......
...@@ -3,31 +3,10 @@ class AutocompleteController < ApplicationController ...@@ -3,31 +3,10 @@ class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users, :award_emojis] skip_before_action :authenticate_user!, only: [:users, :award_emojis]
before_action :load_project, only: [:users] before_action :load_project, only: [:users]
before_action :find_users, only: [:users] before_action :load_group, only: [:users]
def users def users
@users ||= User.none @users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute
@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
render json: @users, only: [:name, :username, :id], methods: [:avatar_url] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
end end
...@@ -60,25 +39,13 @@ class AutocompleteController < ApplicationController ...@@ -60,25 +39,13 @@ class AutocompleteController < ApplicationController
private private
def find_users def load_group
@users = @group ||= begin
if @project if @project.blank? && params[:group_id].present?
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?
group = Group.find(params[:group_id]) group = Group.find(params[:group_id])
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
group
group.users end
elsif current_user
User.all
else
User.none
end end
end end
......
...@@ -7,9 +7,9 @@ class Profiles::GpgKeysController < Profiles::ApplicationController ...@@ -7,9 +7,9 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
end end
def create 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 redirect_to profile_gpg_keys_path
else else
@gpg_keys = current_user.gpg_keys.select(&:persisted?) @gpg_keys = current_user.gpg_keys.select(&:persisted?)
......
...@@ -11,9 +11,9 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -11,9 +11,9 @@ class Profiles::KeysController < Profiles::ApplicationController
end end
def create 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) redirect_to profile_key_path(@key)
else else
@keys = current_user.keys.select(&:persisted?) @keys = current_user.keys.select(&:persisted?)
......
...@@ -27,7 +27,7 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -27,7 +27,7 @@ class Projects::CompareController < Projects::ApplicationController
def create def create
if params[:from].blank? || params[:to].blank? 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_to_vars = {
from: params[:from].presence, from: params[:from].presence,
to: params[:to].presence to: params[:to].presence
......
...@@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end end
def create 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 unless @key.valid? && @project.deploy_keys << @key
flash[:alert] = @key.errors.full_messages.join(', ').html_safe flash[:alert] = @key.errors.full_messages.join(', ').html_safe
......
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
...@@ -119,8 +119,4 @@ module TabHelper ...@@ -119,8 +119,4 @@ module TabHelper
'active' if current_controller?('oauth/applications') 'active' if current_controller?('oauth/applications')
end 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 end
...@@ -453,6 +453,10 @@ module Ci ...@@ -453,6 +453,10 @@ module Ci
.fabricate! .fabricate!
end end
def latest_builds_with_artifacts
@latest_builds_with_artifacts ||= builds.latest.with_artifacts
end
private private
def ci_yaml_from_repo def ci_yaml_from_repo
......
...@@ -28,10 +28,4 @@ class DeployKey < Key ...@@ -28,10 +28,4 @@ class DeployKey < Key
def can_push_to?(project) def can_push_to?(project)
can_push? && has_access_to?(project) can_push? && has_access_to?(project)
end end
private
# we don't want to notify the user for deploy keys
def notify_user
end
end end
...@@ -6,7 +6,10 @@ class Environment < ActiveRecord::Base ...@@ -6,7 +6,10 @@ class Environment < ActiveRecord::Base
belongs_to :project, required: true, validate: true 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' has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment'
before_validation :nullify_external_url before_validation :nullify_external_url
......
...@@ -36,7 +36,6 @@ class GpgKey < ActiveRecord::Base ...@@ -36,7 +36,6 @@ class GpgKey < ActiveRecord::Base
before_validation :extract_fingerprint, :extract_primary_keyid before_validation :extract_fingerprint, :extract_primary_keyid
after_commit :update_invalid_gpg_signatures, on: :create after_commit :update_invalid_gpg_signatures, on: :create
after_commit :notify_user, on: :create
def primary_keyid def primary_keyid
super&.upcase super&.upcase
...@@ -107,8 +106,4 @@ class GpgKey < ActiveRecord::Base ...@@ -107,8 +106,4 @@ class GpgKey < ActiveRecord::Base
# only allows one key # only allows one key
self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
end end
def notify_user
NotificationService.new.new_gpg_key(self)
end
end end
...@@ -28,7 +28,6 @@ class Key < ActiveRecord::Base ...@@ -28,7 +28,6 @@ class Key < ActiveRecord::Base
delegate :name, :email, to: :user, prefix: true delegate :name, :email, to: :user, prefix: true
after_commit :add_to_shell, on: :create after_commit :add_to_shell, on: :create
after_commit :notify_user, on: :create
after_create :post_create_hook after_create :post_create_hook
after_commit :remove_from_shell, on: :destroy after_commit :remove_from_shell, on: :destroy
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
...@@ -118,8 +117,4 @@ class Key < ActiveRecord::Base ...@@ -118,8 +117,4 @@ class Key < ActiveRecord::Base
"type is forbidden. Must be #{allowed_types}" "type is forbidden. Must be #{allowed_types}"
end end
def notify_user
NotificationService.new.new_key(self)
end
end end
...@@ -231,6 +231,13 @@ class Namespace < ActiveRecord::Base ...@@ -231,6 +231,13 @@ class Namespace < ActiveRecord::Base
end end
def force_share_with_group_lock_on_descendants 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
end end
...@@ -161,7 +161,7 @@ class Project < ActiveRecord::Base ...@@ -161,7 +161,7 @@ class Project < ActiveRecord::Base
has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent 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 :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' has_one :statistics, class_name: 'ProjectStatistics'
# Container repositories need to remove data from the container registry, # Container repositories need to remove data from the container registry,
...@@ -190,7 +190,7 @@ class Project < ActiveRecord::Base ...@@ -190,7 +190,7 @@ class Project < ActiveRecord::Base
has_one :auto_devops, class_name: 'ProjectAutoDevops' has_one :auto_devops, class_name: 'ProjectAutoDevops'
accepts_nested_attributes_for :variables, allow_destroy: true 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 :import_data
accepts_nested_attributes_for :auto_devops accepts_nested_attributes_for :auto_devops
...@@ -1163,6 +1163,23 @@ class Project < ActiveRecord::Base ...@@ -1163,6 +1163,23 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref) pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end 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 def enable_ci
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end end
......
...@@ -41,6 +41,8 @@ class ProjectFeature < ActiveRecord::Base ...@@ -41,6 +41,8 @@ class ProjectFeature < ActiveRecord::Base
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to # http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
belongs_to :project, -> { unscope(where: :pending_delete) } belongs_to :project, -> { unscope(where: :pending_delete) }
validates :project, presence: true
validate :repository_children_level validate :repository_children_level
default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :builds_access_level, value: ENABLED, allows_nil: false
......
...@@ -80,6 +80,6 @@ class PipelinesEmailService < Service ...@@ -80,6 +80,6 @@ class PipelinesEmailService < Service
end end
def retrieve_recipients(data) def retrieve_recipients(data)
recipients.to_s.split(',').reject(&:blank?) recipients.to_s.split(/[,(?:\r?\n) ]+/).reject(&:empty?)
end end
end end
...@@ -90,6 +90,12 @@ class Repository ...@@ -90,6 +90,12 @@ class Repository
) )
end 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 def inspect
"#<#{self.class.name}:#{@disk_path}>" "#<#{self.class.name}:#{@disk_path}>"
end end
...@@ -166,7 +172,7 @@ class Repository ...@@ -166,7 +172,7 @@ class Repository
end end
def add_branch(user, branch_name, ref) 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 after_create_branch
...@@ -176,7 +182,7 @@ class Repository ...@@ -176,7 +182,7 @@ class Repository
end end
def add_tag(user, tag_name, target, message = nil) 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 rescue Gitlab::Git::Repository::InvalidRef
false false
end end
...@@ -184,7 +190,7 @@ class Repository ...@@ -184,7 +190,7 @@ class Repository
def rm_branch(user, branch_name) def rm_branch(user, branch_name)
before_remove_branch before_remove_branch
raw_repository.rm_branch(branch_name, committer: user) raw_repository.rm_branch(branch_name, user: user)
after_remove_branch after_remove_branch
true true
...@@ -193,7 +199,7 @@ class Repository ...@@ -193,7 +199,7 @@ class Repository
def rm_tag(user, tag_name) def rm_tag(user, tag_name)
before_remove_tag before_remove_tag
raw_repository.rm_tag(tag_name, committer: user) raw_repository.rm_tag(tag_name, user: user)
after_remove_tag after_remove_tag
true true
...@@ -762,17 +768,23 @@ class Repository ...@@ -762,17 +768,23 @@ class Repository
multi_action(**options) multi_action(**options)
end end
def with_branch(user, *args) def with_cache_hooks
result = Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit| result = yield
yield start_commit
end 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 result.newrev
after_create_branch if should_run_after_create_branch 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 end
# rubocop:disable Metrics/ParameterLists # rubocop:disable Metrics/ParameterLists
...@@ -837,30 +849,13 @@ class Repository ...@@ -837,30 +849,13 @@ class Repository
end end
end end
def merge(user, source, merge_request, options = {}) def merge(user, source_sha, merge_request, message)
with_branch( with_cache_hooks do
user, raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
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) merge_request.update(in_progress_merge_commit_sha: commit_id)
commit_id nil # Return value does not matter.
end
end end
rescue Gitlab::Git::CommitError # when merge_index.conflicts?
false
end end
def revert( def revert(
...@@ -1151,12 +1146,6 @@ class Repository ...@@ -1151,12 +1146,6 @@ class Repository
Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags)) Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags))
end end
def create_commit(params = {})
params[:message].delete!("\r")
Rugged::Commit.create(rugged, params)
end
def last_commit_for_path_by_gitaly(sha, path) def last_commit_for_path_by_gitaly(sha, path)
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path) c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
commit(c) commit(c)
......
module DeployKeys
class CreateService < Keys::BaseService
def execute
DeployKey.create(params.merge(user: user))
end
end
end
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
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
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
...@@ -38,15 +38,9 @@ module MergeRequests ...@@ -38,15 +38,9 @@ module MergeRequests
private private
def commit def commit
committer = repository.user_to_committer(current_user) message = params[:commit_message] || merge_request.merge_commit_message
options = { commit_id = repository.merge(current_user, source, merge_request, message)
message: params[:commit_message] || merge_request.merge_commit_message,
author: committer,
committer: committer
}
commit_id = repository.merge(current_user, source, merge_request, options)
raise MergeError, 'Conflicts detected during merge' unless commit_id raise MergeError, 'Conflicts detected during merge' unless commit_id
......
...@@ -24,7 +24,10 @@ module Projects ...@@ -24,7 +24,10 @@ module Projects
success success
else 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
end end
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
%span.light %span.light
- has_icon = provider_has_icon?(provider) - 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}" = 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 %fieldset.prepend-top-10.checkbox.remember-me
= check_box_tag :remember_me %label
= label_tag :remember_me, 'Remember me' = check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox'
%span
Remember me
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.sidebar-context-title Admin Area .sidebar-context-title Admin Area
%ul.sidebar-top-level-items %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 = 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 .nav-icon-container
= custom_icon('overview') = custom_icon('overview')
%span.nav-item-name %span.nav-item-name
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
ConvDev Index ConvDev Index
= nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do = 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 .nav-icon-container
= custom_icon('monitoring') = custom_icon('monitoring')
%span.nav-item-name %span.nav-item-name
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
Requests Profiles Requests Profiles
= nav_link(controller: :broadcast_messages) do = 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 .nav-icon-container
= custom_icon('messages') = custom_icon('messages')
%span.nav-item-name %span.nav-item-name
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
#{ _('Messages') } #{ _('Messages') }
= nav_link(controller: [:hooks, :hook_logs]) do = nav_link(controller: [:hooks, :hook_logs]) do
= sidebar_link admin_hooks_path, title: _('Hooks') do = link_to admin_hooks_path do
.nav-icon-container .nav-icon-container
= custom_icon('system_hooks') = custom_icon('system_hooks')
%span.nav-item-name %span.nav-item-name
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
#{ _('System Hooks') } #{ _('System Hooks') }
= nav_link(controller: :applications) do = nav_link(controller: :applications) do
= sidebar_link admin_applications_path, title: _('Applications') do = link_to admin_applications_path do
.nav-icon-container .nav-icon-container
= custom_icon('applications') = custom_icon('applications')
%span.nav-item-name %span.nav-item-name
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
#{ _('Applications') } #{ _('Applications') }
= nav_link(controller: :abuse_reports) do = 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 .nav-icon-container
= custom_icon('abuse_reports') = custom_icon('abuse_reports')
%span.nav-item-name %span.nav-item-name
...@@ -138,7 +138,7 @@ ...@@ -138,7 +138,7 @@
- if akismet_enabled? - if akismet_enabled?
= nav_link(controller: :spam_logs) do = 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 .nav-icon-container
= custom_icon('spam_logs') = custom_icon('spam_logs')
%span.nav-item-name %span.nav-item-name
...@@ -150,7 +150,7 @@ ...@@ -150,7 +150,7 @@
#{ _('Spam Logs') } #{ _('Spam Logs') }
= nav_link(controller: :deploy_keys) do = 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 .nav-icon-container
= custom_icon('key') = custom_icon('key')
%span.nav-item-name %span.nav-item-name
...@@ -162,7 +162,7 @@ ...@@ -162,7 +162,7 @@
#{ _('Deploy Keys') } #{ _('Deploy Keys') }
= nav_link(controller: :services) do = 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 .nav-icon-container
= custom_icon('service_templates') = custom_icon('service_templates')
%span.nav-item-name %span.nav-item-name
...@@ -174,7 +174,7 @@ ...@@ -174,7 +174,7 @@
#{ _('Service Templates') } #{ _('Service Templates') }
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= sidebar_link admin_labels_path, title: _('Labels') do = link_to admin_labels_path do
.nav-icon-container .nav-icon-container
= custom_icon('labels') = custom_icon('labels')
%span.nav-item-name %span.nav-item-name
...@@ -186,7 +186,7 @@ ...@@ -186,7 +186,7 @@
#{ _('Labels') } #{ _('Labels') }
= nav_link(controller: :appearances) do = nav_link(controller: :appearances) do
= sidebar_link admin_appearances_path, title: _('Appearances') do = link_to admin_appearances_path do
.nav-icon-container .nav-icon-container
= custom_icon('appearance') = custom_icon('appearance')
%span.nav-item-name %span.nav-item-name
...@@ -198,7 +198,7 @@ ...@@ -198,7 +198,7 @@
#{ _('Appearance') } #{ _('Appearance') }
= nav_link(controller: :application_settings) do = 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 .nav-icon-container
= custom_icon('settings') = custom_icon('settings')
%span.nav-item-name %span.nav-item-name
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= @group.name = @group.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do = 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 .nav-icon-container
= custom_icon('project') = custom_icon('project')
%span.nav-item-name %span.nav-item-name
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
Activity Activity
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do = 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 .nav-icon-container
= custom_icon('issues') = custom_icon('issues')
%span.nav-item-name %span.nav-item-name
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
Milestones Milestones
= nav_link(path: 'groups#merge_requests') do = 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 .nav-icon-container
= custom_icon('mr_bold') = custom_icon('mr_bold')
%span.nav-item-name %span.nav-item-name
...@@ -77,19 +77,19 @@ ...@@ -77,19 +77,19 @@
#{ _('Merge Requests') } #{ _('Merge Requests') }
%span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count) %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count)
= nav_link(path: 'group_members#index') do = 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 .nav-icon-container
= custom_icon('members') = custom_icon('members')
%span.nav-item-name %span.nav-item-name
Members Members
%ul.sidebar-sub-level-items.is-fly-out-only %ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do = 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 %strong.fly-out-top-item-name
#{ _('Members') } #{ _('Members') }
- if current_user && can?(current_user, :admin_group, @group) - if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do = 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 .nav-icon-container
= custom_icon('settings') = custom_icon('settings')
%span.nav-item-name %span.nav-item-name
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.sidebar-context-title User Settings .sidebar-context-title User Settings
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = 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 .nav-icon-container
= custom_icon('profile') = custom_icon('profile')
%span.nav-item-name %span.nav-item-name
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Profile') } #{ _('Profile') }
= nav_link(controller: [:accounts, :two_factor_auths]) do = 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 .nav-icon-container
= custom_icon('account') = custom_icon('account')
%span.nav-item-name %span.nav-item-name
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
#{ _('Account') } #{ _('Account') }
- if current_application_settings.user_oauth_applications? - if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do = nav_link(controller: 'oauth/applications') do
= sidebar_link applications_profile_path, title: _('Applications') do = link_to applications_profile_path do
.nav-icon-container .nav-icon-container
= custom_icon('applications') = custom_icon('applications')
%span.nav-item-name %span.nav-item-name
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Applications') } #{ _('Applications') }
= nav_link(controller: :chat_names) do = 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 .nav-icon-container
= custom_icon('chat') = custom_icon('chat')
%span.nav-item-name %span.nav-item-name
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Chat') } #{ _('Chat') }
= nav_link(controller: :personal_access_tokens) do = 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 .nav-icon-container
= custom_icon('access_tokens') = custom_icon('access_tokens')
%span.nav-item-name %span.nav-item-name
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Access Tokens') } #{ _('Access Tokens') }
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
= sidebar_link profile_emails_path, title: _('Emails') do = link_to profile_emails_path do
.nav-icon-container .nav-icon-container
= custom_icon('emails') = custom_icon('emails')
%span.nav-item-name %span.nav-item-name
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
#{ _('Emails') } #{ _('Emails') }
- unless current_user.ldap_user? - unless current_user.ldap_user?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= sidebar_link edit_profile_password_path, title: _('Password') do = link_to edit_profile_password_path do
.nav-icon-container .nav-icon-container
= custom_icon('lock') = custom_icon('lock')
%span.nav-item-name %span.nav-item-name
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Password') } #{ _('Password') }
= nav_link(controller: :notifications) do = nav_link(controller: :notifications) do
= sidebar_link profile_notifications_path, title: _('Notifications') do = link_to profile_notifications_path do
.nav-icon-container .nav-icon-container
= custom_icon('notifications') = custom_icon('notifications')
%span.nav-item-name %span.nav-item-name
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Notifications') } #{ _('Notifications') }
= nav_link(controller: :keys) do = nav_link(controller: :keys) do
= sidebar_link profile_keys_path, title: _('SSH Keys') do = link_to profile_keys_path do
.nav-icon-container .nav-icon-container
= custom_icon('key') = custom_icon('key')
%span.nav-item-name %span.nav-item-name
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('SSH Keys') } #{ _('SSH Keys') }
= nav_link(controller: :gpg_keys) do = 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 .nav-icon-container
= custom_icon('key_2') = custom_icon('key_2')
%span.nav-item-name %span.nav-item-name
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('GPG Keys') } #{ _('GPG Keys') }
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= sidebar_link profile_preferences_path, title: _('Preferences') do = link_to profile_preferences_path do
.nav-icon-container .nav-icon-container
= custom_icon('preferences') = custom_icon('preferences')
%span.nav-item-name %span.nav-item-name
...@@ -130,7 +130,7 @@ ...@@ -130,7 +130,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Preferences') } #{ _('Preferences') }
= nav_link(path: 'profiles#audit_log') do = 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 .nav-icon-container
= custom_icon('authentication_log') = custom_icon('authentication_log')
%span.nav-item-name %span.nav-item-name
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= @project.name = @project.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = 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 .nav-icon-container
= custom_icon('project') = custom_icon('project')
%span.nav-item-name %span.nav-item-name
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
- if project_nav_tab? :files - 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 = 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 .nav-icon-container
= custom_icon('doc_text') = custom_icon('doc_text')
%span.nav-item-name %span.nav-item-name
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
- if project_nav_tab? :container_registry - if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do = 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 .nav-icon-container
= custom_icon('container_registry') = custom_icon('container_registry')
%span.nav-item-name %span.nav-item-name
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
- if project_nav_tab? :issues - if project_nav_tab? :issues
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do = 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 .nav-icon-container
= custom_icon('issues') = custom_icon('issues')
%span.nav-item-name %span.nav-item-name
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do = 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 .nav-icon-container
= custom_icon('mr_bold') = custom_icon('mr_bold')
%span.nav-item-name %span.nav-item-name
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do = 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 .nav-icon-container
= custom_icon('pipeline') = custom_icon('pipeline')
%span.nav-item-name %span.nav-item-name
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do = 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 .nav-icon-container
= custom_icon('wiki') = custom_icon('wiki')
%span.nav-item-name %span.nav-item-name
...@@ -218,7 +218,7 @@ ...@@ -218,7 +218,7 @@
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = 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 .nav-icon-container
= custom_icon('snippets') = custom_icon('snippets')
%span.nav-item-name %span.nav-item-name
...@@ -231,7 +231,7 @@ ...@@ -231,7 +231,7 @@
- if project_nav_tab? :settings - 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 = 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 .nav-icon-container
= custom_icon('settings') = custom_icon('settings')
%span.nav-item-name %span.nav-item-name
......
...@@ -43,7 +43,8 @@ ...@@ -43,7 +43,8 @@
data: { toggle: "modal", data: { toggle: "modal",
target: "#modal-delete-branch", target: "#modal-delete-branch",
delete_path: project_branch_path(@project, branch.name), 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") = icon("trash-o")
- else - else
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled", %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
......
...@@ -6,13 +6,18 @@ ...@@ -6,13 +6,18 @@
%h3.page-title %h3.page-title
Delete protected branch Delete protected branch
= surround "'", "'?" do = surround "'", "'?" do
%span.js-branch-name>[branch name] %span.js-branch-name.ref-name>[branch name]
.modal-body .modal-body
%p %p
You’re about to permanently delete the protected branch You’re about to permanently delete the protected branch
= succeed '.' do = 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 %p
Once you confirm and press Once you confirm and press
= succeed ',' do = succeed ',' do
......
- 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) - if !project.empty_repo? && can?(current_user, :download_code, project)
.project-action-button.dropdown.inline> .project-action-button.dropdown.inline>
...@@ -26,18 +26,16 @@ ...@@ -26,18 +26,16 @@
%i.fa.fa-download %i.fa.fa-download
%span= _('Download tar') %span= _('Download tar')
- if pipeline - if pipeline && pipeline.latest_builds_with_artifacts.any?
- artifacts = pipeline.builds.latest.with_artifacts
- if artifacts.any?
%li.dropdown-header Artifacts %li.dropdown-header Artifacts
- unless pipeline.latest? - unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref) - latest_pipeline = project.pipeline_for(ref)
%li %li
.unclickable= ci_status_for_statuseable(latest_pipeline) .unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts %li.dropdown-header Previous Artifacts
- artifacts.each do |job| - pipeline.latest_builds_with_artifacts.each do |job|
%li %li
= link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download %i.fa.fa-download
%span %span
#{ s_('DownloadArtifacts|Download') } '#{job.name}' #{s_('DownloadArtifacts|Download')} '#{job.name}'
...@@ -2,22 +2,22 @@ ...@@ -2,22 +2,22 @@
.clearfix .clearfix
- if params[:to] && params[:from] - if params[:to] && params[:from]
.compare-switch-container .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'} = 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.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 ...
.form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon to %span.input-group-addon Source
= hidden_field_tag :to, params[:to] = 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 = 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' .dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag'
= render 'shared/ref_dropdown' = 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; &nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present? - if @merge_request.present?
......
...@@ -7,13 +7,19 @@ ...@@ -7,13 +7,19 @@
.sub-header-block .sub-header-block
Compare Git revisions. Compare Git revisions.
%br %br
Fill input field with commit SHA like Choose a branch/tag (e.g.
%code.ref-name 4eedf23 = succeed ')' do
or branch/tag name like
%code.ref-name master %code.ref-name master
and press compare button for the commits list and a code diff. 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 %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 .prepend-top-20
= render "form" = render "form"
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
%ul %ul
- diff_files.each do |diff_file| - diff_files.each do |diff_file|
%li %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") = 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 .pull-right
%span.cgreen< %span.cgreen<
+#{diff_file.added_lines} +#{diff_file.added_lines}
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.col-sm-10 .col-sm-10
.checkbox .checkbox
= f.check_box :access_level, {}, 'ref_protected', 'not_protected' = 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 .form-group
= label :run_untagged, 'Run untagged jobs', class: 'control-label' = label :run_untagged, 'Run untagged jobs', class: 'control-label'
.col-sm-10 .col-sm-10
......
...@@ -11,13 +11,13 @@ ...@@ -11,13 +11,13 @@
- if params[:author_id].present? - if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id]) = 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", = 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 .filter-item.inline
- if params[:assignee_id].present? - if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id]) = 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", = 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 .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 = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true
......
---
title: Return only group's members in user dropdowns on issuables list pages
merge_request: 14249
author:
type: changed
---
title: Make the labels in the Compare form less confusing
merge_request: 14225
author:
type: changed
---
title: Extract AutocompleteController#users into finder
merge_request: 13778
author: Maxim Rydkin, Mayra Cabrera
type: other
---
title: creation of keys moved to services
merge_request: 13331
author: haseebeqx
---
title: Allow using newlines in pipeline email service recipients
merge_request: 14250
author:
type: fixed
---
title: changed dashed border button color to be darker
merge_request: !14041
author:
type: other
---
title: Constrain environment deployments to project IDs
merge_request:
author:
type: other
---
title: "Disallow NULL values for environments.project_id"
merge_request:
author:
type: other
---
title: Fixed the sidebar scrollbar overlapping links
merge_request:
author:
type: fixed
---
title: Fix project feature being deleted when updating project with invalid visibility
level
merge_request:
author:
type: fixed
---
title: "Memoize the latest builds of a pipeline on a project's homepage"
merge_request:
author:
type: other
---
title: Memoize pipelines for project download buttons
merge_request:
author:
type: other
---
title: Remove animate.js and label animation.
merge_request:
author:
type: removed
---
title: Replace the 'project/archived.feature' spinach test with an rspec analog
merge_request: 14322
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace the 'project/commits/revert.feature' spinach test with an rspec analog
merge_request: 14325
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace the 'search.feature' spinach test with an rspec analog
merge_request: 14248
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Made the "remember me" check boxes have consistent styles and alignment
merge_request:
author: Jedidiah Broadbent
type: fixed
---
title: Display whether branch has been merged when deleting protected branch
merge_request: 14220
author:
type: changed
...@@ -416,3 +416,39 @@ ...@@ -416,3 +416,39 @@
:why: https://gitlab.com/gitlab-com/organization/issues/117 :why: https://gitlab.com/gitlab-com/organization/issues/117
:versions: [] :versions: []
:when: 2017-09-04 12:59:51.150798717 Z :when: 2017-09-04 12:59:51.150798717 Z
- - :approve
- console-browserify
- :who: Mike Greiling
:why: https://github.com/Raynos/console-browserify/blob/f0a8898487e2a47b8a5dc8734b91059fa2825506/LICENCE
:versions: []
:when: 2017-09-16 05:13:07.073651000 Z
- - :approve
- duplexer
- :who: Mike Greiling
:why: https://github.com/Raynos/duplexer/blob/master/LICENCE
:versions: []
:when: 2017-09-16 05:14:15.774643000 Z
- - :approve
- json3
- :who: Mike Greiling
:why: https://github.com/bestiejs/json3/blob/v3.3.2/LICENSE
:versions: []
:when: 2017-09-16 05:15:16.273892000 Z
- - :approve
- mime
- :who: Mike Greiling
:why: https://github.com/broofa/node-mime/blob/v1.3.4/LICENSE
:versions: []
:when: 2017-09-16 05:16:21.135542000 Z
- - :approve
- querystring-es3
- :who: Mike Greiling
:why: https://github.com/mike-spainhower/querystring/blob/v0.2.0/License.md
:versions: []
:when: 2017-09-16 05:17:20.372089000 Z
- - :approve
- utils-merge
- :who: Mike Greiling
:why: https://github.com/jaredhanson/utils-merge/blob/v1.0.0/LICENSE
:versions: []
:when: 2017-09-16 05:18:26.193764000 Z
...@@ -30,7 +30,7 @@ if app.config.serve_static_files ...@@ -30,7 +30,7 @@ if app.config.serve_static_files
settings.merge!( settings.merge!(
host: Gitlab.config.gitlab.host, host: Gitlab.config.gitlab.host,
port: Gitlab.config.gitlab.port, port: Gitlab.config.gitlab.port,
https: Gitlab.config.gitlab.https https: false
) )
app.config.middleware.insert_before( app.config.middleware.insert_before(
Gitlab::Middleware::Static, Gitlab::Middleware::Static,
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class EnvironmentsProjectIdNotNull < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
change_column_null :environments, :project_id, false
end
def down
change_column_null :environments, :project_id, true
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddIndexForRecentPushEvents < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index_if_not_present(
:merge_requests,
[:source_project_id, :source_branch]
)
remove_concurrent_index_if_present(:merge_requests, :source_project_id)
end
def down
add_concurrent_index_if_not_present(:merge_requests, :source_project_id)
remove_concurrent_index_if_present(
:merge_requests,
[:source_project_id, :source_branch]
)
end
def add_concurrent_index_if_not_present(table, columns)
return if index_exists?(table, columns)
add_concurrent_index(table, columns)
end
def remove_concurrent_index_if_present(table, columns)
return unless index_exists?(table, columns)
remove_concurrent_index(table, columns)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DeleteConflictingRedirectRoutes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'DeleteConflictingRedirectRoutesRange'.freeze
BATCH_SIZE = 200 # At 200, I expect under 20s per batch, which is under our query timeout of 60s.
DELAY_INTERVAL = 12.seconds
disable_ddl_transaction!
class Route < ActiveRecord::Base
include EachBatch
self.table_name = 'routes'
end
def up
say opening_message
queue_background_migration_jobs_by_range_at_intervals(Route, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
# nothing
end
def opening_message
<<~MSG
Clean up redirect routes that conflict with regular routes.
See initial bug fix:
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13357
MSG
end
end
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
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170905112933) do ActiveRecord::Schema.define(version: 20170914135630) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -520,7 +520,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do ...@@ -520,7 +520,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do
add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
create_table "environments", force: :cascade do |t| create_table "environments", force: :cascade do |t|
t.integer "project_id" t.integer "project_id", null: false
t.string "name", null: false t.string "name", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
...@@ -894,7 +894,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do ...@@ -894,7 +894,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do
add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_source_project_id", using: :btree add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
......
...@@ -77,6 +77,33 @@ and use [an application password](https://support.google.com/mail/answer/185833) ...@@ -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 To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the
[Postfix setup documentation](reply_by_email_postfix_setup.md). [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 ### Omnibus package installations
1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the 1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the
......
...@@ -155,3 +155,5 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gi ...@@ -155,3 +155,5 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gi
``` ```
On success the HTTP status code is `204` and no JSON response is expected. On success the HTTP status code is `204` and no JSON response is expected.
[ce-13372]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13372
...@@ -240,55 +240,18 @@ Remember that if your environment's name is `production` (all lowercase), then ...@@ -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). it will get recorded in [Cycle Analytics](../user/project/cycle_analytics.md).
Double the benefit! 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 ## Dynamic environments
As the name suggests, it is possible to create environments on the fly by just 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 declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is
the basis of [Review apps](review_apps/index.md). 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, 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 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`: 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. ...@@ -434,7 +397,8 @@ Let's briefly see where URL that's defined in the environments is exposed.
## Making use of the environment URL ## 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 | | 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: ...@@ -598,7 +562,7 @@ exist, you should see something like:
>**Notes:** >**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] - Have enabled the [Prometheus integration][prom]
- Configured Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/metrics.md) - 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 - 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 ...@@ -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 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 environment which has had a successful deployment. If monitoring data was
successfully retrieved, a Monitoring button will appear on the environment's successfully retrieved, a Monitoring button will appear for each environment.
detail page.
![Environment Detail with Metrics](img/prometheus_environment_detail_with_metrics.png) ![Environment Detail with Metrics](img/prometheus_environment_detail_with_metrics.png)
...@@ -623,6 +586,49 @@ version of the app, all without leaving GitLab. ...@@ -623,6 +586,49 @@ version of the app, all without leaving GitLab.
![Monitoring dashboard](img/environments_monitoring.png) ![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 ## Checkout deployments locally
Since 8.13, a reference in the git repository is saved for each deployment, so Since 8.13, a reference in the git repository is saved for each deployment, so
......
...@@ -106,7 +106,7 @@ What is important is that each job is run independently from each other. ...@@ -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 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 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. **Pipelines ➔ Jobs** in your project.
For more information and a complete `.gitlab-ci.yml` syntax, please read For more information and a complete `.gitlab-ci.yml` syntax, please read
...@@ -155,7 +155,7 @@ Find more information about different Runners in the ...@@ -155,7 +155,7 @@ Find more information about different Runners in the
[Runners](../runners/README.md) documentation. [Runners](../runners/README.md) documentation.
You can find whether any Runners are assigned to your project by going to 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 official Runner supported by GitLab is written in Go and its documentation
can be found at <https://docs.gitlab.com/runner/>. 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 ...@@ -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. described in the next section.
Once the Runner has been set up, you should see it on the Runners page of your 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) ![Activated runners](img/runners_activated.png)
...@@ -181,7 +181,7 @@ These are special virtual machines that run on GitLab's infrastructure and can ...@@ -181,7 +181,7 @@ These are special virtual machines that run on GitLab's infrastructure and can
build any project. build any project.
To enable the **Shared Runners** you have to go to your project's 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). [Read more on Shared Runners](../runners/README.md).
......
...@@ -35,7 +35,7 @@ are: ...@@ -35,7 +35,7 @@ are:
A Runner that is specific only runs for the specified project(s). A shared Runner 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** 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 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 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 ...@@ -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 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 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. Runners to disabled.
## Registering a specific Runner ## Registering a specific Runner
...@@ -76,7 +76,7 @@ Registering a specific can be done in two ways: ...@@ -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, 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: 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] 1. [Register the Runner][register]
### Making an existing shared Runner specific ### Making an existing shared Runner specific
...@@ -101,7 +101,7 @@ can be changed afterwards under each Runner's settings. ...@@ -101,7 +101,7 @@ can be changed afterwards under each Runner's settings.
To lock/unlock a Runner: 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. Find the Runner you wish to lock/unlock and make sure it's enabled
1. Click the pencil button 1. Click the pencil button
1. Check the **Lock to current projects** option 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 ...@@ -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: 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. Find the Runner you wish to enable/disable
1. Click **Enable for this project** or **Disable for this project** 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 ...@@ -136,7 +136,7 @@ Whenever a Runner is protected, the Runner picks only jobs created on
To protect/unprotect Runners: 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. Find a Runner you want to protect/unprotect and make sure it's enabled
1. Click the pencil button besides the Runner name 1. Click the pencil button besides the Runner name
1. Check the **Protected** option 1. Check the **Protected** option
...@@ -220,7 +220,7 @@ each Runner's settings. ...@@ -220,7 +220,7 @@ each Runner's settings.
To make a Runner pick tagged/untagged jobs: 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. Find the Runner you wish and make sure it's enabled
1. Click the pencil button 1. Click the pencil button
1. Check the **Run untagged jobs** option 1. Check the **Run untagged jobs** option
......
...@@ -34,7 +34,7 @@ instructions to [generate an SSH key](../../ssh/README.md). Do not add a ...@@ -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. 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 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 As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the
content of your _private_ key that you created earlier. content of your _private_ key that you created earlier.
......
...@@ -19,7 +19,7 @@ A unique trigger token can be obtained when [adding a new trigger](#adding-a-new ...@@ -19,7 +19,7 @@ A unique trigger token can be obtained when [adding a new trigger](#adding-a-new
## Adding a new trigger ## Adding a new trigger
You can add a new trigger by going to your project's 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 create a new token which you can then use to trigger a rerun of this
particular project's pipeline. particular project's pipeline.
...@@ -43,7 +43,7 @@ From now on the trigger will be run as you. ...@@ -43,7 +43,7 @@ From now on the trigger will be run as you.
## Revoking a trigger ## Revoking a trigger
You can revoke a trigger any time by going at your project's 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. The action is irreversible.
## Triggering a pipeline ## Triggering a pipeline
...@@ -64,7 +64,7 @@ POST /projects/:id/trigger/pipeline ...@@ -64,7 +64,7 @@ POST /projects/:id/trigger/pipeline
The required parameters are the [trigger's `token`](#authentication-tokens) 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 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 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. settings page which provides self-explanatory examples.
When a rerun of a pipeline is triggered, the information is exposed in GitLab's When a rerun of a pipeline is triggered, the information is exposed in GitLab's
......
...@@ -158,17 +158,17 @@ script: ...@@ -158,17 +158,17 @@ script:
settings. Follow the discussion in issue [#13784][ce-13784] for masking the settings. Follow the discussion in issue [#13784][ce-13784] for masking the
secret variables. secret variables.
GitLab CI allows you to define per-project or per-group **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 that are set in the pipeline environment. The secret variables are stored out of
the repository (`.gitlab-ci.yml`) and are securely passed to GitLab Runner the repository (not in `.gitlab-ci.yml`) and are securely passed to GitLab Runner
making them available in the build environment. It's the recommended method to making them available during a pipeline run. It's the recommended method to
use for storing things like passwords, secret keys and credentials. use for storing things like passwords, SSH keys and credentials.
Project-level secret variables can be added by going to your project's 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 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. Any variables of [subgroups] will be inherited recursively.
Once you set them, they will be available for all subsequent pipelines. You can also 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 ...@@ -185,8 +185,8 @@ protected, it would only be securely passed to pipelines running on the
protected variables. protected variables.
Protected variables can be added by going to your project's Protected variables can be added by going to your project's
**Settings ➔ Pipelines**, then finding the section called **Settings > CI/CD**, then finding the section called
**Secret variables**, and check *Protected*. **Secret variables**, and check "Protected".
Once you set them, they will be available for all subsequent pipelines. Once you set them, they will be available for all subsequent pipelines.
......
...@@ -727,6 +727,9 @@ deployment to the `production` environment. ...@@ -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 - 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 `environment: production`. The recommended way now is to define it under the
`name` keyword. `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: The `environment` name can contain:
...@@ -762,6 +765,9 @@ deploy to production: ...@@ -762,6 +765,9 @@ deploy to production:
- Introduced in GitLab 8.11. - Introduced in GitLab 8.11.
- Before GitLab 8.11, the URL could be added only in GitLab's UI. The - 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`. 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 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. 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 ...@@ -841,10 +847,9 @@ The `stop_review_app` job is **required** to have the following keywords defined
**Notes:** **Notes:**
- [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6. - [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
- The `$CI_ENVIRONMENT_SLUG` was [introduced][ce-7983] in GitLab 8.15. - The `$CI_ENVIRONMENT_SLUG` was [introduced][ce-7983] in GitLab 8.15.
- The `name` and `url` parameters can use any of the defined CI variables,
`environment` can also represent a configuration hash with `name` and `url`. including predefined, secure variables and `.gitlab-ci.yml` [`variables`](#variables).
These parameters can use any of the defined [CI variables](#variables) You however cannot use variables defined under `script`.
(including predefined, secure variables and `.gitlab-ci.yml` variables).
For example: For example:
......
...@@ -253,7 +253,7 @@ only. ...@@ -253,7 +253,7 @@ only.
[^1]: On public and internal projects, all users are able to perform this action. [^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 [^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 [^4]: Not allowed for Guest, Reporter, Developer, Master, or Owner
[^5]: Only if user is not external one. [^5]: Only if user is not external one.
[^6]: Only if user is a member of the project. [^6]: Only if user is a member of the project.
......
...@@ -11,7 +11,7 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI ...@@ -11,7 +11,7 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) | | Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
| HTTP Error Rate (HTTP Errors / sec) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) | | HTTP Error Rate (HTTP Errors / sec) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) |
## Configuring Prometheus to monitor for NGINX ingress metrics ## Configuring NGINX ingress monitoring
If you have deployed with the [gitlab-omnibus](https://docs.gitlab.com/ee/install/kubernetes/gitlab_omnibus.md) Helm chart, and your application is running in the same cluster, no further action is required. The ingress metrics will be automatically enabled and annotated for Prometheus monitoring. Simply ensure Prometheus monitoring is [enabled for your project](../prometheus.md), which is on by default. If you have deployed with the [gitlab-omnibus](https://docs.gitlab.com/ee/install/kubernetes/gitlab_omnibus.md) Helm chart, and your application is running in the same cluster, no further action is required. The ingress metrics will be automatically enabled and annotated for Prometheus monitoring. Simply ensure Prometheus monitoring is [enabled for your project](../prometheus.md), which is on by default.
...@@ -20,14 +20,14 @@ For other deployments, there is some configuration required depending on your in ...@@ -20,14 +20,14 @@ For other deployments, there is some configuration required depending on your in
* NGINX Ingress should be annotated for Prometheus monitoring * NGINX Ingress should be annotated for Prometheus monitoring
* Prometheus should be configured to monitor annotated pods * Prometheus should be configured to monitor annotated pods
### Configuring NGINX Ingress for Prometheus monitoring ### Setting up NGINX Ingress for Prometheus monitoring
Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx) have built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint will start running on port 10254. Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx) have built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint will start running on port 10254.
With metric data now available, Prometheus needs to be configured to collect it. The easiest way to do this is to leverage Prometheus' [built-in Kubernetes service discovery](https://prometheus.io/docs/operating/configuration/#kubernetes_sd_config), which automatically detects a variety of Kubernetes components and makes them available for monitoring. NGINX ingress metrics are exposed per pod, a sample scrape configuration [is available](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml#L248). This configuration will detect pods and enable collection of metrics **only if** they have been specifically annotated for monitoring. With metric data now available, Prometheus needs to be configured to collect it. The easiest way to do this is to leverage Prometheus' [built-in Kubernetes service discovery](https://prometheus.io/docs/operating/configuration/#kubernetes_sd_config), which automatically detects a variety of Kubernetes components and makes them available for monitoring. Since NGINX ingress metrics are exposed per pod, a scrape job for Kubernetes pods is required. A sample pod scraping configuration [is available](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml#L248). This configuration will detect pods and enable collection of metrics **only if** they have been specifically annotated for monitoring.
Depending on how NGINX ingress was deployed, typically a DaemonSet or Deployment, edit the corresponding YML spec. Two new annotations need to be added: Depending on how NGINX ingress was deployed, typically a DaemonSet or Deployment, edit the corresponding YML spec. Two new annotations need to be added:
* `prometheus.io/port: "true"` * `prometheus.io/scrape: "true"`
* `prometheus.io/port: "10254"` * `prometheus.io/port: "10254"`
Prometheus should now be collecting NGINX ingress metrics. To validate view the Prometheus Targets, available under `Status > Targets` on the Prometheus dashboard. New entries for NGINX should be listed in the kubernetes pod monitoring job, `kubernetes-pods`. Prometheus should now be collecting NGINX ingress metrics. To validate view the Prometheus Targets, available under `Status > Targets` on the Prometheus dashboard. New entries for NGINX should be listed in the kubernetes pod monitoring job, `kubernetes-pods`.
......
# Pipelines settings # Pipelines settings
To reach the pipelines settings navigate to your project's To reach the pipelines settings navigate to your project's
**Settings ➔ Pipelines**. **Settings ➔ CI/CD**.
The following settings can be configured per project. The following settings can be configured per project.
......
doc/user/project/repository/img/compare_branches.png

35.2 KB | W: | H:

doc/user/project/repository/img/compare_branches.png

202 KB | W: | H:

doc/user/project/repository/img/compare_branches.png
doc/user/project/repository/img/compare_branches.png
doc/user/project/repository/img/compare_branches.png
doc/user/project/repository/img/compare_branches.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -31,8 +31,8 @@ on the search field on the top-right of your screen: ...@@ -31,8 +31,8 @@ on the search field on the top-right of your screen:
If you want to search for issues present in a specific project, navigate to If you want to search for issues present in a specific project, navigate to
a project's **Issues** tab, and click on the field **Search or filter results...**. It will a project's **Issues** tab, and click on the field **Search or filter results...**. It will
display a dropdown menu, from which you can add filters per author, assignee, milestone, label, display a dropdown menu, from which you can add filters per author, assignee, milestone,
and weight. When done, press **Enter** on your keyboard to filter the issues. label, weight, and 'my-reaction' (based on your emoji votes). When done, press **Enter** on your keyboard to filter the issues.
![filter issues in a project](img/issue_search_filter.png) ![filter issues in a project](img/issue_search_filter.png)
......
Feature: Project Archived
Background:
Given I sign in as a user
And I own project "Shop"
And I own project "Forum"
Scenario: I should not see archived on project page of not-archive project
And project "Forum" is archived
And I visit project "Shop" page
Then I should not see "Archived"
Scenario: I should see archived on project page of archive project
And project "Forum" is archived
And I visit project "Forum" page
Then I should see "Archived"
Scenario: I archive project
When project "Shop" has push event
And I visit project "Shop" page
And I visit edit project "Shop" page
And I set project archived
Then I should see "Archived"
Scenario: I unarchive project
When project "Shop" has push event
And project "Shop" is archived
And I visit project "Shop" page
And I visit edit project "Shop" page
And I set project unarchived
Then I should not see "Archived"
@project_commits
Feature: Revert Commits
Background:
Given I sign in as a user
And I own a project
And I visit my project's commits page
@javascript
Scenario: I revert a commit
Given I click on commit link
And I click on the revert button
And I revert the changes directly
Then I should see the revert commit notice
@javascript
Scenario: I revert a commit that was previously reverted
Given I click on commit link
And I click on the revert button
And I revert the changes directly
And I visit my project's commits page
And I click on commit link
And I click on the revert button
And I revert the changes directly
Then I should see a revert error
@javascript
Scenario: I revert a commit in a new merge request
Given I click on commit link
And I click on the revert button
And I revert the changes in a new merge request
Then I should see the new merge request notice
@dashboard
Feature: Search
Background:
Given I sign in as a user
And I own project "Shop"
And I visit dashboard search page
Scenario: I should see project I am looking for
Given I search for "Sho"
Then I should see "Shop" project link
@javascript
Scenario: I should see issues I am looking for
And project has issues
When I search for "Foo"
And I click "Issues" link
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
@javascript
Scenario: I should see merge requests I am looking for
And project has merge requests
When I search for "Foo"
When I click "Merge requests" link
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
@javascript
Scenario: I should see milestones I am looking for
And project has milestones
When I search for "Foo"
When I click "Milestones" link
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
@javascript
Scenario: I should see project code I am looking for
When I click project "Shop" link
And I search for "rspec"
Then I should see code results for project "Shop"
@javascript
Scenario: I should see project issues
And project has issues
When I click project "Shop" link
And I search for "Foo"
And I click "Issues" link
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
@javascript
Scenario: I should see project merge requests
And project has merge requests
When I click project "Shop" link
And I search for "Foo"
And I click "Merge requests" link
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
@javascript
Scenario: I should see project milestones
And project has milestones
When I click project "Shop" link
And I search for "Foo"
And I click "Milestones" link
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
@javascript
Scenario: I should see Wiki blobs
And project has Wiki content
When I click project "Shop" link
And I search for "Wiki content"
And I click "Wiki" link
Then I should see "test_wiki" link in the search results
Scenario: I logout and should see project I am looking for
Given project "Shop" is public
And I logout directly
And I visit dashboard search page
And I search for "Sho"
Then I should see "Shop" project link
@javascript
Scenario: I logout and should see issues I am looking for
Given project "Shop" is public
And I logout directly
And I visit dashboard search page
And project has issues
When I search for "Foo"
And I click "Issues" link
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
Scenario: I logout and should see project code I am looking for
Given project "Shop" is public
And I logout directly
When I visit project "Shop" page
And I search for "rspec" on project page
Then I should see code results for project "Shop"
class Spinach::Features::ProjectArchived < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
When 'project "Forum" is archived' do
project = Project.find_by(name: "Forum")
project.update_attribute(:archived, true)
end
When 'project "Shop" is archived' do
project = Project.find_by(name: "Shop")
project.update_attribute(:archived, true)
end
When 'I visit project "Forum" page' do
project = Project.find_by(name: "Forum")
visit project_path(project)
end
step 'I should not see "Archived"' do
expect(page).not_to have_content "Archived"
end
step 'I should see "Archived"' do
expect(page).to have_content "Archived"
end
When 'I set project archived' do
click_link "Archive"
end
When 'I set project unarchived' do
click_link "Unarchive"
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment