Merge branch 'master' into go-go-gadget-webpack

* master: (83 commits)
  Fix small typo on new branch button spec
  Abillity to promote project labels to group labels
  Update and pin the `jwt` gem to ~> 1.5.6
  refactor merge request build service
  Update index.md
  Clarify that Auto Deploy requires a public project.
  cop for gem fetched from a git source
  Add CHANGELOG entry
  Remove new branch button for confidential issues
  Remove flash warning from login page
  add complete changelog
  Add project ID index to `project_authorizations` table to optimize queries
  Fix disable storing of sensitive information when importing a new repo
  Fix notification when global=disabled, group=watch
  Ensure rake is called within the correct bundle context
  Fix 8.16.0 release date.
  Improve search within group logic
  Add changelog item about new attributes in group api
  Update group api doc with full_name and full_path attributes
  Add tests for nested groups in search service and search helper
  ...
This commit is contained in:
Mike Greiling 2017-01-31 14:38:45 -06:00
commit 5fcbe12730
176 changed files with 1837 additions and 858 deletions

View File

@ -273,7 +273,7 @@ rake db:migrate:reset:
<<: *use-db
<<: *dedicated-runner
script:
- rake db:migrate:reset
- bundle exec rake db:migrate:reset
rake db:seed_fu:
stage: test
@ -303,7 +303,7 @@ karma:
<<: *dedicated-runner
script:
- npm link istanbul
- rake karma
- bundle exec rake karma
artifacts:
name: coverage-javascript
expire_in: 31d
@ -354,10 +354,10 @@ migration paths:
- cp config/resque.yml.example config/resque.yml
- sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- rake db:drop db:create db:schema:load db:seed_fu
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF
- source scripts/prepare_build.sh
- rake db:migrate
- bundle exec rake db:migrate
coverage:
stage: post-test

View File

@ -18,7 +18,7 @@ entry.
- Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2.
## 8.16.0 (2017-02-22)
## 8.16.0 (2017-01-22)
- Add LDAP Rake task to rename a provider. !2181
- Validate label's title length. !5767 (Tomáš Kukrál)

View File

@ -35,7 +35,7 @@ gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.2.0'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt'
gem 'jwt', '~> 1.5.6'
# Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'

View File

@ -375,7 +375,7 @@ GEM
json (1.8.3)
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.4)
jwt (1.5.6)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
@ -900,7 +900,7 @@ DEPENDENCIES
jquery-rails (~> 4.1.0)
jquery-ui-rails (~> 5.0.0)
json-schema (~> 2.6.2)
jwt
jwt (~> 1.5.6)
kaminari (~> 0.17.0)
knapsack (~> 1.11.0)
kubeclient (~> 2.2.0)
@ -1007,4 +1007,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.13.7
1.14.2

View File

