Merge branch 'master' into sh-headless-chrome-support

* master: (97 commits)
  Eliminate N+1 queries in loading discussions.json endpoint
  Clean up read_registry scope changes
  Add missing import statements
  Improve “New project“ page description
  Fix notification message when admin label was modified
  Remove gaps under nav on build page
  Replace the 'project/snippets.feature' spinach test with an rspec analog
  Use correct group members path for members flyout link
  Fix docs for lightweight tag creation via API
  Replace the 'project/commits/revert.feature' spinach test with an rspec analog
  Merge branch 'rs-incoming-email-domain-docs' into 'security-10-0'
  Replace the 'project/archived.feature' spinach test with an rspec analog
  Fix broken link in docs/api/wiki.md
  Fixed the new sidebars width when browser has scrollbars
  Improve 'spec/features/profiles/*' specs
  Replace the 'search.feature' spinach test with an rspec analog
  dedupe yarn packages
  add dependency approvals (all MIT license)
  update build image to latest with node 8.x, yarn 1.0.2, and chrome 61
  Ensure we use `Entities::User` for non-admin `users/:id` API requests
  ...
This commit is contained in:
Mike Greiling 2017-09-18 13:05:34 -05:00
commit 27a28d9970
222 changed files with 2883 additions and 1501 deletions

View File

@ -1,4 +1,4 @@
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"
.default-cache: &default-cache
key: "ruby-233-with-yarn"
@ -191,6 +191,9 @@ review-docs-deploy:
stage: build
environment:
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
script:
- gem install gitlab --no-doc

View File

@ -286,7 +286,10 @@ might be edited to make them small and simple.
Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
For changes in the interface, it can be helpful to create a mockup first.
For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself.
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.

View File

@ -358,7 +358,7 @@ GEM
rake
grape_logging (1.7.0)
grape
grpc (1.4.5)
grpc (1.6.0)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)

View File

@ -7,6 +7,7 @@ class DeleteModal {
this.$branchName = $('.js-branch-name', this.$modal);
this.$confirmInput = $('.js-delete-branch-input', this.$modal);
this.$deleteBtn = $('.js-delete-branch', this.$modal);
this.$notMerged = $('.js-not-merged', this.$modal);
this.bindEvents();
}
@ -16,8 +17,10 @@ class DeleteModal {
}
setModalData(e) {
this.branchName = e.currentTarget.dataset.branchName || '';
this.deletePath = e.currentTarget.dataset.deletePath || '';
const branchData = e.currentTarget.dataset;
this.branchName = branchData.branchName || '';
this.deletePath = branchData.deletePath || '';
this.isMerged = !!branchData.isMerged;
this.updateModal();
}
@ -30,6 +33,7 @@ class DeleteModal {
this.$confirmInput.val('');
this.$deleteBtn.attr('href', this.deletePath);
this.$deleteBtn.attr('disabled', true);
this.$notMerged.toggleClass('hidden', this.isMerged);
}
}

View File