@ -12,106 +12,54 @@ etc.).
## Common actions
### Issue team
### Issue triaging
- Looks for issues without [workflow labels](#how-we-handle-issues) and triages
issue
- Closes invalid issues with a comment (duplicates,
[fixed in newer version](#issue-fixed-in-newer-version),
[issue report for old version](#issue-report-for-old-version), not a problem
in GitLab, etc.)
- Asks for feedback from issue reporter
([invalid issue reports](#improperly-formatted-issue),
[format code](#code-format), etc.)
- Monitors all issues for feedback (but especially ones commented on since
automatically watching them)
- Closes issues with no feedback from the reporter for two weeks
### Merge marshall & merge request coach
- Responds to merge requests the issue team mentions them in and monitors for
new merge requests
- Provides feedback to the merge request submitter to improve the merge request
(style, tests, etc.)
- Mark merge requests `Ready for Merge` when they meet the
[contribution acceptance criteria]
- Mention developer(s) based on the
[list of members and their specialities][team]
- Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team
1. Mentioning people (critical)
1. Workflow labels (normal)
1. Functional labels (minor)
1. Assigning issues (avoid if possible)
## Mentioning people
Our issue triage policies are [described in our handbook]. You are very welcome
to help the GitLab team triage issues. We also organize [issue bash events] once
every quarter.
The most important thing is making sure valid issues receive feedback from the
development team. Therefore the priority is mentioning developers that can help
on those issues. Please select someone with relevant experience from
[GitLab core team][core-team]. If there is nobody mentioned with that expertise
[GitLab team][team]. If there is nobody mentioned with that expertise
look in the commit history for the affected files to find someone. Avoid
mentioning the lead developer, this is the person that is least likely to give a
timely response. If the involvement of the lead developer is needed the other
core team members will mention this person.
[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/
[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
### Merge request coaching
Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done].
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
## Workflow labels
Workflow labels are purposely not very detailed since that would be hard to keep
updated as you would need to re-evaluate them after every comment. We optionally
use functional labels on demand when we want to group related issues to get an
overview (for example all issues related to RVM, to tackle them in one go) and
to add details to the issue.
Labelling issues is described in the [GitLab Inc engineering workflow].
- ~"Awaiting Feedback" Feedback pending from the reporter
- ~UX needs help from a UX designer
- ~Frontend needs help from a Front-end engineer. Please follow the
["Implement design & UI elements" guidelines].
- ~"Accepting Merge Requests" is a low priority, well-defined issue that we
encourage people to contribute to. Not exclusive with other labels.
- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
in support or comment for further detail. Do not use `feature request`.
- ~bug is an issue reporting undesirable or incorrect behavior.
- ~customer is an issue reported by enterprise subscribers. This label should
be accompanied by *bug* or *feature proposal* labels.
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
## Functional labels
These labels describe what development specialities are involved such as: `CI`,
`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
`Release`, `Repository`, `UX`.
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
## Assigning issues
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
## Label colors
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting
feedback, awaiting confirmation of fix)
- Bright orange `#eb6420`: workflow labels for core team members (attached MR,
awaiting developer action/feedback)
- Light blue `#82C5FF`: functional labels
- Green labels `#009800`: issues that can generally be ignored. For example,
issues given the following labels normally can be closed immediately:
- Support (see copy & paste response:
[Support requests and configuration questions](#support-requests-and-configuration-questions)
## Be kind
Be kind to people trying to contribute. Be aware that people may be a non-native
English speaker, they might not understand things or they might be very
sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide].
star, smile, etc.). Some good tips about code reviews can be found in our
[Code Review Guidelines].
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
## Feature Freeze
5 working days before the 22nd the stable branches for the upcoming release will
On the 7th of each month, the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of
@ -120,10 +68,9 @@ things.
What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the
decision will be made by those reviewing a merge request and the release
manager.
decision will be made by the maintainers and the release managers.
During the feature freeze all merge requests that are meant to go into the next
During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches.
@ -189,7 +136,6 @@ prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is
still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues).
[core-team]: https://about.gitlab.com/core-team/
[team]: https://about.gitlab.com/team/
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements

View File

@ -2,7 +2,6 @@
/* global autosize */
var autosize = require('vendor/autosize');
require('vendor/jquery.ba-resize');
(function() {
$(function() {

View File

@ -29,6 +29,12 @@
watch: {
detail: {
handler () {
if (this.issue.id !== this.detail.issue.id) {
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el).data('glDropdown').clearMenu();
});
}
this.issue = this.detail.issue;
},
deep: true

View File

@ -8,7 +8,6 @@
/* global ShortcutsIssuable */
/* global ZenMode */
/* global Milestone */
/* global GLForm */
/* global IssuableForm */
/* global LabelsSelect */
/* global MilestoneSelect */
@ -64,17 +63,6 @@
new UsernameValidator();
new ActiveTabMemoizer();
break;
case 'sessions:create':
if (!gon.u2f) break;
window.gl.u2fAuthenticate = new gl.U2FAuthenticate(
$("#js-authenticate-u2f"),
'#js-login-u2f-form',
gon.u2f,
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'),
);
window.gl.u2fAuthenticate.start();
break;
case 'projects:boards:show':
case 'projects:boards:index':
shortcut_handler = new ShortcutsNavigation();
@ -110,7 +98,7 @@
case 'projects:milestones:edit':
new ZenMode();
new gl.DueDateSelectors();
new GLForm($('.milestone-form'));
new gl.GLForm($('.milestone-form'));
break;
case 'groups:milestones:new':
new ZenMode();
@ -121,7 +109,7 @@
case 'projects:issues:new':
case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation();
new GLForm($('.issue-form'));
new gl.GLForm($('.issue-form'));
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
@ -131,7 +119,7 @@
case 'projects:merge_requests:edit':
new gl.Diff();
shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form'));
new gl.GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
@ -139,11 +127,11 @@
break;
case 'projects:tags:new':
new ZenMode();
new GLForm($('.tag-form'));
new gl.GLForm($('.tag-form'));
break;
case 'projects:releases:edit':
new ZenMode();
new GLForm($('.release-form'));
new gl.GLForm($('.release-form'));
break;
case 'projects:merge_requests:show':
new gl.Diff();
@ -280,6 +268,17 @@
break;
}
switch (path.first()) {
case 'sessions':
case 'omniauth_callbacks':
if (!gon.u2f) break;
gl.u2fAuthenticate = new gl.U2FAuthenticate(
$('#js-authenticate-u2f'),
'#js-login-u2f-form',
gon.u2f,
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'),
);
gl.u2fAuthenticate.start();
case 'admin':
new Admin();
switch (path[1]) {
@ -332,7 +331,7 @@
new gl.Wikis();
shortcut_handler = new ShortcutsNavigation();
new ZenMode();
new GLForm($('.wiki-form'));
new gl.GLForm($('.wiki-form'));
break;
case 'snippets':
shortcut_handler = new ShortcutsNavigation();
@ -357,7 +356,7 @@
}
// If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) {
return new Shortcuts();
new Shortcuts();
}
};

View File

@ -512,12 +512,17 @@
// Append the menu into the dropdown
GitLabDropdown.prototype.appendMenu = function(html) {
return this.clearMenu().append(html);
};
GitLabDropdown.prototype.clearMenu = function() {
var selector;
selector = '.dropdown-content';
if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one .dropdown-content";
}
return $(selector, this.dropdown).empty().append(html);
return $(selector, this.dropdown).empty();
};
GitLabDropdown.prototype.renderItem = function(data, group, index) {

View File

@ -1,64 +0,0 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
/* global GitLab */
/* global DropzoneInput */
/* global autosize */
var autosize = require('vendor/autosize');
(function() {
this.GLForm = (function() {
function GLForm(form) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
this.setupForm();
this.form.data('gl-form', this);
}
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
return this.form.data('gl-form', null);
};
GLForm.prototype.setupForm = function() {
var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form);
autosize(this.textarea);
// form and textarea event listeners
this.addEventListeners();
}
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
return this.form.show();
};
GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus');
this.textarea.off('blur');
return gl.text.removeListeners(this.form);
};
GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused');
});
return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused');
});
};
return GLForm;
})();
}).call(this);

View File

@ -0,0 +1,92 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
/* global GitLab */
/* global DropzoneInput */
/* global autosize */
(() => {
const global = window.gl || (window.gl = {});
function GLForm(form) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
this.setupForm();
this.form.data('gl-form', this);
}
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
return this.form.data('gl-form', null);
};
GLForm.prototype.setupForm = function() {
var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
if (isNewForm) {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form);
autosize(this.textarea);
// form and textarea event listeners
this.addEventListeners();
}
gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
};
GLForm.prototype.setupAutosize = function () {
this.textarea.off('autosize:resized')
.on('autosize:resized', this.setHeightData.bind(this));
this.textarea.off('mouseup.autosize')
.on('mouseup.autosize', this.destroyAutosize.bind(this));
setTimeout(() => {
autosize(this.textarea);
this.textarea.css('resize', 'vertical');
}, 0);
};
GLForm.prototype.setHeightData = function () {
this.textarea.data('height', this.textarea.outerHeight());
};
GLForm.prototype.destroyAutosize = function () {
const outerHeight = this.textarea.outerHeight();
if (this.textarea.data('height') === outerHeight) return;
autosize.destroy(this.textarea);
this.textarea.data('height', outerHeight);
this.textarea.outerHeight(outerHeight);
this.textarea.css('max-height', window.outerHeight);
};
GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus');
this.textarea.off('blur');
return gl.text.removeListeners(this.form);
};
GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused');
});
return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused');
});
};
global.GLForm = GLForm;
})();

View File

@ -59,11 +59,11 @@
} else {
avatar = gon.default_avatar_url;
}
return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>";
return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>";
};
GroupsSelect.prototype.formatSelection = function(group) {
return group.name;
return group.full_name;
};
return GroupsSelect;

View File

@ -8,6 +8,7 @@
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.prioritizedLabels.sortable({
items: 'li',
placeholder: 'list-placeholder',
@ -29,7 +30,12 @@
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
$tooltip.tooltip('destroy');
return _this.toggleLabelPriority($label, action);
_this.toggleLabelPriority($label, action);
_this.toggleEmptyState($label, $btn, action);
}
toggleEmptyState($label, $btn, action) {
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
}
toggleLabelPriority($label, action, persistState) {

View File

@ -1,6 +1,5 @@
/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */
/* global Flash */
/* global GLForm */
/* global Autosave */
/* global ResolveService */
/* global mrRefreshWidgetUrl */
@ -421,7 +420,7 @@ require('vendor/task_list');
Notes.prototype.setupNoteForm = function(form) {
var textarea;
new GLForm(form);
new gl.GLForm(form);
textarea = form.find(".js-note-text");
return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
};
@ -885,7 +884,7 @@ require('vendor/task_list');
var targetId = $originalContentEl.data('target-id');
var targetType = $originalContentEl.data('target-type');
new GLForm($editForm.find('form'));
new gl.GLForm($editForm.find('form'));
$editForm.find('form')
.attr('action', postUrl)

View File

@ -13,12 +13,12 @@
filterable: true,
fieldName: 'group_id',
search: {
fields: ['name']
fields: ['full_name']
},
data: function(term, callback) {
return Api.groups(term, {}, function(data) {
data.unshift({
name: 'Any'
full_name: 'Any'
});
data.splice(1, 0, 'divider');
return callback(data);
@ -28,10 +28,10 @@
return obj.id;
},
text: function(obj) {
return obj.name;
return obj.full_name;
},
toggleLabel: function(obj) {
return ($groupDropdown.data('default-label')) + " " + obj.name;
return ($groupDropdown.data('default-label')) + " " + obj.full_name;
},
clicked: (function(_this) {
return function() {

View File

@ -85,7 +85,7 @@
},
success: (data) => {
$target.remove();
$('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
$('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
return this.updateBadges(data);
}
});

View File

@ -278,6 +278,10 @@
display: inline-block;
}
.btn {
margin: $btn-side-margin $btn-side-margin 0 0;
}
@media(max-width: $screen-xs-max) {
margin-top: 50px;
text-align: center;
@ -286,6 +290,12 @@
width: 100%;
}
}
@media(min-width: $screen-xs-max) {
&.labels .text-content {
margin-top: 70px;
}
}
}
.flex-container-block {

View File

@ -162,6 +162,10 @@
}
}
}
&.panel-without-border {
border: 0;
}
}
.panel-succes .panel-heading,

View File

@ -494,31 +494,27 @@
// Action Icons in big pipeline-graph nodes
> .ci-action-icon-container .ci-action-icon-wrapper {
i {
color: $border-color;
border-radius: 100%;
border: 1px solid $border-color;
padding: 5px 6px;
font-size: 13px;
background: $white-light;
height: 30px;
width: 30px;
height: 30px;
width: 30px;
background: $white-light;
border: 1px solid $border-color;
border-radius: 100%;
display: block;
&::before {
position: relative;
top: 3px;
left: 3px;
}
&:hover {
color: $gl-text-color;
background-color: $stage-hover-bg;
border: 1px solid $stage-hover-bg;
}
&:hover {
background-color: $stage-hover-bg;
border: 1px solid $stage-hover-bg;
}
.ci-play-icon {
padding: 5px 5px 5px 7px;
svg {
fill: $border-color;
position: relative;
left: -1px;
top: -1px;
}
&:hover svg {
fill: $gl-text-color;
}
}
@ -657,7 +653,7 @@
font-weight: 100;
font-size: 15px;
position: absolute;
right: 5px;
right: 13px;
top: 8px;
}
@ -825,11 +821,23 @@
&:hover,
&:focus {
text-decoration: none;
color: $gl-text-color;
background-color: $stage-hover-bg;
border: 1px solid transparent;
}
svg {
width: 22px;
height: 22px;
left: -6px;
position: relative;
top: -3px;
fill: $action-icon-color;
}
&:hover svg,
&:focus svg {
fill: $gl-text-color;
}
}
// link to the build

View File

@ -76,6 +76,10 @@
font-size: 14px;
}
.action-name {
font-weight: normal;
}
.todo-body {
.todo-note {
word-wrap: break-word;

View File

@ -4,6 +4,7 @@ class DashboardController < Dashboard::ApplicationController
before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests]
before_action :set_show_full_reference, only: [:issues, :merge_requests]
respond_to :html
@ -34,4 +35,8 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
def set_show_full_reference
@show_full_reference = true
end
end

View File

@ -30,6 +30,17 @@ class Projects::CommitController < Projects::ApplicationController
end
def pipelines
@pipelines = @commit.pipelines.order(id: :desc)
respond_to do |format|
format.html
format.json do
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines)
end
end
end
def branches

View File

@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController
include ToggleSubscriptionAction
before_action :module_enabled
before_action :label, only: [:edit, :update, :destroy]
before_action :label, only: [:edit, :update, :destroy, :promote]
before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription]
before_action :authorize_read_label!
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
:generate, :destroy, :remove_priority,
:set_priorities]
before_action :authorize_admin_group!, only: [:promote]
respond_to :js, :html
@ -71,13 +72,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label.destroy
@labels = find_labels
respond_to do |format|
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
notice: 'Label was removed')
end
format.js
end
redirect_to(namespace_project_labels_path(@project.namespace, @project), notice: 'Label was removed')
end
def remove_priority
@ -108,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController
end
end
def promote
promote_service = Labels::PromoteService.new(@project, @current_user)
begin
return render_404 unless promote_service.execute(@label)
respond_to do |format|
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
notice: 'Label was promoted to a Group Label')
end
format.js
end
rescue ActiveRecord::RecordInvalid => e
Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label"
Gitlab::AppLogger.error e
respond_to do |format|
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
notice: 'Failed to promote label due to internal error. Please contact administrators.')
end
format.js
end
end
end
protected
def module_enabled
@ -135,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController
def authorize_admin_labels!
return render_404 unless can?(current_user, :admin_label, @project)
end
def authorize_admin_group!
return render_404 unless can?(current_user, :admin_group, @project.group)
end
end

View File

@ -34,7 +34,7 @@ class Projects::MattermostsController < Projects::ApplicationController
end
def teams
@teams ||= @service.list_teams(current_user)
@teams, @teams_error_message = @service.list_teams(current_user)
end
def service

View File

@ -214,7 +214,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render 'show'
end
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_pipelines') } }
format.json do
render json: {
html: view_to_html_string('projects/merge_requests/show/_pipelines'),
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
.with_pagination(request, response)
.represent(@pipelines)
}
end
end
end

View File

@ -7,6 +7,7 @@
# For users who haven't customized the setting, we simply delegate to
# `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController
skip_before_action :authenticate_user!, only: [:index]
before_action :redirect_to_custom_dashboard, only: [:index]
def index
@ -16,7 +17,7 @@ class RootController < Dashboard::ProjectsController
private
def redirect_to_custom_dashboard
return unless current_user
return redirect_to new_user_session_path unless current_user
case current_user.dashboard
when 'stars'

View File

@ -162,6 +162,10 @@ module IssuablesHelper
]
end
def issuable_reference(issuable)
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
end
def issuable_filter_present?
issuable_filter_params.any? { |k| params.key?(k) }
end

View File

@ -89,7 +89,7 @@ module SearchHelper
{
category: "Groups",
id: group.id,
label: "#{search_result_sanitize(group.name)}",
label: "#{search_result_sanitize(group.full_name)}",
url: group_path(group)
}
end

View File