@ -4,6 +4,8 @@
import Vue from 'vue';
import '../mixins/discussion';
const JumpToDiscussion = Vue.extend({
mixins: [DiscussionMixins],
props: {

View File

@ -4,6 +4,8 @@
import Vue from 'vue';
import '../mixins/discussion';
window.ResolveCount = Vue.extend({
mixins: [DiscussionMixins],
props: {

View File

@ -15,6 +15,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
params: {
per_page: 20,
active: true,
group_id: this.getGroupId(),
project_id: this.getProjectId(),
current_user: true,
},
@ -47,6 +48,10 @@ class DropdownUser extends gl.FilteredSearchDropdown {
super.renderContent(forceShowList);
}
getGroupId() {
return this.input.getAttribute('data-group-id');
}
getProjectId() {
return this.input.getAttribute('data-project-id');
}

View File

@ -77,10 +77,11 @@ export const hideMenu = (el) => {
export const moveSubItemsToPosition = (el, subItems) => {
const boundingRect = el.getBoundingClientRect();
const top = calculateTop(boundingRect, subItems.offsetHeight);
const left = sidebar ? sidebar.offsetWidth : 50;
const isAbove = top < boundingRect.top;
subItems.classList.add('fly-out-list');
subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
subItems.style.transform = `translate3d(${left}px, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
const subItemsRect = subItems.getBoundingClientRect();

View File

@ -127,13 +127,6 @@ import DropdownUtils from './filtered_search/dropdown_utils';
$('.has-tooltip', $value).tooltip({
container: 'body'
});
return $value.find('a').each(function(i) {
return setTimeout((function(_this) {
return function() {
return gl.animate.animate($(_this), 'pulse');
};
})(this), 200 * i);
});
});
};
$dropdown.glDropdown({

View File

@ -1,49 +0,0 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, max-len */
(function() {
(function(w) {
if (w.gl == null) {
w.gl = {};
}
if (gl.animate == null) {
gl.animate = {};
}
gl.animate.animate = function($el, animation, options, done) {
if ((options != null ? options.cssStart : void 0) != null) {
$el.css(options.cssStart);
}
$el.removeClass(animation + ' animated').addClass(animation + ' animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
$(this).removeClass(animation + ' animated');
if (done != null) {
done();
}
if ((options != null ? options.cssEnd : void 0) != null) {
$el.css(options.cssEnd);
}
});
};
gl.animate.animateEach = function($els, animation, time, options, done) {
var dfd;
dfd = $.Deferred();
if (!$els.length) {
dfd.resolve();
}
$els.each(function(i) {
setTimeout((function(_this) {
return function() {
var $this;
$this = $(_this);
return gl.animate.animate($this, animation, options, function() {
if (i === $els.length - 1) {
dfd.resolve();
if (done != null) {
return done();
}
}
});
};
})(this), time * i);
});
return dfd.promise();
};
})(window);
}).call(window);

View File

@ -39,7 +39,6 @@ import './commit/file';
import './commit/image_file';
// lib/utils
import './lib/utils/animate';
import './lib/utils/bootstrap_linked_tabs';
import './lib/utils/common_utils';
import './lib/utils/datetime_utility';

View File

@ -45,7 +45,7 @@ import _ from 'underscore';
if (issueUpdateURL) {
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
}
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
@ -208,6 +208,7 @@ import _ from 'underscore';
if (data.milestone != null) {
data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
} else {

View File

@ -15,7 +15,6 @@ export default class NewNavSidebar {
this.$openSidebar = $('.toggle-mobile-nav');
this.$closeSidebar = $('.close-nav-button');
this.$sidebarToggle = $('.js-toggle-sidebar');
this.$topLevelLinks = $('.sidebar-top-level-items > li > a');
}
bindEvents() {
@ -56,10 +55,6 @@ export default class NewNavSidebar {
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
}
NewNavSidebar.setCollapsedCookie(collapsed);
this.$topLevelLinks.attr('title', function updateTopLevelTitle() {
return collapsed ? this.getAttribute('aria-label') : '';
});
}
render() {

View File

@ -86,7 +86,7 @@
<div class="note-actions">
<span
v-if="accessLevel"
class="note-role">{{accessLevel}}</span>
class="note-role note-role-access">{{accessLevel}}</span>
<div
v-if="canAddAwardEmoji"
class="note-actions-item">

View File

@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import findAndFollowLink from './shortcuts_dashboard_navigation';

View File

@ -158,11 +158,23 @@
box-shadow: inset 4px 0 0 $color-700;
> a {
color: $color-900;
color: $color-800;
}
svg {
fill: $color-900;
fill: $color-800;
}
}
.sidebar-top-level-items > li.active .badge {
color: $color-800;
}
.nav-links li.active a {
border-bottom-color: $color-500;
.badge {
font-weight: $gl-font-weight-bold;
}
}
}
@ -261,5 +273,9 @@ body {
fill: $theme-gray-900;
}
}
.sidebar-top-level-items > li.active .badge {
color: $theme-gray-900;
}
}
}

View File

@ -328,7 +328,7 @@
border-bottom: 1px solid $border-color;
transition: padding $sidebar-transition-duration;
text-align: center;
margin-top: $header-height;
margin-top: $new-navbar-height;
.container-fluid {
position: relative;

View File

@ -78,16 +78,16 @@
.right-sidebar {
border-left: 1px solid $border-color;
height: calc(100% - #{$header-height});
height: calc(100% - #{$new-navbar-height});
&.affix {
position: fixed;
top: $header-height;
top: $new-navbar-height;
}
}
.with-performance-bar .right-sidebar.affix {
top: $header-height + $performance-bar-height;
top: $new-navbar-height + $performance-bar-height;
}
@mixin maintain-sidebar-dimensions {

View File

@ -13,6 +13,7 @@ $sidebar-breakpoint: 1024px;
$darken-normal-factor: 7%;
$darken-dark-factor: 10%;
$darken-border-factor: 5%;
$darken-border-dashed-factor: 25%;
$white-light: #fff;
$white-normal: #f0f0f0;
@ -134,6 +135,7 @@ $border-white-normal: darken($white-normal, $darken-border-factor);
$border-gray-light: darken($gray-light, $darken-border-factor);
$border-gray-normal: darken($gray-normal, $darken-border-factor);
$border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor);
$border-gray-dark: darken($white-normal, $darken-border-factor);
/*

View File

@ -3,8 +3,6 @@
@import "bootstrap/variables";
$active-background: rgba(0, 0, 0, .04);
$active-border: $indigo-500;
$active-color: $indigo-700;
$active-hover-background: $active-background;
$active-hover-color: $gl-text-color;
$inactive-badge-background: rgba(0, 0, 0, .08);
@ -107,7 +105,8 @@ $new-sidebar-collapsed-width: 50px;
}
&.sidebar-icons-only {
width: $new-sidebar-collapsed-width;
width: auto;
min-width: $new-sidebar-collapsed-width;
.nav-sidebar-inner-scroll {
overflow-x: hidden;
@ -126,6 +125,10 @@ $new-sidebar-collapsed-width: 50px;
.fly-out-top-item {
display: block;
}
.avatar-container {
margin-right: 0;
}
}
&.nav-sidebar-expanded {
@ -189,7 +192,7 @@ $new-sidebar-collapsed-width: 50px;
.nav-sidebar-inner-scroll {
height: 100%;
width: 100%;
overflow: auto;
overflow: scroll;
}
.with-performance-bar .nav-sidebar {
@ -217,7 +220,6 @@ $new-sidebar-collapsed-width: 50px;
&:hover,
&:focus {
background: $active-background;
color: $active-color;
}
}
}
@ -251,7 +253,7 @@ $new-sidebar-collapsed-width: 50px;
@media (min-width: $screen-sm-min) {
position: fixed;
top: 0;
left: $new-sidebar-width;
left: 0;
min-width: 150px;
margin-top: -1px;
padding: 4px 1px;
@ -317,7 +319,6 @@ $new-sidebar-collapsed-width: 50px;
}
.badge {
color: $active-color;
font-weight: $gl-font-weight-bold;
}
@ -390,10 +391,6 @@ $new-sidebar-collapsed-width: 50px;
}
.sidebar-sub-level-items {
@media (min-width: $screen-sm-min) {
left: $new-sidebar-collapsed-width;
}
&:not(.flyout-list) {
display: none;
}
@ -494,13 +491,3 @@ $new-sidebar-collapsed-width: 50px;
.with-performance-bar .boards-list {
height: calc(100vh - #{$new-navbar-height} - #{$performance-bar-height});
}
// Change color of all horizontal tabs to match the new indigo color
.nav-links li.active a {
border-bottom-color: $active-border;
.badge {
font-weight: $gl-font-weight-bold;
}
}

View File

@ -64,10 +64,10 @@
color: $gl-text-color;
position: sticky;
position: -webkit-sticky;
top: $header-height;
top: $new-navbar-height;
&.affix {
top: $header-height;
top: $new-navbar-height;
}
// with sidebar
@ -174,10 +174,10 @@
.with-performance-bar .build-page {
.top-bar {
top: $header-height + $performance-bar-height;
top: $new-navbar-height + $performance-bar-height;
&.affix {
top: $header-height + $performance-bar-height;
top: $new-navbar-height + $performance-bar-height;
}
}
}

View File

@ -634,8 +634,16 @@
padding-top: 8px;
padding-bottom: 8px;
}
.diff-changed-file {
display: flex;
align-items: center;
}
}
.diff-file-changes-path {
@include str-truncated(78%);
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -449,6 +449,12 @@
}
}
}
.milestone-title span {
@include str-truncated(100%);
display: block;
margin: 0 4px;
}
}
a {

View File

@ -95,6 +95,8 @@
}
.omniauth-container {
font-size: 13px;
p {
margin: 0;
}

View File

@ -209,6 +209,11 @@
}
.stage-cell {
@media (min-width: $screen-md-min) {
min-width: 148px;
margin-right: -4px;
}
.mini-pipeline-graph-dropdown-toggle svg {
height: $ci-action-icon-size;
width: $ci-action-icon-size;

View File

@ -752,7 +752,7 @@ a.deploy-project-label {
}
li.missing {
border: 1px dashed $border-gray-normal;
border: 1px dashed $border-gray-normal-dashed;
border-radius: $border-radius-default;
a {

View File

@ -71,6 +71,11 @@
height: 100%;
.monaco-editor.vs {
.current-line {
border: none;
background: $well-light-border;
}
.line-numbers {
cursor: pointer;
@ -84,6 +89,13 @@
}
}
.blob-no-preview {
.vertical-center {
justify-content: center;
width: 100%;
}
}
&.edit-mode {
.blob-viewer-container {
overflow: hidden;
@ -103,7 +115,7 @@
overflow: auto;
> div,
.file-content {
.file-content:not(.wiki) {
display: flex;
}

View File

@ -10,9 +10,8 @@ class Admin::DeployKeysController < Admin::ApplicationController
end
def create
@deploy_key = deploy_keys.new(create_params.merge(user: current_user))
if @deploy_key.save
@deploy_key = DeployKeys::CreateService.new(current_user, create_params.merge(public: true)).execute
if @deploy_key.persisted?
redirect_to admin_deploy_keys_path
else
render 'new'

View File

@ -29,7 +29,7 @@ class Admin::LabelsController < Admin::ApplicationController
@label = Labels::UpdateService.new(label_params).execute(@label)
if @label.valid?
redirect_to admin_labels_path, notice: 'label was successfully updated.'
redirect_to admin_labels_path, notice: 'Label was successfully updated.'
else
render :edit
end

View File

@ -3,31 +3,10 @@ class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users, :award_emojis]
before_action :load_project, only: [:users]
before_action :find_users, only: [:users]
before_action :load_group, only: [:users]
def users
@users ||= User.none
@users = @users.active
@users = @users.reorder(:name)
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = @users.page(params[:page]).per(params[:per_page])
if params[:todo_filter].present? && current_user
@users = @users.todo_authors(current_user.id, params[:todo_state_filter])
end
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user].present? && current_user
@users = [current_user, *@users].uniq
end
if params[:author_id].present? && current_user
author = User.find_by_id(params[:author_id])
@users = [author, *@users].uniq if author
end
end
@users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
end
@ -60,26 +39,14 @@ class AutocompleteController < ApplicationController
private
def find_users
@users =
if @project
user_ids = @project.team.users.pluck(:id)
if params[:author_id].present?
user_ids << params[:author_id]
end
User.where(id: user_ids)
elsif params[:group_id].present?
def load_group
@group ||= begin
if @project.blank? && params[:group_id].present?
group = Group.find(params[:group_id])
return render_404 unless can?(current_user, :read_group, group)
group.users
elsif current_user
User.all
else
User.none
group
end
end
end
def load_project

View File

@ -11,9 +11,15 @@ module Boards
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
issues = issues.page(params[:page]).per(params[:per] || 20)
make_sure_position_is_set(issues)
issues = issues.preload(:project,
:milestone,
:assignees,
labels: [:priorities],
notes: [:award_emoji, :author]
)
render json: {
issues: serialize_as_json(issues.preload(:project)),
issues: serialize_as_json(issues),
size: issues.total_count
}
end
@ -76,14 +82,13 @@ module Boards
def serialize_as_json(resource)
resource.as_json(
labels: true,
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
labels: true,
include: {
project: { only: [:id, :path] },
assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
milestone: { only: [:id, :title] }
},
user: current_user
}
)
end
end

View File

@ -7,9 +7,9 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
end
def create
@gpg_key = current_user.gpg_keys.new(gpg_key_params)
@gpg_key = GpgKeys::CreateService.new(current_user, gpg_key_params).execute
if @gpg_key.save
if @gpg_key.persisted?
redirect_to profile_gpg_keys_path
else
@gpg_keys = current_user.gpg_keys.select(&:persisted?)

View File

@ -11,9 +11,9 @@ class Profiles::KeysController < Profiles::ApplicationController
end
def create
@key = current_user.keys.new(key_params)
@key = Keys::CreateService.new(current_user, key_params).execute
if @key.save
if @key.persisted?
redirect_to profile_key_path(@key)
else
@keys = current_user.keys.select(&:persisted?)

View File

@ -38,7 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def set_index_vars
@scopes = Gitlab::Auth::AVAILABLE_SCOPES
@scopes = Gitlab::Auth.available_scopes
@personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute

View File

@ -27,7 +27,7 @@ class Projects::CompareController < Projects::ApplicationController
def create
if params[:from].blank? || params[:to].blank?
flash[:alert] = "You must select from and to branches"
flash[:alert] = "You must select a Source and a Target revision"
from_to_vars = {
from: params[:from].presence,
to: params[:to].presence

View File

@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def create
@key = DeployKey.new(create_params.merge(user: current_user))
@key = DeployKeys::CreateService.new(current_user, create_params).execute
unless @key.valid? && @project.deploy_keys << @key
flash[:alert] = @key.errors.full_messages.join(', ').html_safe

View File

@ -87,9 +87,9 @@ class Projects::IssuesController < Projects::ApplicationController
.inc_relations_for_view
.includes(:noteable)
.fresh
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
prepare_notes_for_rendering(notes)
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
discussions = Discussion.build_collection(notes, @issue)

View File

@ -0,0 +1,60 @@
class AutocompleteUsersFinder
attr_reader :current_user, :project, :group, :search, :skip_users,
:page, :per_page, :author_id, :params
def initialize(params:, current_user:, project:, group:)
@current_user = current_user
@project = project
@group = group
@search = params[:search]
@skip_users = params[:skip_users]
@page = params[:page]
@per_page = params[:per_page]
@author_id = params[:author_id]
@params = params
end
def execute
items = find_users
items = items.active
items = items.reorder(:name)
items = items.search(search) if search.present?
items = items.where.not(id: skip_users) if skip_users.present?
items = items.page(page).per(per_page)
if params[:todo_filter].present? && current_user
items = items.todo_authors(current_user.id, params[:todo_state_filter])
end
if search.blank?
# Include current user if available to filter by "Me"
if params[:current_user].present? && current_user
items = [current_user, *items].uniq
end
if author_id.present? && current_user
author = User.find_by_id(author_id)
items = [author, *items].uniq if author
end
end
items
end
private
def find_users
return users_from_project if project
return group.users if group
return User.all if current_user
User.none
end
def users_from_project
user_ids = project.team.users.pluck(:id)
user_ids << author_id if author_id.present?
User.where(id: user_ids)
end
end

View File

@ -77,4 +77,8 @@ module BoardsHelper
'max-select': dropdown_options[:data][:'max-select']
}
end
def boards_link_text
_("Board")
end
end

View File

@ -21,7 +21,7 @@ module GroupsHelper
group.ancestors.reverse.each_with_index do |parent, index|
if index > 0
add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true), location: :before)
add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true, for_dropdown: true), location: :before)
else
full_title += breadcrumb_list_item group_title_link(parent, hidable: false)
end
@ -85,8 +85,8 @@ module GroupsHelper
private
def group_title_link(group, hidable: false, show_avatar: false)
link_to(group_path(group), class: "group-path breadcrumb-item-text js-breadcrumb-item-text #{'hidable' if hidable}") do
def group_title_link(group, hidable: false, show_avatar: false, for_dropdown: false)
link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do
output =
if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15)

View File

@ -119,8 +119,4 @@ module TabHelper
'active' if current_controller?('oauth/applications')
end
def sidebar_link(href, title: nil, css: nil, &block)
link_to capture(&block), href, title: (title if collapsed_sidebar?), class: css, aria: { label: title }
end
end

View File

@ -453,6 +453,10 @@ module Ci
.fabricate!
end
def latest_builds_with_artifacts
@latest_builds_with_artifacts ||= builds.latest.with_artifacts
end
private
def ci_yaml_from_repo

View File

@ -28,10 +28,4 @@ class DeployKey < Key
def can_push_to?(project)
can_push? && has_access_to?(project)
end
private
# we don't want to notify the user for deploy keys
def notify_user
end
end

View File

@ -6,7 +6,10 @@ class Environment < ActiveRecord::Base
belongs_to :project, required: true, validate: true
has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :deployments,
-> (env) { where(project_id: env.project_id) },
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment'
before_validation :nullify_external_url

View File

@ -36,7 +36,6 @@ class GpgKey < ActiveRecord::Base
before_validation :extract_fingerprint, :extract_primary_keyid
after_commit :update_invalid_gpg_signatures, on: :create
after_commit :notify_user, on: :create
def primary_keyid
super&.upcase
@ -107,8 +106,4 @@ class GpgKey < ActiveRecord::Base
# only allows one key
self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
end
def notify_user
NotificationService.new.new_gpg_key(self)
end
end

View File

@ -28,7 +28,6 @@ class Key < ActiveRecord::Base
delegate :name, :email, to: :user, prefix: true
after_commit :add_to_shell, on: :create
after_commit :notify_user, on: :create
after_create :post_create_hook
after_commit :remove_from_shell, on: :destroy
after_destroy :post_destroy_hook
@ -118,8 +117,4 @@ class Key < ActiveRecord::Base
"type is forbidden. Must be #{allowed_types}"
end
def notify_user
NotificationService.new.new_key(self)
end
end

View File

@ -127,7 +127,12 @@ class Label < ActiveRecord::Base
end
def priority(project)
priorities.find_by(project: project).try(:priority)
priority = if priorities.loaded?
priorities.first { |p| p.project == project }
else
priorities.find_by(project: project)
end
priority.try(:priority)
end
def template?

View File

@ -231,6 +231,13 @@ class Namespace < ActiveRecord::Base
end
def force_share_with_group_lock_on_descendants
descendants.update_all(share_with_group_lock: true)
return unless Group.supports_nested_groups?
# We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
# different table aliases, hence we're just using WHERE IN. Since we have a
# maximum of 20 nested groups this should be fine.
Namespace.where(id: descendants.select(:id))
.update_all(share_with_group_lock: true)
end
end

View File

@ -28,7 +28,7 @@ class PersonalAccessToken < ActiveRecord::Base
protected
def validate_scopes
unless revoked || scopes.all? { |scope| Gitlab::Auth::AVAILABLE_SCOPES.include?(scope.to_sym) }
unless revoked || scopes.all? { |scope| Gitlab::Auth.available_scopes.include?(scope.to_sym) }
errors.add :scopes, "can only contain available scopes"
end
end

View File

@ -161,7 +161,7 @@ class Project < ActiveRecord::Base
has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
has_one :project_feature
has_one :project_feature, inverse_of: :project
has_one :statistics, class_name: 'ProjectStatistics'
# Container repositories need to remove data from the container registry,
@ -190,7 +190,7 @@ class Project < ActiveRecord::Base
has_one :auto_devops, class_name: 'ProjectAutoDevops'
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops
@ -1163,6 +1163,23 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
def latest_successful_pipeline_for_default_branch
if defined?(@latest_successful_pipeline_for_default_branch)
return @latest_successful_pipeline_for_default_branch
end
@latest_successful_pipeline_for_default_branch =
pipelines.latest_successful_for(default_branch)
end
def latest_successful_pipeline_for(ref = nil)
if ref && ref != default_branch
pipelines.latest_successful_for(ref)
else
latest_successful_pipeline_for_default_branch
end
end
def enable_ci
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end

View File

@ -41,6 +41,8 @@ class ProjectFeature < ActiveRecord::Base
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
belongs_to :project, -> { unscope(where: :pending_delete) }
validates :project, presence: true
validate :repository_children_level
default_value_for :builds_access_level, value: ENABLED, allows_nil: false

View File

@ -80,6 +80,6 @@ class PipelinesEmailService < Service
end
def retrieve_recipients(data)
recipients.to_s.split(',').reject(&:blank?)
recipients.to_s.split(/[,(?:\r?\n) ]+/).reject(&:empty?)
end
end

View File

@ -146,7 +146,7 @@ class ProjectTeam
def member?(user, min_access_level = Gitlab::Access::GUEST)
return false unless user
user.authorized_project?(project, min_access_level)
max_member_access(user.id) >= min_access_level
end
def human_max_access(user_id)

View File

@ -90,6 +90,12 @@ class Repository
)
end
# we need to have this method here because it is not cached in ::Git and
# the method is called multiple times for every request
def has_visible_content?
branch_count > 0
end
def inspect
"#<#{self.class.name}:#{@disk_path}>"
end
@ -166,7 +172,7 @@ class Repository
end
def add_branch(user, branch_name, ref)
branch = raw_repository.add_branch(branch_name, committer: user, target: ref)
branch = raw_repository.add_branch(branch_name, user: user, target: ref)
after_create_branch
@ -176,7 +182,7 @@ class Repository
end
def add_tag(user, tag_name, target, message = nil)
raw_repository.add_tag(tag_name, committer: user, target: target, message: message)
raw_repository.add_tag(tag_name, user: user, target: target, message: message)
rescue Gitlab::Git::Repository::InvalidRef
false
end
@ -184,7 +190,7 @@ class Repository
def rm_branch(user, branch_name)
before_remove_branch
raw_repository.rm_branch(branch_name, committer: user)
raw_repository.rm_branch(branch_name, user: user)
after_remove_branch
true
@ -193,7 +199,7 @@ class Repository
def rm_tag(user, tag_name)
before_remove_tag
raw_repository.rm_tag(tag_name, committer: user)
raw_repository.rm_tag(tag_name, user: user)
after_remove_tag
true
@ -762,17 +768,23 @@ class Repository
multi_action(**options)
end
def with_cache_hooks
result = yield
return unless result
after_create if result.repo_created?
after_create_branch if result.branch_created?
result.newrev
end
def with_branch(user, *args)
result = Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit|
yield start_commit
with_cache_hooks do
Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit|
yield start_commit
end
end
newrev, should_run_after_create, should_run_after_create_branch = result
after_create if should_run_after_create
after_create_branch if should_run_after_create_branch
newrev
end
# rubocop:disable Metrics/ParameterLists
@ -837,30 +849,13 @@ class Repository
end
end
def merge(user, source, merge_request, options = {})
with_branch(
user,
merge_request.target_branch) do |start_commit|
our_commit = start_commit.sha
their_commit = source
raise 'Invalid merge target' unless our_commit
raise 'Invalid merge source' unless their_commit
merge_index = rugged.merge_commits(our_commit, their_commit)
break if merge_index.conflicts?
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged)
)
commit_id = create_commit(actual_options)
merge_request.update(in_progress_merge_commit_sha: commit_id)
commit_id
def merge(user, source_sha, merge_request, message)
with_cache_hooks do
raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
merge_request.update(in_progress_merge_commit_sha: commit_id)
nil # Return value does not matter.
end
end
rescue Gitlab::Git::CommitError # when merge_index.conflicts?
false
end
def revert(
@ -1151,12 +1146,6 @@ class Repository
Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags))
end
def create_commit(params = {})
params[:message].delete!("\r")
Rugged::Commit.create(rugged, params)
end
def last_commit_for_path_by_gitaly(sha, path)
c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
commit(c)

View File

@ -0,0 +1,7 @@
module DeployKeys
class CreateService < Keys::BaseService
def execute
DeployKey.create(params.merge(user: user))
end
end
end

View File

@ -0,0 +1,9 @@
module GpgKeys
class CreateService < Keys::BaseService
def execute
key = user.gpg_keys.create(params)
notification_service.new_gpg_key(key) if key.persisted?
key
end
end
end

View File

@ -0,0 +1,13 @@
module Keys
class BaseService
attr_accessor :user, :params
def initialize(user, params)
@user, @params = user, params
end
def notification_service
NotificationService.new
end
end
end

View File

@ -0,0 +1,9 @@
module Keys
class CreateService < ::Keys::BaseService
def execute
key = user.keys.create(params)
notification_service.new_key(key) if key.persisted?
key
end
end
end

View File

@ -38,15 +38,9 @@ module MergeRequests
private
def commit
committer = repository.user_to_committer(current_user)
message = params[:commit_message] || merge_request.merge_commit_message
options = {
message: params[:commit_message] || merge_request.merge_commit_message,
author: committer,
committer: committer
}
commit_id = repository.merge(current_user, source, merge_request, options)
commit_id = repository.merge(current_user, source, merge_request, message)
raise MergeError, 'Conflicts detected during merge' unless commit_id

View File

@ -24,7 +24,10 @@ module Projects
success
else
error('Project could not be updated!')
model_errors = project.errors.full_messages.to_sentence
error_message = model_errors.presence || 'Project could not be updated!'
error(error_message)
end
end

View File

@ -7,6 +7,8 @@
%span.light
- has_icon = provider_has_icon?(provider)
= link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
%fieldset.prepend-top-10
= check_box_tag :remember_me
= label_tag :remember_me, 'Remember me'
%fieldset.prepend-top-10.checkbox.remember-me
%label
= check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox'
%span
Remember me

View File

@ -7,7 +7,7 @@
.sidebar-context-title Admin Area
%ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do
= sidebar_link admin_root_path, title: _('Overview'), css: 'shortcuts-tree' do
= link_to admin_root_path, class: 'shortcuts-tree' do
.nav-icon-container
= custom_icon('overview')
%span.nav-item-name
@ -53,7 +53,7 @@
ConvDev Index
= nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
= sidebar_link admin_system_info_path, title: _('Monitoring') do
= link_to admin_system_info_path do
.nav-icon-container
= custom_icon('monitoring')
%span.nav-item-name
@ -87,7 +87,7 @@
Requests Profiles
= nav_link(controller: :broadcast_messages) do
= sidebar_link admin_broadcast_messages_path, title: _('Messages') do
= link_to admin_broadcast_messages_path do
.nav-icon-container
= custom_icon('messages')
%span.nav-item-name
@ -99,7 +99,7 @@
#{ _('Messages') }
= nav_link(controller: [:hooks, :hook_logs]) do
= sidebar_link admin_hooks_path, title: _('Hooks') do
= link_to admin_hooks_path do
.nav-icon-container
= custom_icon('system_hooks')
%span.nav-item-name
@ -111,7 +111,7 @@
#{ _('System Hooks') }
= nav_link(controller: :applications) do
= sidebar_link admin_applications_path, title: _('Applications') do
= link_to admin_applications_path do
.nav-icon-container
= custom_icon('applications')
%span.nav-item-name
@ -123,7 +123,7 @@
#{ _('Applications') }
= nav_link(controller: :abuse_reports) do
= sidebar_link admin_abuse_reports_path, title: _("Abuse Reports") do
= link_to admin_abuse_reports_path do
.nav-icon-container
= custom_icon('abuse_reports')
%span.nav-item-name
@ -138,7 +138,7 @@
- if akismet_enabled?
= nav_link(controller: :spam_logs) do
= sidebar_link admin_spam_logs_path, title: _("Spam Logs") do
= link_to admin_spam_logs_path do
.nav-icon-container
= custom_icon('spam_logs')
%span.nav-item-name
@ -150,7 +150,7 @@
#{ _('Spam Logs') }
= nav_link(controller: :deploy_keys) do
= sidebar_link admin_deploy_keys_path, title: _('Deploy Keys') do
= link_to admin_deploy_keys_path do
.nav-icon-container
= custom_icon('key')
%span.nav-item-name
@ -162,7 +162,7 @@
#{ _('Deploy Keys') }
= nav_link(controller: :services) do
= sidebar_link admin_application_settings_services_path, title: _('Service Templates') do
= link_to admin_application_settings_services_path do
.nav-icon-container
= custom_icon('service_templates')
%span.nav-item-name
@ -174,7 +174,7 @@
#{ _('Service Templates') }
= nav_link(controller: :labels) do
= sidebar_link admin_labels_path, title: _('Labels') do
= link_to admin_labels_path do
.nav-icon-container
= custom_icon('labels')
%span.nav-item-name
@ -186,7 +186,7 @@
#{ _('Labels') }
= nav_link(controller: :appearances) do
= sidebar_link admin_appearances_path, title: _('Appearances') do
= link_to admin_appearances_path do
.nav-icon-container
= custom_icon('appearance')
%span.nav-item-name
@ -198,7 +198,7 @@
#{ _('Appearance') }
= nav_link(controller: :application_settings) do
= sidebar_link admin_application_settings_path, title: _('Settings') do
= link_to admin_application_settings_path do
.nav-icon-container
= custom_icon('settings')
%span.nav-item-name

View File

@ -11,7 +11,7 @@
= @group.name
%ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
= sidebar_link group_path(@group), title: _('Group overview') do
= link_to group_path(@group) do
.nav-icon-container
= custom_icon('project')
%span.nav-item-name
@ -34,7 +34,7 @@
Activity
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= sidebar_link issues_group_path(@group), title: _('Issues') do
= link_to issues_group_path(@group) do
.nav-icon-container
= custom_icon('issues')
%span.nav-item-name
@ -64,7 +64,7 @@
Milestones
= nav_link(path: 'groups#merge_requests') do
= sidebar_link merge_requests_group_path(@group), title: _('Merge Requests') do
= link_to merge_requests_group_path(@group) do
.nav-icon-container
= custom_icon('mr_bold')
%span.nav-item-name
@ -77,19 +77,19 @@
#{ _('Merge Requests') }
%span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count)
= nav_link(path: 'group_members#index') do
= sidebar_link group_group_members_path(@group), title: _('Members') do
= link_to group_group_members_path(@group) do
.nav-icon-container
= custom_icon('members')
%span.nav-item-name
Members
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do
= link_to merge_requests_group_path(@group) do
= link_to group_group_members_path(@group) do
%strong.fly-out-top-item-name
#{ _('Members') }
- if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
= sidebar_link edit_group_path(@group), title: _('Settings') do
= link_to edit_group_path(@group) do
.nav-icon-container
= custom_icon('settings')
%span.nav-item-name

View File

@ -7,7 +7,7 @@
.sidebar-context-title User Settings
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= sidebar_link profile_path, title: _('Profile Settings') do
= link_to profile_path do
.nav-icon-container
= custom_icon('profile')
%span.nav-item-name
@ -18,7 +18,7 @@
%strong.fly-out-top-item-name
#{ _('Profile') }
= nav_link(controller: [:accounts, :two_factor_auths]) do
= sidebar_link profile_account_path, title: _('Account') do
= link_to profile_account_path do
.nav-icon-container
= custom_icon('account')
%span.nav-item-name
@ -30,7 +30,7 @@
#{ _('Account') }
- if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do
= sidebar_link applications_profile_path, title: _('Applications') do
= link_to applications_profile_path do
.nav-icon-container
= custom_icon('applications')
%span.nav-item-name
@ -41,7 +41,7 @@
%strong.fly-out-top-item-name
#{ _('Applications') }
= nav_link(controller: :chat_names) do
= sidebar_link profile_chat_names_path, title: _('Chat') do
= link_to profile_chat_names_path do
.nav-icon-container
= custom_icon('chat')
%span.nav-item-name
@ -52,7 +52,7 @@
%strong.fly-out-top-item-name
#{ _('Chat') }
= nav_link(controller: :personal_access_tokens) do
= sidebar_link profile_personal_access_tokens_path, title: _('Access Tokens') do
= link_to profile_personal_access_tokens_path do
.nav-icon-container
= custom_icon('access_tokens')
%span.nav-item-name
@ -63,7 +63,7 @@
%strong.fly-out-top-item-name
#{ _('Access Tokens') }
= nav_link(controller: :emails) do
= sidebar_link profile_emails_path, title: _('Emails') do
= link_to profile_emails_path do
.nav-icon-container
= custom_icon('emails')
%span.nav-item-name
@ -75,7 +75,7 @@
#{ _('Emails') }
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= sidebar_link edit_profile_password_path, title: _('Password') do
= link_to edit_profile_password_path do
.nav-icon-container
= custom_icon('lock')
%span.nav-item-name
@ -86,7 +86,7 @@
%strong.fly-out-top-item-name
#{ _('Password') }
= nav_link(controller: :notifications) do
= sidebar_link profile_notifications_path, title: _('Notifications') do
= link_to profile_notifications_path do
.nav-icon-container
= custom_icon('notifications')
%span.nav-item-name
@ -97,7 +97,7 @@
%strong.fly-out-top-item-name
#{ _('Notifications') }
= nav_link(controller: :keys) do
= sidebar_link profile_keys_path, title: _('SSH Keys') do
= link_to profile_keys_path do
.nav-icon-container
= custom_icon('key')
%span.nav-item-name
@ -108,7 +108,7 @@
%strong.fly-out-top-item-name
#{ _('SSH Keys') }
= nav_link(controller: :gpg_keys) do
= sidebar_link profile_gpg_keys_path, title: _('GPG Keys') do
= link_to profile_gpg_keys_path do
.nav-icon-container
= custom_icon('key_2')
%span.nav-item-name
@ -119,7 +119,7 @@
%strong.fly-out-top-item-name
#{ _('GPG Keys') }
= nav_link(controller: :preferences) do
= sidebar_link profile_preferences_path, title: _('Preferences') do
= link_to profile_preferences_path do
.nav-icon-container
= custom_icon('preferences')
%span.nav-item-name
@ -130,7 +130,7 @@
%strong.fly-out-top-item-name
#{ _('Preferences') }
= nav_link(path: 'profiles#audit_log') do
= sidebar_link audit_log_profile_path, title: _('Authentication log') do
= link_to audit_log_profile_path do
.nav-icon-container
= custom_icon('authentication_log')
%span.nav-item-name

View File

@ -9,7 +9,7 @@
= @project.name
%ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
= sidebar_link project_path(@project), title: _('Project overview'), css: 'shortcuts-project' do
= link_to project_path(@project), class: 'shortcuts-project' do
.nav-icon-container
= custom_icon('project')
%span.nav-item-name
@ -36,7 +36,7 @@
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
= sidebar_link project_tree_path(@project), title: _('Repository'), css: 'shortcuts-tree' do
= link_to project_tree_path(@project), class: 'shortcuts-tree' do
.nav-icon-container
= custom_icon('doc_text')
%span.nav-item-name
@ -82,7 +82,7 @@
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
= sidebar_link project_container_registry_index_path(@project), title: _('Container Registry'), css: 'shortcuts-container-registry' do
= link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
.nav-icon-container
= custom_icon('container_registry')
%span.nav-item-name
@ -90,7 +90,7 @@
- if project_nav_tab? :issues
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
= sidebar_link project_issues_path(@project), title: _('Issues'), css: 'shortcuts-issues' do
= link_to project_issues_path(@project), class: 'shortcuts-issues' do
.nav-icon-container
= custom_icon('issues')
%span.nav-item-name
@ -114,9 +114,9 @@
List
= nav_link(controller: :boards) do
= link_to project_boards_path(@project), title: 'Board' do
= link_to project_boards_path(@project), title: boards_link_text do
%span
Board
= boards_link_text
.feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } }
.feature-highlight-popover-content
= render 'feature_highlight/issue_boards.svg'
@ -144,7 +144,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
= sidebar_link project_merge_requests_path(@project), title: _('Merge Requests'), css: 'shortcuts-merge_requests' do
= link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do
.nav-icon-container
= custom_icon('mr_bold')
%span.nav-item-name
@ -161,7 +161,7 @@
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
= sidebar_link project_pipelines_path(@project), title: _('CI / CD'), css: 'shortcuts-pipelines' do
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do
.nav-icon-container
= custom_icon('pipeline')
%span.nav-item-name
@ -205,7 +205,7 @@
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= sidebar_link get_project_wiki_path(@project), title: _('Wiki'), css: 'shortcuts-wiki' do
= link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do
.nav-icon-container
= custom_icon('wiki')
%span.nav-item-name
@ -218,7 +218,7 @@
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= sidebar_link project_snippets_path(@project), title: _('Snippets'), css: 'shortcuts-snippets' do
= link_to project_snippets_path(@project), class: 'shortcuts-snippets' do
.nav-icon-container
= custom_icon('snippets')
%span.nav-item-name
@ -231,7 +231,7 @@
- if project_nav_tab? :settings
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
= sidebar_link edit_project_path(@project), title: _('Settings'), css: 'shortcuts-tree' do
= link_to edit_project_path(@project), class: 'shortcuts-tree' do
.nav-icon-container
= custom_icon('settings')
%span.nav-item-name

View File

@ -17,7 +17,7 @@
.preview-row
.quadrant.three
.quadrant.four
= f.radio_button :theme_id, theme.id
= f.radio_button :theme_id, theme.id, checked: Gitlab::Themes.for_user(@user).id == theme.id
= theme.name
.col-sm-12

View File

@ -1,5 +1,5 @@
.file-content.blob_file.blob-no-preview
.center
.center.render-error.vertical-center
= link_to blob_raw_path do
%h1.light
= icon('download')

View File

@ -43,7 +43,8 @@
data: { toggle: "modal",
target: "#modal-delete-branch",
delete_path: project_branch_path(@project, branch.name),
branch_name: branch.name } }
branch_name: branch.name,
is_merged: ("true" if @repository.merged_to_root_ref?(branch.name)) } }
= icon("trash-o")
- else
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",

View File

@ -6,13 +6,18 @@
%h3.page-title
Delete protected branch
= surround "'", "'?" do
%span.js-branch-name>[branch name]
%span.js-branch-name.ref-name>[branch name]
.modal-body
%p
Youre about to permanently delete the protected branch
= succeed '.' do
%strong.js-branch-name [branch name]
%strong.js-branch-name.ref-name [branch name]
%p.js-not-merged
- default_branch = capture do
%span.ref-name= @repository.root_ref
= s_("Branches|This branch hasnt been merged into %{default_branch}.").html_safe % { default_branch: default_branch }
= s_("Branches|To avoid data loss, consider merging this branch before deleting it.")
%p
Once you confirm and press
= succeed ',' do

View File

@ -1,4 +1,4 @@
- pipeline = local_assigns.fetch(:pipeline) { project.pipelines.latest_successful_for(ref) }
- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) }
- if !project.empty_repo? && can?(current_user, :download_code, project)
.project-action-button.dropdown.inline>
@ -26,18 +26,16 @@
%i.fa.fa-download
%span= _('Download tar')
- if pipeline
- artifacts = pipeline.builds.latest.with_artifacts
- if artifacts.any?
%li.dropdown-header Artifacts
- unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref)
%li
.unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
= link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span
#{ s_('DownloadArtifacts|Download') } '#{job.name}'
- if pipeline && pipeline.latest_builds_with_artifacts.any?
%li.dropdown-header Artifacts
- unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref)
%li
.unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts
- pipeline.latest_builds_with_artifacts.each do |job|
%li
= link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span
#{s_('DownloadArtifacts|Download')} '#{job.name}'

View File

@ -2,22 +2,22 @@
.clearfix
- if params[:to] && params[:from]
.compare-switch-container
= link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Switch base of comparison'}
.form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
.input-group.inline-input-group
%span.input-group-addon from
= hidden_field_tag :from, params[:from]
= button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag'
= render 'shared/ref_dropdown'
.compare-ellipsis.inline ...
= link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions'
.form-group.dropdown.compare-form-group.to.js-compare-to-dropdown
.input-group.inline-input-group
%span.input-group-addon to
%span.input-group-addon Source
= hidden_field_tag :to, params[:to]
= button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
.dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag'
= render 'shared/ref_dropdown'
.compare-ellipsis.inline ...
.form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
.input-group.inline-input-group
%span.input-group-addon Target
= hidden_field_tag :from, params[:from]
= button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag'
= render 'shared/ref_dropdown'
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present?

View File

@ -7,13 +7,19 @@
.sub-header-block
Compare Git revisions.
%br
Fill input field with commit SHA like
%code.ref-name 4eedf23
or branch/tag name like
%code.ref-name master
and press compare button for the commits list and a code diff.
Choose a branch/tag (e.g.
= succeed ')' do
%code.ref-name master
or enter a commit SHA (e.g.
= succeed ')' do
%code.ref-name 4eedf23
to see what's changed or to create a merge request.
%br
Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field.
Changes are shown as if the
%b source
revision was being merged into the
%b target
revision.
.prepend-top-20
= render "form"

View File

@ -21,9 +21,9 @@
%ul
- diff_files.each do |diff_file|
%li
%a{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path }
%a.diff-changed-file{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path }
= icon("#{diff_file_changed_icon(diff_file)} fw", class: "#{diff_file_changed_icon_color(diff_file)} append-right-5")
%span.diff-file-changes-path= diff_file.new_path
%span.diff-file-changes-path.append-right-5= diff_file.new_path
.pull-right
%span.cgreen<
+#{diff_file.added_lines}

View File

@ -16,7 +16,9 @@
New project
- if import_sources_enabled?
%p
Create or Import your project from popular Git services
A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}.
%p
All features are enabled when you create a project, but you can disable the ones you dont need in the project settings.
.col-lg-9.js-toggle-container
= form_for @project, html: { class: 'new_project' } do |f|
.create-project-options

View File

@ -11,7 +11,7 @@
.col-sm-10
.checkbox
= f.check_box :access_level, {}, 'ref_protected', 'not_protected'
%span.light This runner will only run on pipelines trigged on protected branches
%span.light This runner will only run on pipelines triggered on protected branches
.form-group
= label :run_untagged, 'Run untagged jobs', class: 'control-label'
.col-sm-10

View File

@ -11,13 +11,13 @@
- if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user.try(:username), current_user: true, project_id: @project.try(:id), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id])
= dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
= render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true

View File

@ -24,9 +24,9 @@
.block.milestone
.sidebar-collapsed-icon
= icon('clock-o', 'aria-hidden': 'true')
%span
%span.milestone-title
- if issuable.milestone
%span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } }
%span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_remaining_days(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } }
= issuable.milestone.title
- else
None

View File

@ -26,6 +26,6 @@
%span.assignee-icon
- assignees.each do |assignee|
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }),
= link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }),
class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- image_tag(avatar_icon(assignee, 16), class: "avatar s16", alt: '')

View File

@ -0,0 +1,5 @@
---
title: Return only group's members in user dropdowns on issuables list pages
merge_request: 14249
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Make the labels in the Compare form less confusing
merge_request: 14225
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Extract AutocompleteController#users into finder
merge_request: 13778
author: Maxim Rydkin, Mayra Cabrera
type: other

View File

@ -0,0 +1,5 @@
---
title: Optimize the boards' issues fetching.
merge_request: 14198
author:
type: other

View File

@ -0,0 +1,4 @@
---
title: creation of keys moved to services
merge_request: 13331
author: haseebeqx

View File

@ -0,0 +1,5 @@
---
title: Truncate milestone title if sidebar is collapsed
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix the diff file header from being html escaped for renamed files.
merge_request: 14121
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix mini graph pipeline breakin in merge request view
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Allow using newlines in pipeline email service recipients
merge_request: 14250
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: changed dashed border button color to be darker
merge_request: !14041
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Constrain environment deployments to project IDs
merge_request:
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: "Disallow NULL values for environments.project_id"
merge_request:
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix docs for lightweight tag creation via API
merge_request:
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Fixed the sidebar scrollbar overlapping links
merge_request:
author:
type: fixed

View File

@ -0,0 +1,6 @@
---
title: Fix project feature being deleted when updating project with invalid visibility
level
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: "Memoize the latest builds of a pipeline on a project's homepage"
merge_request:
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Fixed milestone issuable assignee link URL
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Memoize pipelines for project download buttons
merge_request:
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Remove animate.js and label animation.
merge_request:
author:
type: removed

View File

@ -0,0 +1,5 @@
---
title: Replace the 'project/archived.feature' spinach test with an rspec analog
merge_request: 14322
author: Vitaliy @blackst0ne Klachkov
type: other

View File

@ -0,0 +1,5 @@
---
title: Replace the 'project/commits/revert.feature' spinach test with an rspec analog
merge_request: 14325
author: Vitaliy @blackst0ne Klachkov
type: other

View File

@ -0,0 +1,5 @@
---
title: Replace the 'project/snippets.feature' spinach test with an rspec analog
merge_request: 14326
author: Vitaliy @blackst0ne Klachkov
type: other

Some files were not shown because too many files have changed in this diff Show More