@ -100,8 +100,8 @@ class Commit
commit_reference(from_project, id, full: full)
end
def reference_link_text(from_project = nil)
commit_reference(from_project, short_id)
def reference_link_text(from_project = nil, full: false)
commit_reference(from_project, short_id, full: full)
end
def diff_line_count

View File

@ -1,7 +1,8 @@
class Environment < ActiveRecord::Base
# Used to generate random suffixes for the slug
LETTERS = 'a'..'z'
NUMBERS = '0'..'9'
SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a
SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
belongs_to :project, required: true, validate: true
@ -148,17 +149,24 @@ class Environment < ActiveRecord::Base
slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
# Must start with a letter
slugified = "env-" + slugified if NUMBERS.cover?(slugified[0])
slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
# Repeated dashes are invalid (OpenShift limitation)
slugified.gsub!(/\-+/, '-')
# Maximum length: 24 characters (OpenShift limitation)
slugified = slugified[0..23]
# Cannot end with a "-" character (Kubernetes label limitation)
slugified = slugified[0..-2] if slugified[-1] == "-"
# Cannot end with a dash (Kubernetes label limitation)
slugified.chop! if slugified.end_with?('-')
# Add a random suffix, shortening the current string if necessary, if it
# has been slugified. This ensures uniqueness.
slugified = slugified[0..16] + "-" + random_suffix if slugified != name
if slugified != name
slugified = slugified[0..16]
slugified << '-' unless slugified.end_with?('-')
slugified << random_suffix
end
self.slug = slugified
end

View File

@ -97,10 +97,11 @@ class Issue < ActiveRecord::Base
end
end
def to_reference(from_project = nil, full: false)
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
"#{project.to_reference(from_project, full: full)}#{reference}"
"#{project.to_reference(from, full: full)}#{reference}"
end
def referenced_merge_requests(current_user = nil)

View File

@ -179,10 +179,11 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
def to_reference(from_project = nil, full: false)
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
"#{project.to_reference(from_project, full: full)}#{reference}"
"#{project.to_reference(from, full: full)}#{reference}"
end
def first_commit

View File

@ -225,6 +225,7 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") }
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
@ -591,10 +592,11 @@ class Project < ActiveRecord::Base
end
end
def to_reference(from_project = nil, full: false)
if full || cross_namespace_reference?(from_project)
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
if full || cross_namespace_reference?(from)
path_with_namespace
elsif cross_project_reference?(from_project)
elsif cross_project_reference?(from)
path
end
end
@ -1291,21 +1293,26 @@ class Project < ActiveRecord::Base
private
def cross_namespace_reference?(from)
case from
when Project
namespace != from.namespace
when Namespace
namespace != from
end
end
# Check if a reference is being done cross-project
#
# from_project - Refering Project object
def cross_project_reference?(from_project)
from_project && self != from_project
def cross_project_reference?(from)
return true if from.is_a?(Namespace)
from && self != from
end
def pushes_since_gc_redis_key
"projects/#{id}/pushes_since_gc"
end
def cross_namespace_reference?(from_project)
from_project && namespace != from_project.namespace
end
def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE

View File

@ -60,9 +60,9 @@ class JiraService < IssueTrackerService
end
def help
'You need to configure JIRA before enabling this service. For more details
"You need to configure JIRA before enabling this service. For more details
read the
[JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).'
[JIRA service documentation](#{help_page_url('project_services/jira')})."
end
def title

View File

@ -28,8 +28,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
[false, e.message]
end
def list_teams(user)
Mattermost::Team.new(user).all
def list_teams(current_user)
[Mattermost::Team.new(current_user).all, nil]
rescue Mattermost::Error => e
[[], e.message]
end

View File

@ -103,9 +103,9 @@ class Todo < ActiveRecord::Base
def target_reference
if for_commit?
target.short_id
target.reference_link_text(full: true)
else
target.to_reference
target.to_reference(full: true)
end
end

View File

@ -1,8 +1,6 @@
module Ci
class BuildPolicy < CommitStatusPolicy
def rules
can! :read_build if @subject.project.public_builds?
super
# If we can't read build we should also not have that

View File

@ -6,6 +6,7 @@ class BaseSerializer
def represent(resource, opts = {})
self.class.entity_class
.represent(resource, opts.merge(request: @request))
.as_json
end
def self.entity(entity_class)

View File

@ -1,9 +1,10 @@
class PipelineSerializer < BaseSerializer
entity PipelineEntity
class InvalidResourceError < StandardError; end
include API::Helpers::Pagination
Struct.new('Pagination', :request, :response)
entity PipelineEntity
def represent(resource, opts = {})
if paginated?
raise InvalidResourceError unless resource.respond_to?(:page)

View File

@ -0,0 +1,71 @@
module Labels
class PromoteService < BaseService
BATCH_SIZE = 1000
def execute(label)
return unless project.group &&
label.is_a?(ProjectLabel)
Label.transaction do
new_label = clone_label_to_group_label(label)
label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids|
update_issuables(new_label, batched_ids)
update_issue_board_lists(new_label, batched_ids)
update_priorities(new_label, batched_ids)
# Order is important, project labels need to be last
update_project_labels(batched_ids)
end
# We skipped validations during creation. Let's run them now, after deleting conflicting labels
raise ActiveRecord::RecordInvalid.new(new_label) unless new_label.valid?
new_label
end
end
private
def label_ids_for_merge(new_label)
LabelsFinder.
new(current_user, title: new_label.title, group_id: project.group.id).
execute(skip_authorization: true).
where.not(id: new_label).
select(:id) # Can't use pluck() to avoid object-creation because of the batching
end
def update_issuables(new_label, label_ids)
LabelLink.
where(label: label_ids).
update_all(label_id: new_label)
end
def update_issue_board_lists(new_label, label_ids)
List.
where(label: label_ids).
update_all(label_id: new_label)
end
def update_priorities(new_label, label_ids)
LabelPriority.
where(label: label_ids).
update_all(label_id: new_label)
end
def update_project_labels(label_ids)
Label.where(id: label_ids).delete_all
end
def clone_label_to_group_label(label)
params = label.attributes.slice('title', 'description', 'color')
# Since the title of the new label has to be the same as the previous labels
# and we're merging old labels in batches we'll skip validation to omit 2-step
# merge process and do it in one batch
# We'll be forcing validation at the end of the transaction to ensure everything
# was merged correctly
new_label = GroupLabel.new(params.merge(group: project.group))
new_label.save(validate: false)
new_label
end
end
end

View File

@ -1,62 +1,88 @@
module MergeRequests
class BuildService < MergeRequests::BaseService
def execute
merge_request = MergeRequest.new(params)
# Set MR attributes
merge_request.can_be_created = true
self.merge_request = MergeRequest.new(params)
merge_request.can_be_created = true
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
merge_request.source_project = find_source_project
merge_request.target_project = find_target_project
merge_request.target_branch = find_target_branch
merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
if branches_specified? && branches_valid?
compare_branches
assign_title_and_description
else
merge_request.can_be_created = false
end
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
messages = validate_branches(merge_request)
return build_failed(merge_request, messages) unless messages.empty?
compare = CompareService.new.execute(
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project,
merge_request.target_branch,
)
merge_request.compare_commits = compare.commits
merge_request.compare = compare
set_title_and_description(merge_request)
merge_request
end
private
def validate_branches(merge_request)
messages = []
attr_accessor :merge_request
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
messages <<
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
end
delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request
if merge_request.source_project == merge_request.target_project &&
merge_request.target_branch == merge_request.source_branch
def find_source_project
source_project || project
end
messages << 'You must select different branches'
end
def find_target_project
return target_project if target_project.present? && can?(current_user, :read_project, target_project)
project.forked_from_project || project
end
# See if source and target branches exist
if merge_request.source_branch.present? && !merge_request.source_project.commit(merge_request.source_branch)
messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
end
def find_target_branch
target_branch || target_project.default_branch
end
if merge_request.target_branch.present? && !merge_request.target_project.commit(merge_request.target_branch)
messages << "Target branch \"#{merge_request.target_branch}\" does not exist"
end
def branches_specified?
params[:source_branch] && params[:target_branch]
end
messages
def branches_valid?
validate_branches
errors.blank?
end
def compare_branches
compare = CompareService.new.execute(
source_project,
source_branch,
target_project,
target_branch
)
merge_request.compare_commits = compare.commits
merge_request.compare = compare
end
def validate_branches
add_error('You must select source and target branch') unless branches_present?
add_error('You must select different branches') if same_source_and_target?
add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists?
add_error("Target branch \"#{target_branch}\" does not exist") unless target_branch_exists?
end
def add_error(message)
errors.add(:base, message)
end
def branches_present?
target_branch.present? && source_branch.present?
end
def same_source_and_target?
source_project == target_project && target_branch == source_branch
end
def source_branch_exists?
source_branch.blank? || source_project.commit(source_branch)
end
def target_branch_exists?
target_branch.blank? || target_project.commit(target_branch)
end
# When your branch name starts with an iid followed by a dash this pattern will be
@ -71,17 +97,17 @@ module MergeRequests
# - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
# more than one commit in the MR
#
def set_title_and_description(merge_request)
if match = merge_request.source_branch.match(/\A(\d+)-/)
def assign_title_and_description
if match = source_branch.match(/\A(\d+)-/)
iid = match[1]
end
commits = merge_request.compare_commits
commits = compare_commits
if commits && commits.count == 1
commit = commits.first
merge_request.title = commit.title
merge_request.description ||= commit.description.try(:strip)
elsif iid && issue = merge_request.target_project.get_issue(iid, current_user)
elsif iid && issue = target_project.get_issue(iid, current_user)
case issue
when Issue
merge_request.title = "Resolve \"#{issue.title}\""
@ -89,31 +115,20 @@ module MergeRequests
merge_request.title = "Resolve #{issue.title}"
end
else
merge_request.title = merge_request.source_branch.titleize.humanize
merge_request.title = source_branch.titleize.humanize
end
if iid
closes_issue = "Closes ##{iid}"
if merge_request.description.present?
if description.present?
merge_request.description += closes_issue.prepend("\n\n")
else
merge_request.description = closes_issue
end
end
merge_request.title = merge_request.wip_title if commits.empty?
merge_request
end
def build_failed(merge_request, messages)
messages.compact.each do |message|
merge_request.errors.add(:base, message)
end
merge_request.compare_commits = []
merge_request.can_be_created = false
merge_request
merge_request.title = wip_title if commits.empty?
end
end
end

View File

@ -365,7 +365,7 @@ class NotificationService
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users)
users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users)
users_with_group_setting = select_group_member_setting(project.group, project_members, users_with_group_level_global, users)
User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
end
@ -415,8 +415,8 @@ class NotificationService
end
# Build a list of users based on group notification settings
def select_group_member_setting(project, project_members, global_setting, users_global_level_watch)
uids = notification_settings_for(project, :watch)
def select_group_member_setting(group, project_members, global_setting, users_global_level_watch)
uids = notification_settings_for(group, :watch)
# Group setting is watch, add to users list if user is not project member
users = []
@ -473,7 +473,7 @@ class NotificationService
setting = user.notification_settings_for(project)
if !setting && project.group
if project.group && (setting.nil? || setting.global?)
setting = user.notification_settings_for(project.group)
end

View File

@ -9,7 +9,10 @@ module Search
def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new.execute(current_user)
projects = projects.in_namespace(group.id) if group
if group
projects = projects.inside_path(group.full_path)
end
Gitlab::SearchResults.new(current_user, projects, params[:search])
end

View File

@ -16,4 +16,4 @@
- if status.has_action?
= link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do
= icon(status.action_icon, class: status.action_class)
= custom_icon(status.action_icon)

View File

@ -2,7 +2,7 @@
- subject = local_assigns.fetch(:subject)
- status = subject.detailed_status(current_user)
- klass = "ci-status-icon ci-status-icon-#{status.group}"
- klass = "ci-status-icon ci-status-icon-#{status.group} js-ci-status-icon-#{status.group}"
- tooltip = "#{subject.name} - #{status.label}"
- if status.has_details?
@ -16,5 +16,5 @@
- if status.has_action?
= link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do
%i.ci-action-icon-wrapper
= icon(status.action_icon, class: status.action_class)
%i.ci-action-icon-wrapper{ class: "js-#{status.action_icon.dasherize}" }
= custom_icon(status.action_icon)

View File

@ -15,6 +15,4 @@
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
.prepend-top-default
= render 'shared/issues'
= render 'shared/issues'

View File

@ -7,6 +7,4 @@
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
.prepend-top-default
= render 'shared/merge_requests'
= render 'shared/merge_requests'

View File

@ -11,8 +11,11 @@
= link_to_author(todo)
- else
(removed)
%span.todo-label
%span.action-name
= todo_action_name(todo)
%span.todo-label
- if todo.target
= todo_target_link(todo)
- else

View File

@ -67,21 +67,17 @@
= sort_title_oldest_created
.prepend-top-default
.js-todos-all
- if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group|
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
.panel.panel-default.panel-small.panel-without-border
%ul.content-list.todos-list
= render group[1]
= render @todos
= paginate @todos, theme: "gitlab"
- elsif current_user.todos.any?
.todos-all-done
= render "shared/empty_states/todos_all_done.svg"
= render "shared/empty_states/icons/todos_all_done.svg"
- if todos_filter_empty?
%h4.text-center
= Gitlab.config.gitlab.no_todos_messages.sample
@ -98,7 +94,7 @@
- else
.todos-empty
.todos-empty-hero
= render "shared/empty_states/todos_empty.svg"
= render "shared/empty_states/icons/todos_empty.svg"
.todos-empty-content
%h4
Todos let you see what you should do next.

View File

@ -23,7 +23,6 @@
- if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
.prepend-top-default
= render 'shared/issues'
= render 'shared/issues'
- else
= render 'shared/empty_states/issues', project_select_button: true

View File

@ -15,5 +15,4 @@
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
.prepend-top-default
= render 'shared/merge_requests'
= render 'shared/merge_requests'

View File

@ -1,6 +1,5 @@
- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_title 'Pipelines', "#{@commit.title} (#{@commit.short_id})", 'Commits'
= render "commit_box"
= render "ci_menu"
= render "pipelines_list", pipelines: @commit.pipelines.order(id: :desc)
= render 'commit_box'
= render 'ci_menu'
= render 'pipelines_list', pipelines: @pipelines

View File

@ -183,6 +183,8 @@
%li Build traces and artifacts
%li LFS objects
%li Container registry images
%li CI variables
%li Any encrypted tokens
%hr
- if can? current_user, :archive_project, @project
.row.prepend-top-default

View File

@ -34,7 +34,7 @@
= note_count
.issue-info
#{issue.to_reference} &middot;
#{issuable_reference(issue)} &middot;
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
by #{link_to_member(@project, issue.author, avatar: false)}
- if issue.milestone

View File

@ -75,7 +75,7 @@
// This element is filled in using JavaScript.
.content-block.content-block-small
= render 'new_branch'
= render 'new_branch' unless @issue.confidential?
= render 'award_emoji/awards_block', awardable: @issue, inline: true
%section.issuable-discussion

View File

@ -1,2 +0,0 @@
- if @labels.empty?
$('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)

View File

@ -3,37 +3,35 @@
- hide_class = ''
= render "projects/issues/head"
%div{ class: container_class }
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
- if @labels.exists? || @prioritized_labels.exists?
%div{ class: container_class }
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
.nav-controls
.nav-controls
- if can?(current_user, :admin_label, @project)
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
New label
.labels
- if can?(current_user, :admin_label, @project)
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
New label
-# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
#js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
= render 'shared/empty_states/priority_labels'
- if @prioritized_labels.present?
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
.labels
- if can?(current_user, :admin_label, @project)
-# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
%p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
- if @prioritized_labels.present?
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
.other-labels
- if can?(current_user, :admin_label, @project)
%h5{ class: ('hide' if hide) } Other Labels
%ul.content-list.manage-labels-list.js-other-labels
- if @labels.present?
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- if @labels.blank?
.nothing-here-block
- if @labels.present?
.other-labels
- if can?(current_user, :admin_label, @project)
Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
- else
No labels created
%h5{ class: ('hide' if hide) } Other Labels
%ul.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- else
= render 'shared/empty_states/labels'

View File

@ -1,3 +1,7 @@
- if @teams_error_message
= content_for :flash_message do
.alert.alert-danger= @teams_error_message
%p
You arent a member of any team on the Mattermost instance at
%strong= Gitlab.config.mattermost.host

View File

@ -46,7 +46,7 @@
= note_count
.merge-request-info
#{merge_request.to_reference} &middot;
#{issuable_reference(merge_request)} &middot;
opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch

View File

@ -2,7 +2,7 @@
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
= f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
.well.prepend-top-20
%ul
@ -13,4 +13,4 @@
%li
The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.

View File

@ -1,16 +1,7 @@
- if @issues.to_a.any?
- @issues.group_by(&:project).each do |group|
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
- if can?(current_user, :create_issue, project)
.pull-right
= link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
%ul.content-list.issues-list
- group[1].each do |issue|
= render 'projects/issues/issue', issue: issue
.panel.panel-default.panel-small.panel-without-border
%ul.content-list.issues-list
= render partial: 'projects/issues/issue', collection: @issues
= paginate @issues, theme: "gitlab"
- else
= render 'shared/empty_states/issues'

View File

@ -36,7 +36,7 @@
%li
= link_to 'Edit', edit_label_path(label)
%li
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'}
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'}
.pull-right.hidden-xs.hidden-sm.hidden-md
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
@ -66,11 +66,15 @@
%a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } }
Group level
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_group, label.project.group)
= link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
%span.sr-only Promote to Group
= icon('level-up')
- if can?(current_user, :admin_label, label)
= link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
%span.sr-only Edit
= icon('pencil-square-o')
= link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
= link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
%span.sr-only Delete
= icon('trash-o')

View File

@ -1,16 +1,8 @@
- if @merge_requests.to_a.any?
- @merge_requests.group_by(&:target_project).each do |group|
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
- if can?(current_user, :create_merge_request, project)
.pull-right
= link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
.panel.panel-default.panel-small.panel-without-border
%ul.content-list.mr-list
= render partial: 'projects/merge_requests/merge_request', collection: @merge_requests
%ul.content-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
= paginate @merge_requests, theme: "gitlab"
- else

View File

@ -1,8 +1,7 @@
- if outdated_browser?
- link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
.browser-alert
GitLab may not work properly because you are using an outdated web browser.
%br
Please install a
= link_to 'supported web browser', link
= link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers')
for a better experience.

View File

@ -0,0 +1,11 @@
.row.empty-state.labels
.pull-right.col-xs-12.col-sm-6
.svg-content
= render 'shared/empty_states/icons/labels.svg'
.col-xs-12.col-sm-6
.text-content
%h4 Labels can be applied to issues and merge requests to categorize them.
%p You can also star label to make it a priority label.
- if can?(current_user, :admin_label, @project)
= link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
= link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'

View File

@ -0,0 +1,3 @@
.text-center
= render 'shared/empty_states/icons/priority_labels.svg'
%p Star labels to start sorting by priority

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="68" viewBox="181 0 116 68"><g fill="none" fill-rule="evenodd" transform="translate(182)"><rect width="78" height="34" x="37" y="34" fill="#FAFAFA" rx="3"/><rect width="78" height="34" x="31" y="28" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="3"/><path fill="#FFF" stroke="#FC6D26" stroke-width="3" d="M34 35.8c-.6 0-1.4 0-1.8.4L29 38.8c-1 .7-1.7.4-2-.7l-.6-4c0-.5-.5-1.2-1-1.5L22 30.2c-1-.6-1-1.5 0-2l3.7-2c.5-.2 1-.8 1.2-1.3l1-4.2c.3-1 1-1.3 2-.5l3 3c.3.3 1 .6 1.6.6l4.2-.3c1 0 1.5.7 1 1.7L38 29c-.3.6-.3 1.4 0 2l1.3 3.8c.4 1 0 1.8-1.2 1.6l-4-.6z" stroke-linecap="round"/><path fill="#FDE5D8" d="M51.6 14.3c-.2-.2-.8-.4-1-.3l-2.8.5c-.7 0-1-.4-.8-1l1-2.8V9.5L46.6 7c-.3-.7 0-1.2.8-1h2.7c.3 0 .8-.2 1-.5l2-2c.6-.5 1-.4 1.3.3l.7 2.8c0 .3.4.8.7 1l2.3 1.2c.7.3.7 1 0 1.3l-2.2 1.7-.6 1-.4 3c-.2.6-.7.8-1.3.4l-2-1.7zM5.4 43.2c-.2-.2-.5-.2-.7-.2l-1.8.3c-.6 0-1-.2-.7-.7l.7-1.8V40l-1-1.7c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L6.5 36c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2zM10.4 9.2C10.2 9 10 9 9.7 9L8 9.3c-.6 0-1-.2-.7-.7L8 6.8V6L7 4.3c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L11.5 2c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2z"/><rect width="52" height="4" x="43" y="38" fill="#EEE" rx="2"/><rect width="36" height="4" x="43" y="48" fill="#EEE" rx="2"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1 @@
<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M19.25,14.9765625 C19.25,14.1380166 19.0234398,13.3697952 18.5703125,12.671875 L12.6796875,18.5546875 C13.3932327,19.0182315 14.1666625,19.25 15,19.25 C15.5781279,19.25 16.1289036,19.1367199 16.6523438,18.9101562 C17.1757839,18.6835926 17.6276023,18.3802102 18.0078125,18 C18.3880227,17.6197898 18.690103,17.1653672 18.9140625,16.6367188 C19.138022,16.1080703 19.25,15.5546904 19.25,14.9765625 Z M11.4453125,17.3125 L17.34375,11.421875 C16.6406215,10.9479143 15.8593793,10.7109375 15,10.7109375 C14.2291628,10.7109375 13.5182324,10.9010398 12.8671875,11.28125 C12.2161426,11.6614602 11.7005227,12.1796842 11.3203125,12.8359375 C10.9401023,13.4921908 10.75,14.2057253 10.75,14.9765625 C10.75,15.8203167 10.9817685,16.5989548 11.4453125,17.3125 Z M21,14.9765625 C21,15.7942749 20.8411474,16.5755171 20.5234375,17.3203125 C20.2057276,18.0651079 19.7799506,18.7057265 19.2460938,19.2421875 C18.7122369,19.7786485 18.0742225,20.2057276 17.3320312,20.5234375 C16.58984,20.8411474 15.8125041,21 15,21 C14.1874959,21 13.41016,20.8411474 12.6679688,20.5234375 C11.9257775,20.2057276 11.2877631,19.7786485 10.7539062,19.2421875 C10.2200494,18.7057265 9.79427242,18.0651079 9.4765625,17.3203125 C9.15885258,16.5755171 9,15.7942749 9,14.9765625 C9,14.1588501 9.15885258,13.37891 9.4765625,12.6367188 C9.79427242,11.8945275 10.2200494,11.255211 10.7539062,10.71875 C11.2877631,10.182289 11.9257775,9.75520992 12.6679688,9.4375 C13.41016,9.11979008 14.1874959,8.9609375 15,8.9609375 C15.8125041,8.9609375 16.58984,9.11979008 17.3320312,9.4375 C18.0742225,9.75520992 18.7122369,10.182289 19.2460938,10.71875 C19.7799506,11.255211 20.2057276,11.8945275 20.5234375,12.6367188 C20.8411474,13.37891 21,14.1588501 21,14.9765625 Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M21.5401786,15.2320328 L11.90625,20.5858274 C11.7950143,20.6486998 11.6994982,20.6559541 11.6196987,20.6075908 C11.5398992,20.5592275 11.5,20.4721748 11.5,20.3464301 L11.5,9.66785867 C11.5,9.54211399 11.5398992,9.45506129 11.6196987,9.40669795 C11.6994982,9.35833462 11.7950143,9.36558901 11.90625,9.42846135 L21.5401786,14.782256 C21.6514142,14.8451283 21.7070312,14.9200904 21.7070312,15.0071444 C21.7070312,15.0941984 21.6514142,15.1691604 21.5401786,15.2320328 Z"></path></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@ -0,0 +1 @@
<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M20.6114971,16.0821413 C20.6114971,16.106323 20.6090789,16.1232499 20.6042426,16.1329226 C20.2947172,17.42906 19.6466582,18.4797378 18.6600462,19.2849873 C17.6734341,20.0902369 16.5175677,20.4928556 15.1924122,20.4928556 C14.4863075,20.4928556 13.8031856,20.3598584 13.1430261,20.0938601 C12.4828665,19.8278617 11.8940517,19.4482152 11.376564,18.9549092 L10.4407381,19.8907351 C10.3488478,19.9826254 10.2400319,20.0285699 10.1142872,20.0285699 C9.98854256,20.0285699 9.87972669,19.9826254 9.78783635,19.8907351 C9.69594601,19.7988447 9.65000153,19.6900289 9.65000153,19.5642842 L9.65000153,16.3142842 C9.65000153,16.1885395 9.69594601,16.0797236 9.78783635,15.9878333 C9.87972669,15.895943 9.98854256,15.8499985 10.1142872,15.8499985 L13.3642872,15.8499985 C13.4900319,15.8499985 13.5988478,15.895943 13.6907381,15.9878333 C13.7826285,16.0797236 13.828573,16.1885395 13.828573,16.3142842 C13.828573,16.4400289 13.7826285,16.5488447 13.6907381,16.6407351 L12.6968765,17.6345967 C13.0402562,17.9537947 13.4295752,18.200444 13.8648453,18.374552 C14.3001153,18.5486601 14.7523057,18.6357128 15.2214301,18.6357128 C15.8694988,18.6357128 16.4740315,18.4785343 17.0350462,18.1641726 C17.5960609,17.8498109 18.0458332,17.4169655 18.3843765,16.8656235 C18.4375762,16.7834058 18.5657371,16.5004845 18.7688631,16.0168512 C18.8075538,15.9056155 18.8800977,15.8499985 18.9864971,15.8499985 L20.3793542,15.8499985 C20.4422265,15.8499985 20.4966345,15.8729707 20.5425797,15.9189159 C20.5885248,15.9648611 20.6114971,16.019269 20.6114971,16.0821413 Z M20.7928587,10.2785699 L20.7928587,13.5285699 C20.7928587,13.6543146 20.7469142,13.7631305 20.6550238,13.8550208 C20.5631335,13.9469111 20.4543176,13.9928556 20.328573,13.9928556 L17.078573,13.9928556 C16.9528283,13.9928556 16.8440124,13.9469111 16.7521221,13.8550208 C16.6602317,13.7631305 16.6142872,13.6543146 16.6142872,13.5285699 C16.6142872,13.4028252 16.6602317,13.2940094 16.7521221,13.202119 L17.7532381,12.2010029 C17.0374607,11.5384252 16.1935332,11.2071413 15.2214301,11.2071413 C14.5733614,11.2071413 13.9688287,11.3643198 13.407814,11.6786815 C12.8467993,11.9930432 12.397027,12.4258886 12.0584837,12.9772306 C12.005284,13.0594483 11.8771231,13.3423696 11.6739971,13.8260029 C11.6353064,13.9372386 11.5627625,13.9928556 11.4563631,13.9928556 L10.0127247,13.9928556 C9.9498524,13.9928556 9.89544446,13.9698834 9.84949929,13.9239382 C9.80355412,13.877993 9.78058188,13.8235851 9.78058188,13.7607128 L9.78058188,13.7099315 C10.0949436,12.4137941 10.7478388,11.3631163 11.7392872,10.5578668 C12.7307356,9.75261722 13.8914383,9.34999847 15.2214301,9.34999847 C15.9275348,9.34999847 16.6142839,9.48420472 17.281698,9.75262124 C17.949112,10.0210378 18.541554,10.3994752 19.0590417,10.8879449 L20.0021221,9.95211901 C20.0940124,9.86022867 20.2028283,9.81428419 20.328573,9.81428419 C20.4543176,9.81428419 20.5631335,9.86022867 20.6550238,9.95211901 C20.7469142,10.0440094 20.7928587,10.1528252 20.7928587,10.2785699 Z"></path></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1 @@
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M20.1357204,10.2985704 L20.1357204,19.7271418 C20.1357204,19.8432138 20.0933101,19.9436592 20.0084882,20.0284811 C19.9236664,20.1133029 19.823221,20.1557132 19.707149,20.1557132 L10.2785775,20.1557132 C10.1625055,20.1557132 10.0620601,20.1133029 9.97723825,20.0284811 C9.89241639,19.9436592 9.8500061,19.8432138 9.8500061,19.7271418 L9.8500061,10.2985704 C9.8500061,10.1824984 9.89241639,10.0820529 9.97723825,9.99723107 C10.0620601,9.91240922 10.1625055,9.86999893 10.2785775,9.86999893 L19.707149,9.86999893 C19.823221,9.86999893 19.9236664,9.91240922 20.0084882,9.99723107 C20.0933101,10.0820529 20.1357204,10.1824984 20.1357204,10.2985704 Z"></path></svg>

After

Width:  |  Height:  |  Size: 770 B

View File

@ -18,7 +18,7 @@
%p
Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out
= succeed "." do
%a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank" } notification emails
%a{ href: help_page_path('workflow/notifications'), target: "_blank" } notification emails
.col-lg-8
- NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index|
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"

View File

@ -0,0 +1,4 @@
---
title: Added labels empty state
merge_request: 7443
author:

View File

@ -0,0 +1,4 @@
---
title: Don't group issues by project on group-level and dashboard issue indexes.
merge_request: 8111
author: Bernardo Castro

View File

@ -0,0 +1,4 @@
---
title: Fix disable storing of sensitive information when importing a new repo
merge_request: 8885
author: Bernard Pietraga

View File

@ -0,0 +1,4 @@
---
title: Refactor MergeRequests::BuildService
merge_request: 8462
author: Rydkin Maxim

View File

@ -0,0 +1,4 @@
---
title: Remove flash warning from login page
merge_request: 8864
author: Gerald J. Padilla

View File

@ -0,0 +1,4 @@
---
title: Convert pipeline action icons to svg to have them propperly positioned
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: Avoid repeated dashes in $CI_ENVIRONMENT_SLUG
merge_request: 8638
author:

View File

@ -0,0 +1,4 @@
---
title: Update and pin the `jwt` gem to ~> 1.5.6
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: Fix notifications when set at group level
merge_request: 6813
author: Alexandre Maia

View File

@ -0,0 +1,4 @@
---
title: Cop for gem fetched from a git source
merge_request: 8856
author: Adam Pahlevi

View File

@ -0,0 +1,4 @@
---
title: Adds documentation for how to use Vue.js
merge_request: 8866
author:

View File

@ -0,0 +1,4 @@
---
title: Add read-only full_path and full_name attributes to Group API
merge_request: 8827
author:

View File

@ -0,0 +1,4 @@
---
title: Remove new branch button for confidential issues
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: Improve build policy and access abilities
merge_request: 8711
author:

View File

@ -0,0 +1,4 @@
---
title: Ignore encrypted attributes in Import/Export
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: Ensure autogenerated title does not cause failing spec
merge_request: 8963
author: brian m. carlson

View File

@ -0,0 +1,4 @@
---
title: Changed composer installer script in the CI PHP example doc
merge_request: 4342
author: Jeffrey Cafferata

View File

@ -0,0 +1,4 @@
---
title: Resets assignee dropdown when sidebar is open
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: "Project labels can now be promoted to group labels"
merge_request: 7242
author: Olaf Tomalka

View File

@ -0,0 +1,4 @@
---
title: Add project ID index to `project_authorizations` table to optimize queries
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: Improve performance of slash commands
merge_request: 8876
author:

View File

@ -220,6 +220,7 @@ constraints(ProjectUrlConstrainer.new) do
end
member do
post :promote
post :toggle_subscription
delete :remove_priority
end

View File

@ -8,8 +8,9 @@ class AddEnvironmentSlug < ActiveRecord::Migration
DOWNTIME_REASON = 'Adding NOT NULL column environments.slug with dependent data'
# Used to generate random suffixes for the slug
LETTERS = 'a'..'z'
NUMBERS = '0'..'9'
SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a
SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
def up
environments = Arel::Table.new(:environments)
@ -39,17 +40,24 @@ class AddEnvironmentSlug < ActiveRecord::Migration
slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
# Must start with a letter
slugified = "env-" + slugified if NUMBERS.cover?(slugified[0])
slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
# Repeated dashes are invalid (OpenShift limitation)
slugified.gsub!(/\-+/, '-')
# Maximum length: 24 characters (OpenShift limitation)
slugified = slugified[0..23]
# Cannot end with a "-" character (Kubernetes label limitation)
slugified = slugified[0..-2] if slugified[-1] == "-"
# Cannot end with a dash (Kubernetes label limitation)
slugified.chop! if slugified.end_with?('-')
# Add a random suffix, shortening the current string if necessary, if it
# has been slugified. This ensures uniqueness.
slugified = slugified[0..16] + "-" + random_suffix if slugified != name
if slugified != name
slugified = slugified[0..16]
slugified << '-' unless slugified.end_with?('-')
slugified << random_suffix
end
slugified
end

View File

@ -0,0 +1,11 @@
class AddIndexToProjectAuthorizations < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:project_authorizations, :project_id)
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170121130655) do
ActiveRecord::Schema.define(version: 20170130204620) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -874,6 +874,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do
t.integer "access_level"
end
add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree
add_index "project_authorizations", ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree
create_table "project_features", force: :cascade do |t|

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