Commit cad4eb98 authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into 'bootstrap4'

# Conflicts:
#   app/views/projects/issues/_nav_btns.html.haml
#   app/views/projects/merge_requests/creations/_new_compare.html.haml
parents 02579d6a d8dd75ca
...@@ -36,6 +36,7 @@ Set the title to: `[Security] Description of the original issue` ...@@ -36,6 +36,7 @@ Set the title to: `[Security] Description of the original issue`
- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details) - [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) - [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details) - [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
### Summary ### Summary
#### Links #### Links
...@@ -61,8 +62,9 @@ Set the title to: `[Security] Description of the original issue` ...@@ -61,8 +62,9 @@ Set the title to: `[Security] Description of the original issue`
| Upgrade notes | | | | Upgrade notes | | |
| GitLab Settings updated | Yes/No| | | GitLab Settings updated | Yes/No| |
| Migration required | Yes/No | | | Migration required | Yes/No | |
| Thanks | | |
[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/process.md [security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
[RM list]: https://about.gitlab.com/release-managers/ [RM list]: https://about.gitlab.com/release-managers/
/label ~security /label ~security
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import * as consts from '../../stores/modules/commit/constants'; import * as consts from '../../stores/modules/commit/constants';
import RadioGroup from './radio_group.vue'; import RadioGroup from './radio_group.vue';
export default { export default {
components: { components: {
RadioGroup, RadioGroup,
},
computed: {
...mapState(['currentBranchId']),
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
{ branchName: `<strong class="monospace">${this.currentBranchId}</strong>` },
false,
);
}, },
computed: { },
...mapState([ commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
'currentBranchId', commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
]), commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
newMergeRequestHelpText() { };
return sprintf(
__('Creates a new branch from %{branchName} and re-directs to create a new merge request'),
{ branchName: this.currentBranchId },
);
},
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
{ branchName: `<strong>${this.currentBranchId}</strong>` },
false,
);
},
commitToNewBranchText() {
return sprintf(
__('Creates a new branch from %{branchName}'),
{ branchName: this.currentBranchId },
);
},
},
commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
};
</script> </script>
<template> <template>
...@@ -53,13 +39,11 @@ ...@@ -53,13 +39,11 @@
:value="$options.commitToNewBranch" :value="$options.commitToNewBranch"
:label="__('Create a new branch')" :label="__('Create a new branch')"
:show-input="true" :show-input="true"
:help-text="commitToNewBranchText"
/> />
<radio-group <radio-group
:value="$options.commitToNewBranchMR" :value="$options.commitToNewBranchMR"
:label="__('Create a new branch and merge request')" :label="__('Create a new branch and merge request')"
:show-input="true" :show-input="true"
:help-text="newMergeRequestHelpText"
/> />
</div> </div>
</template> </template>
<script>
import { __, sprintf } from '../../../locale';
import Icon from '../../../vue_shared/components/icon.vue';
import popover from '../../../vue_shared/directives/popover';
import { MAX_TITLE_LENGTH, MAX_BODY_LENGTH } from '../../constants';
export default {
directives: {
popover,
},
components: {
Icon,
},
props: {
text: {
type: String,
required: true,
},
},
data() {
return {
scrollTop: 0,
isFocused: false,
};
},
computed: {
allLines() {
return this.text.split('\n').map((line, i) => ({
text: line.substr(0, this.getLineLength(i)) || ' ',
highlightedText: line.substr(this.getLineLength(i)),
}));
},
},
methods: {
handleScroll() {
if (this.$refs.textarea) {
this.$nextTick(() => {
this.scrollTop = this.$refs.textarea.scrollTop;
});
}
},
getLineLength(i) {
return i === 0 ? MAX_TITLE_LENGTH : MAX_BODY_LENGTH;
},
onInput(e) {
this.$emit('input', e.target.value);
},
updateIsFocused(isFocused) {
this.isFocused = isFocused;
},
},
popoverOptions: {
trigger: 'hover',
placement: 'top',
content: sprintf(
__(`
The character highligher helps you keep the subject line to %{titleLength} characters
and wrap the body at %{bodyLength} so they are readable in git.
`),
{ titleLength: MAX_TITLE_LENGTH, bodyLength: MAX_BODY_LENGTH },
),
},
};
</script>
<template>
<fieldset class="common-note-form ide-commit-message-field">
<div
class="md-area"
:class="{
'is-focused': isFocused
}"
>
<div
v-once
class="md-header"
>
<ul class="nav-links">
<li>
{{ __('Commit Message') }}
<span
v-popover="$options.popoverOptions"
class="help-block prepend-left-10"
>
<icon
name="question"
/>
</span>
</li>
</ul>
</div>
<div class="ide-commit-message-textarea-container">
<div class="ide-commit-message-highlights-container">
<div
class="note-textarea highlights monospace"
:style="{
transform: `translate3d(0, ${-scrollTop}px, 0)`
}"
>
<div
v-for="(line, index) in allLines"
:key="index"
>
<span
v-text="line.text"
>
</span><mark
v-show="line.highlightedText"
v-text="line.highlightedText"
>
</mark>
</div>
</div>
</div>
<textarea
class="note-textarea ide-commit-message-textarea"
name="commit-message"
:placeholder="__('Write a commit message...')"
:value="text"
@scroll="handleScroll"
@input="onInput"
@focus="updateIsFocused(true)"
@blur="updateIsFocused(false)"
ref="textarea"
>
</textarea>
</div>
</div>
</fieldset>
</template>
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
export default { export default {
directives: { directives: {
tooltip, tooltip,
},
props: {
value: {
type: String,
required: true,
}, },
props: { label: {
value: { type: String,
type: String, required: false,
required: true, default: null,
},
label: {
type: String,
required: false,
default: null,
},
checked: {
type: Boolean,
required: false,
default: false,
},
showInput: {
type: Boolean,
required: false,
default: false,
},
helpText: {
type: String,
required: false,
default: null,
},
}, },
computed: { checked: {
...mapState('commit', [ type: Boolean,
'commitAction', required: false,
]), default: false,
...mapGetters('commit', [
'newBranchName',
]),
}, },
methods: { showInput: {
...mapActions('commit', [ type: Boolean,
'updateCommitAction', required: false,
'updateBranchName', default: false,
]),
}, },
}; },
computed: {
...mapState('commit', ['commitAction']),
...mapGetters('commit', ['newBranchName']),
},
methods: {
...mapActions('commit', ['updateCommitAction', 'updateBranchName']),
},
};
</script> </script>
<template> <template>
...@@ -65,18 +53,6 @@ ...@@ -65,18 +53,6 @@
{{ label }} {{ label }}
</template> </template>
<slot v-else></slot> <slot v-else></slot>
<span
v-if="helpText"
v-tooltip
class="help-block inline"
:title="helpText"
>
<i
class="fa fa-question-circle"
aria-hidden="true"
>
</i>
</span>
</span> </span>
</label> </label>
<div <div
...@@ -85,7 +61,7 @@ ...@@ -85,7 +61,7 @@
> >
<input <input
type="text" type="text"
class="form-control" class="form-control monospace"
:placeholder="newBranchName" :placeholder="newBranchName"
@input="updateBranchName($event.target.value)" @input="updateBranchName($event.target.value)"
/> />
......
...@@ -5,6 +5,7 @@ import icon from '~/vue_shared/components/icon.vue'; ...@@ -5,6 +5,7 @@ import icon from '~/vue_shared/components/icon.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue';
import commitFilesList from './commit_sidebar/list.vue'; import commitFilesList from './commit_sidebar/list.vue';
import CommitMessageField from './commit_sidebar/message_field.vue';
import * as consts from '../stores/modules/commit/constants'; import * as consts from '../stores/modules/commit/constants';
import Actions from './commit_sidebar/actions.vue'; import Actions from './commit_sidebar/actions.vue';
...@@ -15,6 +16,7 @@ export default { ...@@ -15,6 +16,7 @@ export default {
commitFilesList, commitFilesList,
Actions, Actions,
LoadingButton, LoadingButton,
CommitMessageField,
}, },
directives: { directives: {
tooltip, tooltip,
...@@ -38,15 +40,9 @@ export default { ...@@ -38,15 +40,9 @@ export default {
'changedFiles', 'changedFiles',
]), ]),
...mapState('commit', ['commitMessage', 'submitCommitLoading']), ...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters('commit', [ ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']),
'commitButtonDisabled',
'discardDraftButtonDisabled',
'branchName',
]),
statusSvg() { statusSvg() {
return this.lastCommitMsg return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath;
? this.committedStateSvgPath
: this.noChangesStateSvgPath;
}, },
}, },
methods: { methods: {
...@@ -64,9 +60,7 @@ export default { ...@@ -64,9 +60,7 @@ export default {
}); });
}, },
forceCreateNewBranch() { forceCreateNewBranch() {
return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => this.commitChanges());
this.commitChanges(),
);
}, },
}, },
}; };
...@@ -105,16 +99,10 @@ export default { ...@@ -105,16 +99,10 @@ export default {
@submit.prevent.stop="commitChanges" @submit.prevent.stop="commitChanges"
v-if="!rightPanelCollapsed" v-if="!rightPanelCollapsed"
> >
<div class="multi-file-commit-fieldset"> <commit-message-field
<textarea :text="commitMessage"
class="form-control multi-file-commit-message" @input="updateCommitMessage"
name="commit-message" />
:value="commitMessage"
:placeholder="__('Write a commit message...')"
@input="updateCommitMessage($event.target.value)"
>
</textarea>
</div>
<div class="clearfix prepend-top-15"> <div class="clearfix prepend-top-15">
<actions /> <actions />
<loading-button <loading-button
......
// Fuzzy file finder
export const MAX_TITLE_LENGTH = 50;
export const MAX_BODY_LENGTH = 72;
...@@ -5,45 +5,71 @@ import * as types from '../mutation_types'; ...@@ -5,45 +5,71 @@ import * as types from '../mutation_types';
export const getProjectData = ( export const getProjectData = (
{ commit, state, dispatch }, { commit, state, dispatch },
{ namespace, projectId, force = false } = {}, { namespace, projectId, force = false } = {},
) => new Promise((resolve, reject) => { ) =>
if (!state.projects[`${namespace}/${projectId}`] || force) { new Promise((resolve, reject) => {
commit(types.TOGGLE_LOADING, { entry: state }); if (!state.projects[`${namespace}/${projectId}`] || force) {
service.getProjectData(namespace, projectId)
.then(res => res.data)
.then((data) => {
commit(types.TOGGLE_LOADING, { entry: state }); commit(types.TOGGLE_LOADING, { entry: state });
commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data }); service
if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`); .getProjectData(namespace, projectId)
resolve(data); .then(res => res.data)
}) .then(data => {
.catch(() => { commit(types.TOGGLE_LOADING, { entry: state });
flash('Error loading project data. Please try again.', 'alert', document, null, false, true); commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
reject(new Error(`Project not loaded ${namespace}/${projectId}`)); if (!state.currentProjectId)
}); commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
} else { resolve(data);
resolve(state.projects[`${namespace}/${projectId}`]); })
} .catch(() => {
}); flash(
'Error loading project data. Please try again.',
'alert',
document,
null,
false,
true,
);
reject(new Error(`Project not loaded ${namespace}/${projectId}`));
});
} else {
resolve(state.projects[`${namespace}/${projectId}`]);
}
});
export const getBranchData = ( export const getBranchData = (
{ commit, state, dispatch }, { commit, state, dispatch },
{ projectId, branchId, force = false } = {}, { projectId, branchId, force = false } = {},
) => new Promise((resolve, reject) => { ) =>
if ((typeof state.projects[`${projectId}`] === 'undefined' || new Promise((resolve, reject) => {
!state.projects[`${projectId}`].branches[branchId]) if (
|| force) { typeof state.projects[`${projectId}`] === 'undefined' ||
service.getBranchData(`${projectId}`, branchId) !state.projects[`${projectId}`].branches[branchId] ||
.then(({ data }) => { force
const { id } = data.commit; ) {
commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data }); service
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id }); .getBranchData(`${projectId}`, branchId)
resolve(data); .then(({ data }) => {
}) const { id } = data.commit;
.catch(() => { commit(types.SET_BRANCH, {
flash('Error loading branch data. Please try again.', 'alert', document, null, false, true); projectPath: `${projectId}`,
reject(new Error(`Branch not loaded - ${projectId}/${branchId}`)); branchName: branchId,
}); branch: data,
} else { });
resolve(state.projects[`${projectId}`].branches[branchId]); commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
} commit(types.SET_CURRENT_BRANCH, branchId);
}); resolve(data);
})
.catch(() => {
flash(
'Error loading branch data. Please try again.',
'alert',
document,
null,
false,
true,
);
reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
});
} else {
resolve(state.projects[`${projectId}`].branches[branchId]);
}
});
...@@ -662,11 +662,6 @@ ...@@ -662,11 +662,6 @@
} }
} }
.multi-file-commit-message.form-control {
height: 160px;
resize: none;
}
.dirty-diff { .dirty-diff {
// !important need to override monaco inline style // !important need to override monaco inline style
width: 4px !important; width: 4px !important;
...@@ -839,3 +834,74 @@ ...@@ -839,3 +834,74 @@
align-items: center; align-items: center;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
.ide-commit-message-field {
height: 200px;
background-color: $white-light;
.md-area {
display: flex;
flex-direction: column;
height: 100%;
}
.nav-links {
height: 30px;
}
.help-block {
margin-top: 2px;
color: $blue-500;
cursor: pointer;
}
}
.ide-commit-message-textarea-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.note-textarea {
font-family: $monospace_font;
}
}
.ide-commit-message-highlights-container {
position: absolute;
left: 0;
top: 0;
right: -100px;
bottom: 0;
padding-right: 100px;
pointer-events: none;
z-index: 1;
.highlights {
white-space: pre-wrap;
word-wrap: break-word;
color: transparent;
}
mark {
margin-left: -1px;
padding: 0 2px;
border-radius: $border-radius-small;
background-color: $orange-200;
color: transparent;
opacity: 0.6;
}
}
.ide-commit-message-textarea {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 2;
background: transparent;
resize: none;
}
...@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base ...@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base
include Gitlab::GonHelper include Gitlab::GonHelper
include GitlabRoutingHelper include GitlabRoutingHelper
include PageLayoutHelper include PageLayoutHelper
include SafeParamsHelper
include SentryHelper include SentryHelper
include WorkhorseHelper include WorkhorseHelper
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
......
...@@ -217,7 +217,7 @@ module NotesActions ...@@ -217,7 +217,7 @@ module NotesActions
def note_project def note_project
strong_memoize(:note_project) do strong_memoize(:note_project) do
return nil unless project next nil unless project
note_project_id = params[:note_project_id] note_project_id = params[:note_project_id]
...@@ -228,7 +228,7 @@ module NotesActions ...@@ -228,7 +228,7 @@ module NotesActions
project project
end end
return access_denied! unless can?(current_user, :create_note, the_project) next access_denied! unless can?(current_user, :create_note, the_project)
the_project the_project
end end
......
...@@ -86,7 +86,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -86,7 +86,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
out_of_range = todos.current_page > total_pages out_of_range = todos.current_page > total_pages
if out_of_range if out_of_range
redirect_to url_for(params.merge(page: total_pages, only_path: true)) redirect_to url_for(safe_params.merge(page: total_pages, only_path: true))
end end
out_of_range out_of_range
......
...@@ -15,7 +15,7 @@ module Groups ...@@ -15,7 +15,7 @@ module Groups
def update def update
if @group.update(group_variables_params) if @group.update(group_variables_params)
respond_to do |format| respond_to do |format|
format.json { return render_group_variables } format.json { render_group_variables }
end end
else else
respond_to do |format| respond_to do |format|
......
...@@ -189,6 +189,6 @@ class GroupsController < Groups::ApplicationController ...@@ -189,6 +189,6 @@ class GroupsController < Groups::ApplicationController
params[:id] = group.to_param params[:id] = group.to_param
url_for(params) url_for(safe_params)
end end
end end
...@@ -60,13 +60,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -60,13 +60,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end end
format.patch do format.patch do
return render_404 unless @merge_request.diff_refs break render_404 unless @merge_request.diff_refs
send_git_patch @project.repository, @merge_request.diff_refs send_git_patch @project.repository, @merge_request.diff_refs
end end
format.diff do format.diff do
return render_404 unless @merge_request.diff_refs break render_404 unless @merge_request.diff_refs
send_git_diff @project.repository, @merge_request.diff_refs send_git_diff @project.repository, @merge_request.diff_refs
end end
......
...@@ -12,7 +12,7 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -12,7 +12,7 @@ class Projects::VariablesController < Projects::ApplicationController
def update def update
if @project.update(variables_params) if @project.update(variables_params)
respond_to do |format| respond_to do |format|
format.json { return render_variables } format.json { render_variables }
end end
else else
respond_to do |format| respond_to do |format|
......
...@@ -404,7 +404,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -404,7 +404,7 @@ class ProjectsController < Projects::ApplicationController
params[:namespace_id] = project.namespace.to_param params[:namespace_id] = project.namespace.to_param
params[:id] = project.to_param params[:id] = project.to_param
url_for(params) url_for(safe_params)
end end
def project_export_enabled def project_export_enabled
......
...@@ -146,6 +146,6 @@ class UsersController < ApplicationController ...@@ -146,6 +146,6 @@ class UsersController < ApplicationController
end end
def build_canonical_path(user) def build_canonical_path(user)
url_for(params.merge(username: user.to_param)) url_for(safe_params.merge(username: user.to_param))
end end
end end
...@@ -259,7 +259,7 @@ module BlobHelper ...@@ -259,7 +259,7 @@ module BlobHelper
options = [] options = []
if error == :collapsed if error == :collapsed
options << link_to('load it anyway', url_for(params.merge(viewer: viewer.type, expanded: true, format: nil))) options << link_to('load it anyway', url_for(safe_params.merge(viewer: viewer.type, expanded: true, format: nil)))
end end
# If the error is `:server_side_but_stored_externally`, the simple viewer will show the same error, # If the error is `:server_side_but_stored_externally`, the simple viewer will show the same error,
......
...@@ -180,7 +180,7 @@ module DiffHelper ...@@ -180,7 +180,7 @@ module DiffHelper
private private
def diff_btn(title, name, selected) def diff_btn(title, name, selected)
params_copy = params.dup params_copy = safe_params.dup
params_copy[:view] = name params_copy[:view] = name
# Always use HTML to handle case where JSON diff rendered this button # Always use HTML to handle case where JSON diff rendered this button
......
module SafeParamsHelper
# Rails 5.0 requires to permit `params` if they're used in url helpers.
# Use this helper when generating links with `params.merge(...)`
def safe_params
if params.respond_to?(:permit!)
params.except(:host, :port, :protocol).permit!
else
params
end
end
end
...@@ -6,6 +6,12 @@ module Emails ...@@ -6,6 +6,12 @@ module Emails
mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason)) mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
end end
def issue_due_email(recipient_id, issue_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
end
def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil) def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id) setup_issue_mail(issue_id, recipient_id)
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
......
...@@ -162,7 +162,7 @@ module Ci ...@@ -162,7 +162,7 @@ module Ci
build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies') build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
end end
before_transition pending: :running do |build| after_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state build.ensure_metadata.update_timeout_state
end end
end end
...@@ -479,7 +479,7 @@ module Ci ...@@ -479,7 +479,7 @@ module Ci
def user_variables def user_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables if user.blank? break variables if user.blank?
variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s) variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
variables.append(key: 'GITLAB_USER_EMAIL', value: user.email) variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
...@@ -594,7 +594,7 @@ module Ci ...@@ -594,7 +594,7 @@ module Ci
def persisted_variables def persisted_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables unless persisted? break variables unless persisted?
variables variables
.append(key: 'CI_JOB_ID', value: id.to_s) .append(key: 'CI_JOB_ID', value: id.to_s)
...@@ -643,7 +643,7 @@ module Ci ...@@ -643,7 +643,7 @@ module Ci
def persisted_environment_variables def persisted_environment_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables unless persisted? && persisted_environment.present? break variables unless persisted? && persisted_environment.present?
variables.concat(persisted_environment.predefined_variables) variables.concat(persisted_environment.predefined_variables)
......
...@@ -87,7 +87,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -87,7 +87,7 @@ class CommitStatus < ActiveRecord::Base
transition [:created, :pending, :running, :manual] => :canceled transition [:created, :pending, :running, :manual] => :canceled
end end
before_transition created: [:pending, :running] do |commit_status| before_transition [:created, :skipped, :manual] => :pending do |commit_status|
commit_status.queued_at = Time.now commit_status.queued_at = Time.now
end end
......
...@@ -11,7 +11,9 @@ module CacheMarkdownField ...@@ -11,7 +11,9 @@ module CacheMarkdownField
extend ActiveSupport::Concern extend ActiveSupport::Concern
# Increment this number every time the renderer changes its output # Increment this number every time the renderer changes its output
CACHE_VERSION = 3 CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10
CACHE_COMMONMARK_VERSION = 11
# changes to these attributes cause the cache to be invalidates # changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze INVALIDATED_BY = %w[author project].freeze
...@@ -49,12 +51,14 @@ module CacheMarkdownField ...@@ -49,12 +51,14 @@ module CacheMarkdownField
# Always include a project key, or Banzai complains # Always include a project key, or Banzai complains
project = self.project if self.respond_to?(:project) project = self.project if self.respond_to?(:project)
group = self.group if self.respond_to?(:group) group = self.group if self.respond_to?(:group)
context = cached_markdown_fields[field].merge(project: project, group: group) context = cached_markdown_fields[field].merge(project: project, group: group)
# Banzai is less strict about authors, so don't always have an author key # Banzai is less strict about authors, so don't always have an author key
context[:author] = self.author if self.respond_to?(:author) context[:author] = self.author if self.respond_to?(:author)
context[:markdown_engine] = markdown_engine
context context
end end
...@@ -69,7 +73,7 @@ module CacheMarkdownField ...@@ -69,7 +73,7 @@ module CacheMarkdownField
Banzai::Renderer.cacheless_render_field(self, markdown_field, options) Banzai::Renderer.cacheless_render_field(self, markdown_field, options)
] ]
end.to_h end.to_h
updates['cached_markdown_version'] = CacheMarkdownField::CACHE_VERSION updates['cached_markdown_version'] = latest_cached_markdown_version
updates.each {|html_field, data| write_attribute(html_field, data) } updates.each {|html_field, data| write_attribute(html_field, data) }
end end
...@@ -90,7 +94,7 @@ module CacheMarkdownField ...@@ -90,7 +94,7 @@ module CacheMarkdownField
markdown_changed = attribute_changed?(markdown_field) || false markdown_changed = attribute_changed?(markdown_field) || false
html_changed = attribute_changed?(html_field) || false html_changed = attribute_changed?(html_field) || false
CacheMarkdownField::CACHE_VERSION == cached_markdown_version && latest_cached_markdown_version == cached_markdown_version &&
(html_changed || markdown_changed == html_changed) (html_changed || markdown_changed == html_changed)
end end
...@@ -109,6 +113,24 @@ module CacheMarkdownField ...@@ -109,6 +113,24 @@ module CacheMarkdownField
__send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end end
def latest_cached_markdown_version
return CacheMarkdownField::CACHE_REDCARPET_VERSION unless cached_markdown_version
if cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
CacheMarkdownField::CACHE_REDCARPET_VERSION
else
CacheMarkdownField::CACHE_COMMONMARK_VERSION
end
end
def markdown_engine
if latest_cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
:redcarpet
else
:common_mark
end
end
included do included do
cattr_reader :cached_markdown_fields do cattr_reader :cached_markdown_fields do
FieldData.new FieldData.new
......
...@@ -49,6 +49,7 @@ class Issue < ActiveRecord::Base ...@@ -49,6 +49,7 @@ class Issue < ActiveRecord::Base
scope :without_due_date, -> { where(due_date: nil) } scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) } scope :due_before, ->(date) { where('issues.due_date < ?', date) }
scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) } scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
scope :due_tomorrow, -> { where(due_date: Date.tomorrow) }
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') } scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') } scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
......
...@@ -83,14 +83,14 @@ class NotificationRecipient ...@@ -83,14 +83,14 @@ class NotificationRecipient
def has_access? def has_access?
DeclarativePolicy.subject_scope do DeclarativePolicy.subject_scope do
return false unless user.can?(:receive_notifications) break false unless user.can?(:receive_notifications)
return true if @skip_read_ability break true if @skip_read_ability
return false if @target && !user.can?(:read_cross_project) break false if @target && !user.can?(:read_cross_project)
return false if @project && !user.can?(:read_project, @project) break false if @project && !user.can?(:read_project, @project)
return true unless read_ability break true unless read_ability
return true unless DeclarativePolicy.has_policy?(@target) break true unless DeclarativePolicy.has_policy?(@target)
user.can?(read_ability, @target) user.can?(read_ability, @target)
end end
......
...@@ -47,7 +47,8 @@ class NotificationSetting < ActiveRecord::Base ...@@ -47,7 +47,8 @@ class NotificationSetting < ActiveRecord::Base
].freeze ].freeze
EXCLUDED_WATCHER_EVENTS = [ EXCLUDED_WATCHER_EVENTS = [
:push_to_merge_request :push_to_merge_request,
:issue_due
].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze ].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze
def self.find_or_create_for(source) def self.find_or_create_for(source)
......
...@@ -1637,7 +1637,7 @@ class Project < ActiveRecord::Base ...@@ -1637,7 +1637,7 @@ class Project < ActiveRecord::Base
def container_registry_variables def container_registry_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
return variables unless Gitlab.config.registry.enabled break variables unless Gitlab.config.registry.enabled
variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port) variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port)
......
...@@ -4,6 +4,9 @@ module Ci ...@@ -4,6 +4,9 @@ module Ci
class RegisterJobService class RegisterJobService
attr_reader :runner attr_reader :runner
JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
Result = Struct.new(:build, :valid?) Result = Struct.new(:build, :valid?)
def initialize(runner) def initialize(runner)
...@@ -30,7 +33,7 @@ module Ci ...@@ -30,7 +33,7 @@ module Ci
end end
end end
builds.find do |build| builds.auto_include(false).find do |build|
next unless runner.can_pick?(build) next unless runner.can_pick?(build)
begin begin
...@@ -41,7 +44,7 @@ module Ci ...@@ -41,7 +44,7 @@ module Ci
build.run! build.run!
register_success(build) register_success(build)
return Result.new(build, true) return Result.new(build, true) # rubocop:disable Cop/AvoidReturnFromBlocks
rescue Ci::Build::MissingDependenciesError rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure) build.drop!(:missing_dependency_failure)
end end
...@@ -104,10 +107,22 @@ module Ci ...@@ -104,10 +107,22 @@ module Ci
end end
def register_success(job) def register_success(job)
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at) labels = { shared_runner: runner.shared?,
jobs_running_for_project: jobs_running_for_project(job) }
job_queue_duration_seconds.observe(labels, Time.now - job.queued_at) unless job.queued_at.nil?
attempt_counter.increment attempt_counter.increment
end end
def jobs_running_for_project(job)
return '+Inf' unless runner.shared?
# excluding currently started job
running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.shared)
.limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
end
def failed_attempt_counter def failed_attempt_counter
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job") @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
end end
...@@ -117,7 +132,7 @@ module Ci ...@@ -117,7 +132,7 @@ module Ci
end end
def job_queue_duration_seconds def job_queue_duration_seconds
@job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time') @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS)
end end
end end
end end
...@@ -17,7 +17,7 @@ module Clusters ...@@ -17,7 +17,7 @@ module Clusters
when 'DONE' when 'DONE'
finalize_creation finalize_creation
else else
return provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}") provider.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
end end
end end
end end
......
...@@ -19,8 +19,8 @@ class CreateDeploymentService ...@@ -19,8 +19,8 @@ class CreateDeploymentService
environment.fire_state_event(action) environment.fire_state_event(action)
return unless environment.save break unless environment.save
return if environment.stopped? break if environment.stopped?
deploy.tap(&:update_merge_request_metrics!) deploy.tap(&:update_merge_request_metrics!)
end end
......
...@@ -10,7 +10,7 @@ class ImportExportCleanUpService ...@@ -10,7 +10,7 @@ class ImportExportCleanUpService
def execute def execute
Gitlab::Metrics.measure(:import_export_clean_up) do Gitlab::Metrics.measure(:import_export_clean_up) do
return unless File.directory?(path) next unless File.directory?(path)
clean_up_export_files clean_up_export_files
end end
......
...@@ -203,10 +203,11 @@ module NotificationRecipientService ...@@ -203,10 +203,11 @@ module NotificationRecipientService
attr_reader :action attr_reader :action
attr_reader :previous_assignee attr_reader :previous_assignee
attr_reader :skip_current_user attr_reader :skip_current_user
def initialize(target, current_user, action:, previous_assignee: nil, skip_current_user: true) def initialize(target, current_user, action:, custom_action: nil, previous_assignee: nil, skip_current_user: true)
@target = target @target = target
@current_user = current_user @current_user = current_user
@action = action @action = action
@custom_action = custom_action
@previous_assignee = previous_assignee @previous_assignee = previous_assignee
@skip_current_user = skip_current_user @skip_current_user = skip_current_user
end end
...@@ -236,7 +237,13 @@ module NotificationRecipientService ...@@ -236,7 +237,13 @@ module NotificationRecipientService
add_mentions(current_user, target: target) add_mentions(current_user, target: target)
# Add the assigned users, if any # Add the assigned users, if any
assignees = custom_action == :new_issue ? target.assignees : target.assignee assignees = case custom_action
when :new_issue
target.assignees
else
target.assignee
end
# We use the `:participating` notification level in order to match existing legacy behavior as captured # We use the `:participating` notification level in order to match existing legacy behavior as captured
# in existing specs (notification_service_spec.rb ~ line 507) # in existing specs (notification_service_spec.rb ~ line 507)
add_recipients(assignees, :participating, NotificationReason::ASSIGNED) if assignees add_recipients(assignees, :participating, NotificationReason::ASSIGNED) if assignees
......
...@@ -373,6 +373,20 @@ class NotificationService ...@@ -373,6 +373,20 @@ class NotificationService
end end
end end
def issue_due(issue)
recipients = NotificationRecipientService.build_recipients(
issue,
issue.author,
action: 'due',
custom_action: :issue_due,
skip_current_user: false
)
recipients.each do |recipient|
mailer.send(:issue_due_email, recipient.user.id, issue.id, recipient.reason).deliver_later
end
end
protected protected
def new_resource_email(target, method) def new_resource_email(target, method)
......
...@@ -137,7 +137,7 @@ module Projects ...@@ -137,7 +137,7 @@ module Projects
return true unless Gitlab.config.registry.enabled return true unless Gitlab.config.registry.enabled
ContainerRepository.build_root_repository(project).tap do |repository| ContainerRepository.build_root_repository(project).tap do |repository|
return repository.has_tags? ? repository.delete_tags! : true break repository.has_tags? ? repository.delete_tags! : true
end end
end end
......
...@@ -10,7 +10,7 @@ class RepositoryArchiveCleanUpService ...@@ -10,7 +10,7 @@ class RepositoryArchiveCleanUpService
def execute def execute
Gitlab::Metrics.measure(:repository_archive_clean_up) do Gitlab::Metrics.measure(:repository_archive_clean_up) do
return unless File.directory?(path) next unless File.directory?(path)
clean_up_old_archives clean_up_old_archives
clean_up_empty_directories clean_up_empty_directories
......
...@@ -159,7 +159,7 @@ module SystemNoteService ...@@ -159,7 +159,7 @@ module SystemNoteService
body = if noteable.time_estimate == 0 body = if noteable.time_estimate == 0
"removed time estimate" "removed time estimate"
else else
"changed time estimate to #{parsed_time}" "changed time estimate to #{parsed_time},"
end end
create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
......
...@@ -19,7 +19,7 @@ module TestHooks ...@@ -19,7 +19,7 @@ module TestHooks
error_message = catch(:validation_error) do error_message = catch(:validation_error) do
sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend
return hook.execute(sample_data, trigger_key) return hook.execute(sample_data, trigger_key) # rubocop:disable Cop/AvoidReturnFromBlocks
end end
error(error_message) error(error_message)
......
xml.title "#{current_user.name} issues" xml.title "#{current_user.name} issues"
xml.link href: url_for(params), rel: "self", type: "application/atom+xml" xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html" xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url xml.id issues_dashboard_url
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any? xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
- page_title _("Issues") - page_title _("Issues")
- @breadcrumb_link = issues_dashboard_path(assignee_id: current_user.id) - @breadcrumb_link = issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
.top-area .top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls .nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do = link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
......
xml.title "#{@group.name} issues" xml.title "#{@group.name} issues"
xml.link href: url_for(params), rel: "self", type: "application/atom+xml" xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html" xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url xml.id issues_group_url
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any? xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
......
- page_title "Issues" - page_title "Issues"
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
- if group_issues_count(state: 'all').zero? - if group_issues_count(state: 'all').zero?
= render 'shared/empty_states/issues', project_select_button: true = render 'shared/empty_states/issues', project_select_button: true
......
<%= yield -%> <%= yield -%>
--- -- <%# signature marker %>
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
<%= yield -%> <%= yield -%>
--- -- <%# signature marker %>
<% if @target_url -%> <% if @target_url -%>
<% if @reply_by_email -%> <% if @reply_by_email -%>
<%= "Reply to this email directly or view it on GitLab: #{@target_url}" -%> <%= "Reply to this email directly or view it on GitLab: #{@target_url}" -%>
......
%p.details
#{link_to @issue.author_name, user_url(@issue.author)}'s issue is due soon.
- if @issue.assignees.any?
%p
Assignee: #{@issue.assignee_list}
%p
This issue is due on: #{@issue.due_date.to_s(:medium)}
- if @issue.description
%div
= markdown(@issue.description, pipeline: :email, author: @issue.author)
The following issue is due on <%= @issue.due_date %>:
Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_list %>
<%= @issue.description %>
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?) - load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
- external_embed = local_assigns.fetch(:external_embed, false) - external_embed = local_assigns.fetch(:external_embed, false)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async - viewer_url = local_assigns.fetch(:viewer_url) { url_for(safe_params.merge(viewer: viewer.type, format: :json)) } if load_async
.blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) } .blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
- if render_error - if render_error
= render 'projects/blob/render_error', viewer: viewer = render 'projects/blob/render_error', viewer: viewer
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
= s_('Branches|Cant find HEAD commit for this branch') = s_('Branches|Cant find HEAD commit for this branch')
- if branch.name != @repository.root_ref - if branch.name != @repository.root_ref
.divergence-graph{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind), .divergence-graph.hidden-xs{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
default_branch: @repository.root_ref, default_branch: @repository.root_ref,
number_commits_ahead: diverging_count_label(number_commits_ahead) } } number_commits_ahead: diverging_count_label(number_commits_ahead) } }
.graph-side .graph-side
......
- diff_file = viewer.diff_file - diff_file = viewer.diff_file
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier)) - url = url_for(safe_params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
.nothing-here-block.diff-collapsed{ data: { diff_for_path: url } } .nothing-here-block.diff-collapsed{ data: { diff_for_path: url } }
This diff is collapsed. This diff is collapsed.
%a.click-to-expand Click to expand it. %a.click-to-expand Click to expand it.
= link_to params.merge(rss_url_options), class: 'btn btn-secondary append-right-10 has-tooltip', title: 'Subscribe' do = link_to safe_params.merge(rss_url_options), class: 'btn btn-secondary append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss') = icon('rss')
- if @can_bulk_update - if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-secondary append-right-10 js-bulk-update-toggle" = button_tag "Edit issues", class: "btn btn-secondary append-right-10 js-bulk-update-toggle"
......
xml.title "#{@project.name} issues" xml.title "#{@project.name} issues"
xml.link href: url_for(params), rel: "self", type: "application/atom+xml" xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: project_issues_url(@project), rel: "alternate", type: "text/html" xml.link href: project_issues_url(@project), rel: "alternate", type: "text/html"
xml.id project_issues_url(@project) xml.id project_issues_url(@project)
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any? xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- new_issue_email = @project.new_issuable_address(current_user, 'issue') - new_issue_email = @project.new_issuable_address(current_user, 'issue')
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues") = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
- if project_issues(@project).exists? - if project_issues(@project).exists?
%div{ class: (container_class) } %div{ class: (container_class) }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.d-none.alert.alert-danger.mr-compare-errors .d-none.alert.alert-danger.mr-compare-errors
.merge-request-branches.js-merge-request-new-compare.row.col-md-12{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) } .js-merge-request-new-compare.row.col-md-12{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.col-md-6.pl-0 .col-md-6.pl-0
.card.card-new-merge-request .card.card-new-merge-request
.card-header .card-header
......
...@@ -26,16 +26,16 @@ ...@@ -26,16 +26,16 @@
- else - else
%ul.merge-request-tabs.nav-links.no-top.no-bottom %ul.merge-request-tabs.nav-links.no-top.no-bottom
%li.commits-tab.active %li.commits-tab.active
= link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
Commits Commits
%span.badge.badge-pill= @commits.size %span.badge.badge-pill= @commits.size
- if @pipelines.any? - if @pipelines.any?
%li.builds-tab %li.builds-tab
= link_to url_for(params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do = link_to url_for(safe_params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
Pipelines Pipelines
%span.badge.badge-pill= @pipelines.size %span.badge.badge-pill= @pipelines.size
%li.diffs-tab %li.diffs-tab
= link_to url_for(params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do = link_to url_for(safe_params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes Changes
%span.badge.badge-pill= @merge_request.diff_size %span.badge.badge-pill= @merge_request.diff_size
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
- if @pipelines.any? - if @pipelines.any?
#pipelines.pipelines.tab-pane #pipelines.pipelines.tab-pane
= render 'projects/merge_requests/pipelines', endpoint: url_for(params.merge(action: 'pipelines', format: :json)), disable_initialization: true = render 'projects/merge_requests/pipelines', endpoint: url_for(safe_params.merge(action: 'pipelines', format: :json)), disable_initialization: true
.mr-loading-status .mr-loading-status
= spinner = spinner
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
- cronjob:stuck_import_jobs - cronjob:stuck_import_jobs
- cronjob:stuck_merge_jobs - cronjob:stuck_merge_jobs
- cronjob:trending_projects - cronjob:trending_projects
- cronjob:issue_due_scheduler
- gcp_cluster:cluster_install_app - gcp_cluster:cluster_install_app
- gcp_cluster:cluster_provision - gcp_cluster:cluster_provision
...@@ -39,6 +40,8 @@ ...@@ -39,6 +40,8 @@
- github_importer:github_import_stage_import_pull_requests - github_importer:github_import_stage_import_pull_requests
- github_importer:github_import_stage_import_repository - github_importer:github_import_stage_import_repository
- mail_scheduler:mail_scheduler_issue_due
- object_storage_upload - object_storage_upload
- object_storage:object_storage_background_move - object_storage:object_storage_background_move
- object_storage:object_storage_migrate_uploads - object_storage:object_storage_migrate_uploads
......
module MailSchedulerQueue
extend ActiveSupport::Concern
included do
queue_namespace :mail_scheduler
end
end
class IssueDueSchedulerWorker
include ApplicationWorker
include CronjobQueue
def perform
project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
MailScheduler::IssueDueWorker.bulk_perform_async(project_ids)
end
end
module MailScheduler
class IssueDueWorker
include ApplicationWorker
include MailSchedulerQueue
def perform(project_id)
notification_service = NotificationService.new
Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
notification_service.issue_due(issue)
end
end
end
end
...@@ -33,7 +33,7 @@ class PostReceive ...@@ -33,7 +33,7 @@ class PostReceive
unless @user unless @user
log("Triggered hook for non-existing user \"#{post_received.identifier}\"") log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
return false return false # rubocop:disable Cop/AvoidReturnFromBlocks
end end
if Gitlab::Git.tag_ref?(ref) if Gitlab::Git.tag_ref?(ref)
......
...@@ -38,7 +38,7 @@ class StuckCiJobsWorker ...@@ -38,7 +38,7 @@ class StuckCiJobsWorker
def drop_stuck(status, timeout) def drop_stuck(status, timeout)
search(status, timeout) do |build| search(status, timeout) do |build|
return unless build.stuck? break unless build.stuck?
drop_build :stuck, build, status, timeout drop_build :stuck, build, status, timeout
end end
......
---
title: Add cron job to email users on issue due date
merge_request: 17985
author: Stuart Nelson
type: added
---
title: Add a comma to the time estimate system notes
merge_request: 18326
author:
type: changed
---
title: Remove ahead/behind graphs on project branches on mobile
merge_request: 18415
author: Takuya Noguchi
type: other
---
title: Rubocop rule to avoid returning from a block
merge_request: 18000
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Use RFC 3676 mail signature delimiters
merge_request: 17979
author: Enrico Scholz
type: changed
---
title: Fix `Trace::HttpIO` can not render multi-byte chars
merge_request: 18417
author:
type: fixed
---
title: Partition job_queue_duration_seconds with jobs_running_for_project
merge_request: 17730
author:
type: changed
---
title: Check if a ref exists is done by Gitaly by default
merge_request:
author:
type: performance
...@@ -455,6 +455,10 @@ Settings.cron_jobs['pages_domain_verification_cron_worker'] ||= Settingslogic.ne ...@@ -455,6 +455,10 @@ Settings.cron_jobs['pages_domain_verification_cron_worker'] ||= Settingslogic.ne
Settings.cron_jobs['pages_domain_verification_cron_worker']['cron'] ||= '*/15 * * * *' Settings.cron_jobs['pages_domain_verification_cron_worker']['cron'] ||= '*/15 * * * *'
Settings.cron_jobs['pages_domain_verification_cron_worker']['job_class'] = 'PagesDomainVerificationCronWorker' Settings.cron_jobs['pages_domain_verification_cron_worker']['job_class'] = 'PagesDomainVerificationCronWorker'
Settings.cron_jobs['issue_due_scheduler_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['issue_due_scheduler_worker']['cron'] ||= '50 00 * * *'
Settings.cron_jobs['issue_due_scheduler_worker']['job_class'] = 'IssueDueSchedulerWorker'
# #
# Sidekiq # Sidekiq
# #
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
- [email_receiver, 2] - [email_receiver, 2]
- [emails_on_push, 2] - [emails_on_push, 2]
- [mailers, 2] - [mailers, 2]
- [mail_scheduler, 2]
- [invalid_gpg_signature_update, 2] - [invalid_gpg_signature_update, 2]
- [create_gpg_signature, 2] - [create_gpg_signature, 2]
- [rebase, 2] - [rebase, 2]
......
class AddIssueDueToNotificationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :notification_settings, :issue_due, :boolean
end
end
...@@ -1325,6 +1325,7 @@ ActiveRecord::Schema.define(version: 20180405142733) do ...@@ -1325,6 +1325,7 @@ ActiveRecord::Schema.define(version: 20180405142733) do
t.boolean "failed_pipeline" t.boolean "failed_pipeline"
t.boolean "success_pipeline" t.boolean "success_pipeline"
t.boolean "push_to_merge_request" t.boolean "push_to_merge_request"
t.boolean "issue_due"
end end
add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
......
...@@ -80,6 +80,7 @@ on projects and code. ...@@ -80,6 +80,7 @@ on projects and code.
- [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards. - [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards.
- [Snippets](user/snippets.md): Snippets allow you to create little bits of code. - [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
- [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis. - [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis.
- [Web IDE](user/project/web_ide/index.md)
#### Repositories #### Repositories
......
# Administrator documentation # Administrator documentation **[CORE ONLY]**
Learn how to administer your GitLab instance (Community Edition and Learn how to administer your GitLab instance (Community Edition and
Enterprise Edition). Enterprise Edition).
......
...@@ -23,6 +23,7 @@ new_issue ...@@ -23,6 +23,7 @@ new_issue
reopen_issue reopen_issue
close_issue close_issue
reassign_issue reassign_issue
issue_due
new_merge_request new_merge_request
push_to_merge_request push_to_merge_request
reopen_merge_request reopen_merge_request
...@@ -75,6 +76,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab ...@@ -75,6 +76,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
| `reopen_issue` | boolean | no | Enable/disable this notification | | `reopen_issue` | boolean | no | Enable/disable this notification |
| `close_issue` | boolean | no | Enable/disable this notification | | `close_issue` | boolean | no | Enable/disable this notification |
| `reassign_issue` | boolean | no | Enable/disable this notification | | `reassign_issue` | boolean | no | Enable/disable this notification |
| `issue_due` | boolean | no | Enable/disable this notification |
| `new_merge_request` | boolean | no | Enable/disable this notification | | `new_merge_request` | boolean | no | Enable/disable this notification |
| `push_to_merge_request` | boolean | no | Enable/disable this notification | | `push_to_merge_request` | boolean | no | Enable/disable this notification |
| `reopen_merge_request` | boolean | no | Enable/disable this notification | | `reopen_merge_request` | boolean | no | Enable/disable this notification |
...@@ -142,6 +144,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab ...@@ -142,6 +144,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab
| `reopen_issue` | boolean | no | Enable/disable this notification | | `reopen_issue` | boolean | no | Enable/disable this notification |
| `close_issue` | boolean | no | Enable/disable this notification | | `close_issue` | boolean | no | Enable/disable this notification |
| `reassign_issue` | boolean | no | Enable/disable this notification | | `reassign_issue` | boolean | no | Enable/disable this notification |
| `issue_due` | boolean | no | Enable/disable this notification |
| `new_merge_request` | boolean | no | Enable/disable this notification | | `new_merge_request` | boolean | no | Enable/disable this notification |
| `push_to_merge_request` | boolean | no | Enable/disable this notification | | `push_to_merge_request` | boolean | no | Enable/disable this notification |
| `reopen_merge_request` | boolean | no | Enable/disable this notification | | `reopen_merge_request` | boolean | no | Enable/disable this notification |
...@@ -166,6 +169,7 @@ Example responses: ...@@ -166,6 +169,7 @@ Example responses:
"reopen_issue": false, "reopen_issue": false,
"close_issue": false, "close_issue": false,
"reassign_issue": false, "reassign_issue": false,
"issue_due": false,
"new_merge_request": false, "new_merge_request": false,
"push_to_merge_request": false, "push_to_merge_request": false,
"reopen_merge_request": false, "reopen_merge_request": false,
......
...@@ -133,11 +133,19 @@ roughly be as follows: ...@@ -133,11 +133,19 @@ roughly be as follows:
1. Release B: 1. Release B:
1. Deploy code so that the application starts using the new column and stops 1. Deploy code so that the application starts using the new column and stops
scheduling jobs for newly created data. scheduling jobs for newly created data.
1. In a post-deployment migration you'll need to ensure no jobs remain. To do 1. In a post-deployment migration you'll need to ensure no jobs remain.
so you can use `Gitlab::BackgroundMigration.steal` to process any remaining 1. Use `Gitlab::BackgroundMigration.steal` to process any remaining
jobs before continuing. jobs in Sidekiq.
1. Reschedule the migration to be run directly (i.e. not through Sidekiq)
on any rows that weren't migrated by Sidekiq. This can happen if, for
instance, Sidekiq received a SIGKILL, or if a particular batch failed
enough times to be marked as dead.
1. Remove the old column. 1. Remove the old column.
This may also require a bump to the [import/export version][import-export], if
importing a project from a prior version of GitLab requires the data to be in
the new format.
## Example ## Example
To explain all this, let's use the following example: the table `services` has a To explain all this, let's use the following example: the table `services` has a
...@@ -296,3 +304,4 @@ for more details. ...@@ -296,3 +304,4 @@ for more details.
[migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md [migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md
[issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351 [issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351
[reliable-sidekiq]: https://gitlab.com/gitlab-org/gitlab-ce/issues/36791 [reliable-sidekiq]: https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
[import-export]: ../user/project/settings/import_export.md
...@@ -157,6 +157,39 @@ below. ...@@ -157,6 +157,39 @@ below.
Otherwise, leave this mention out. Otherwise, leave this mention out.
### Product badges
When a feature is available in EE-only tiers, add the corresponding tier according to the
feature availability:
- For GitLab Starter and GitLab.com Bronze: `**[STARTER]**`
- For GitLab Premium and GitLab.com Silver: `**[PREMIUM]**`
- For GitLab Ultimate and GitLab.com Gold: `**[ULTIMATE]**`
- For GitLab Core and GitLab.com Free: `**[CORE]**`
To exclude GitLab.com tiers (when the feature is not available in GitLab.com), add the
keyword "only":
- For GitLab Starter: `**[STARTER ONLY]**`
- For GitLab Premium: `**[PREMIUM ONLY]**`
- For GitLab Ultimate: `**[ULTIMATE ONLY]**`
- For GitLab Core: `**[CORE ONLY]**`
The tier should be ideally added to headers, so that the full badge will be displayed.
But it can be also mentioned from paragraphs, list items, and table cells. For these cases,
the tier mention will be represented by an orange question mark.
E.g., `**[STARTER]**` renders **[STARTER]**, `**[STARTER ONLY]**` renders **[STARTER ONLY]**.
The absence of tiers' mentions mean that the feature is available in GitLab Core,
GitLab.com Free, and higher tiers.
#### How it works
Introduced by [!244](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/244),
the special markup `**[STARTER]**` will generate a `span` element to trigger the
badges and tooltips (`<span class="badge-trigger starter">`). When the keyword
"only" is added, the corresponding GitLab.com badge will not be displayed.
### GitLab Restart ### GitLab Restart
There are many cases that a restart/reconfigure of GitLab is required. To There are many cases that a restart/reconfigure of GitLab is required. To
......
...@@ -245,10 +245,7 @@ To enable this feature, navigate to the group settings page. Select ...@@ -245,10 +245,7 @@ To enable this feature, navigate to the group settings page. Select
![Checkbox for share with group lock](img/share_with_group_lock.png) ![Checkbox for share with group lock](img/share_with_group_lock.png)
#### Member Lock #### Member Lock **[STARTER]**
> Available in [GitLab Starter](https://about.gitlab.com/products/) and
[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
With **Member Lock** it is possible to lock membership in project to the With **Member Lock** it is possible to lock membership in project to the
level of members in group. level of members in group.
...@@ -259,8 +256,8 @@ Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html# ...@@ -259,8 +256,8 @@ Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#
- **Projects**: view all projects within that group, add members to each project, - **Projects**: view all projects within that group, add members to each project,
access each project's settings, and remove any project from the same screen. access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) - **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) to your group.
and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Starter](https://about.gitlab.com/products/).) - **Push rules**: configure [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group. **[STARTER]**
- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events) - **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
for the group (GitLab admins only, available in [GitLab Starter][ee]). for the group. **[STARTER ONLY]**
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group - **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
...@@ -17,7 +17,7 @@ When you create a project in GitLab, you'll have access to a large number of ...@@ -17,7 +17,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Issue tracker](issues/index.md): Discuss implementations with your team within issues - [Issue tracker](issues/index.md): Discuss implementations with your team within issues
- [Issue Boards](issue_board.md): Organize and prioritize your workflow - [Issue Boards](issue_board.md): Organize and prioritize your workflow
- [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) (**Starter/Premium**): Allow your teams to create their own workflows (Issue Boards) for the same project - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
- [Repositories](repository/index.md): Host your code in a fully - [Repositories](repository/index.md): Host your code in a fully
integrated platform integrated platform
- [Branches](repository/branches/index.md): use Git branching strategies to - [Branches](repository/branches/index.md): use Git branching strategies to
...@@ -30,8 +30,8 @@ integrated platform ...@@ -30,8 +30,8 @@ integrated platform
- [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry. - [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry.
- [Merge Requests](merge_requests/index.md): Apply your branching - [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team strategy and get reviewed by your team
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) (**Starter/Premium**): Ask for approval before - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before
implementing a change implementing a change **[STARTER]**
- [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md): - [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
Your Git diff tool right from GitLab's UI Your Git diff tool right from GitLab's UI
- [Review Apps](../../ci/review_apps/index.md): Live preview the results - [Review Apps](../../ci/review_apps/index.md): Live preview the results
......
...@@ -35,5 +35,9 @@ Due dates also appear in your [todos list](../../../workflow/todos.md). ...@@ -35,5 +35,9 @@ Due dates also appear in your [todos list](../../../workflow/todos.md).
![Issues with due dates in the todos](img/due_dates_todos.png) ![Issues with due dates in the todos](img/due_dates_todos.png)
The day before an open issue is due, an email will be sent to all participants
of the issue. Both the due date and the day before are calculated using the
server's timezone.
[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614 [ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
[permissions]: ../../permissions.md#project [permissions]: ../../permissions.md#project
...@@ -41,10 +41,7 @@ it's reassigned to someone else to take it from there. ...@@ -41,10 +41,7 @@ it's reassigned to someone else to take it from there.
if a user is not member of that project, it can only be if a user is not member of that project, it can only be
assigned to them if they created the issue themselves. assigned to them if they created the issue themselves.
##### 3.1. Multiple Assignees ##### 3.1. Multiple Assignees **[STARTER]**
> Available in [GitLab Starter](https://about.gitlab.com/products/) and
[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
Often multiple people likely work on the same issue together, Often multiple people likely work on the same issue together,
which can especially be difficult to track in large teams which can especially be difficult to track in large teams
...@@ -89,10 +86,7 @@ but they are immediately available to all projects in the group. ...@@ -89,10 +86,7 @@ but they are immediately available to all projects in the group.
> **Tip:** > **Tip:**
if the label doesn't exist yet, when you click **Edit**, it opens a dropdown menu from which you can select **Create new label**. if the label doesn't exist yet, when you click **Edit**, it opens a dropdown menu from which you can select **Create new label**.
#### 8. Weight #### 8. Weight **[STARTER]**
> Available in [GitLab Starter](https://about.gitlab.com/products/) and
[GitLab.com Bronze](https://about.gitlab.com/gitlab-com/).
- Attribute a weight (in a 0 to 9 range) to that issue. Easy to complete - Attribute a weight (in a 0 to 9 range) to that issue. Easy to complete
should weight 1 and very hard to complete should weight 9. should weight 1 and very hard to complete should weight 9.
......
...@@ -9,8 +9,7 @@ Labels allow you to categorize issues or merge requests using descriptive titles ...@@ -9,8 +9,7 @@ Labels allow you to categorize issues or merge requests using descriptive titles
In GitLab, you can create project and group labels: In GitLab, you can create project and group labels:
- **Project labels** can be assigned to issues or merge requests in that project only. - **Project labels** can be assigned to issues or merge requests in that project only.
- **Group labels** can be assigned to any issue or merge request of any project in that group or subgroup. - **Group labels** can be assigned to any issue or merge request of any project in that group or any subgroups of the group.
- In the [future](https://gitlab.com/gitlab-org/gitlab-ce/issues/40915), you will be able to assign group labels to issues and merge reqeusts of projects in [subgroups](../group/subgroups/index.md).
## Creating labels ## Creating labels
......
...@@ -32,10 +32,10 @@ With GitLab merge requests, you can: ...@@ -32,10 +32,10 @@ With GitLab merge requests, you can:
With **[GitLab Enterprise Edition][ee]**, you can also: With **[GitLab Enterprise Edition][ee]**, you can also:
- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Premium) - View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) **[PREMIUM]**
- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Starter) - Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers **[STARTER]**
- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Starter) - [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history **[STARTER]**
- Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter) - Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) **[STARTER]**
## Use cases ## Use cases
...@@ -43,7 +43,7 @@ A. Consider you are a software developer working in a team: ...@@ -43,7 +43,7 @@ A. Consider you are a software developer working in a team:
1. You checkout a new branch, and submit your changes through a merge request 1. You checkout a new branch, and submit your changes through a merge request
1. You gather feedback from your team 1. You gather feedback from your team
1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter) 1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) **[STARTER]**
1. You build and test your changes with GitLab CI/CD 1. You build and test your changes with GitLab CI/CD
1. You request the approval from your manager 1. You request the approval from your manager
1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter) 1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
...@@ -56,7 +56,7 @@ B. Consider you're a web developer writing a webpage for your company's: ...@@ -56,7 +56,7 @@ B. Consider you're a web developer writing a webpage for your company's:
1. You gather feedback from your reviewers 1. You gather feedback from your reviewers
1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md) 1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md)
1. You request your web designers for their implementation 1. You request your web designers for their implementation
1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Starter) 1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager **[STARTER]**
1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Starter) 1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Starter)
1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production 1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production
......
...@@ -34,7 +34,7 @@ Set up your project's merge request settings: ...@@ -34,7 +34,7 @@ Set up your project's merge request settings:
- Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)). - Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)).
- Merge request [description templates](../description_templates.md#description-templates). - Merge request [description templates](../description_templates.md#description-templates).
- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals), _available in [GitLab Starter](https://about.gitlab.com/products/)_. - Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals). **[STARTER]**
- Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md). - Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
- Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved). - Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved).
......
# Web IDE # Web IDE
> Introduced in [GitLab Ultimate][ee] 10.4. > [Introduced in](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) [GitLab Ultimate][ee] 10.4.
> Brought to [GitLab CE][ce] in 10.7. > [Brought to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7.
The Web IDE makes it faster and easier to contribute changes to your projects The Web IDE makes it faster and easier to contribute changes to your projects
by providing an advanced editor with commit staging. by providing an advanced editor with commit staging.
...@@ -30,5 +30,4 @@ list. ...@@ -30,5 +30,4 @@ list.
An additional review mode is available when you open a merge request, which An additional review mode is available when you open a merge request, which
shows you a preview of the merge request diff if you commit your changes. shows you a preview of the merge request diff if you commit your changes.
[ee]: https://about.gitlab.com/products/ [ee]: https://about.gitlab.com/pricing/
[ce]: https://about.gitlab.com/products/
...@@ -86,6 +86,7 @@ In most of the below cases, the notification will be sent to: ...@@ -86,6 +86,7 @@ In most of the below cases, the notification will be sent to:
| Close issue | | | Close issue | |
| Reassign issue | The above, plus the old assignee | | Reassign issue | The above, plus the old assignee |
| Reopen issue | | | Reopen issue | |
| Due issue | Participants and Custom notification level with this event selected |
| New merge request | | | New merge request | |
| Push to merge request | Participants and Custom notification level with this event selected | | Push to merge request | Participants and Custom notification level with this event selected |
| Reassign merge request | The above, plus the old assignee | | Reassign merge request | The above, plus the old assignee |
...@@ -96,15 +97,14 @@ In most of the below cases, the notification will be sent to: ...@@ -96,15 +97,14 @@ In most of the below cases, the notification will be sent to:
| Failed pipeline | The author of the pipeline | | Failed pipeline | The author of the pipeline |
| Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set | | Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set |
In addition, if the title or description of an Issue or Merge Request is In addition, if the title or description of an Issue or Merge Request is
changed, notifications will be sent to any **new** mentions by `@username` as changed, notifications will be sent to any **new** mentions by `@username` as
if they had been mentioned in the original text. if they had been mentioned in the original text.
You won't receive notifications for Issues, Merge Requests or Milestones You won't receive notifications for Issues, Merge Requests or Milestones created
created by yourself. You will only receive automatic notifications when by yourself (except when an issue is due). You will only receive automatic
somebody else comments or adds changes to the ones that you've created or notifications when somebody else comments or adds changes to the ones that
mentions you. you've created or mentions you.
### Email Headers ### Email Headers
...@@ -122,7 +122,7 @@ Notification emails include headers that provide extra content about the notific ...@@ -122,7 +122,7 @@ Notification emails include headers that provide extra content about the notific
| X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc | | X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc |
#### X-GitLab-NotificationReason #### X-GitLab-NotificationReason
This header holds the reason for the notification to have been sent out, This header holds the reason for the notification to have been sent out,
where reason can be `mentioned`, `assigned`, `own_activity`, etc. where reason can be `mentioned`, `assigned`, `own_activity`, etc.
Only one reason is sent out according to its priority: Only one reason is sent out according to its priority:
- `own_activity` - `own_activity`
...@@ -130,7 +130,7 @@ Only one reason is sent out according to its priority: ...@@ -130,7 +130,7 @@ Only one reason is sent out according to its priority:
- `mentioned` - `mentioned`
The reason in this header will also be shown in the footer of the notification email. For example an email with the The reason in this header will also be shown in the footer of the notification email. For example an email with the
reason `assigned` will have this sentence in the footer: reason `assigned` will have this sentence in the footer:
`"You are receiving this email because you have been assigned an item on {configured GitLab hostname}"` `"You are receiving this email because you have been assigned an item on {configured GitLab hostname}"`
**Note: Only reasons listed above have been implemented so far** **Note: Only reasons listed above have been implemented so far**
......
...@@ -25,7 +25,7 @@ module API ...@@ -25,7 +25,7 @@ module API
get ":id/#{noteables_str}/:noteable_id/discussions" do get ":id/#{noteables_str}/:noteable_id/discussions" do
noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
return not_found!("Discussions") unless can?(current_user, noteable_read_ability_name(noteable), noteable) break not_found!("Discussions") unless can?(current_user, noteable_read_ability_name(noteable), noteable)
notes = noteable.notes notes = noteable.notes
.inc_relations_for_view .inc_relations_for_view
...@@ -50,7 +50,7 @@ module API ...@@ -50,7 +50,7 @@ module API
notes = readable_discussion_notes(noteable, params[:discussion_id]) notes = readable_discussion_notes(noteable, params[:discussion_id])
if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable) if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable)
return not_found!("Discussion") break not_found!("Discussion")
end end
discussion = Discussion.build(notes, noteable) discussion = Discussion.build(notes, noteable)
...@@ -98,7 +98,7 @@ module API ...@@ -98,7 +98,7 @@ module API
notes = readable_discussion_notes(noteable, params[:discussion_id]) notes = readable_discussion_notes(noteable, params[:discussion_id])
if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable) if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable)
return not_found!("Notes") break not_found!("Notes")
end end
present notes, with: Entities::Note present notes, with: Entities::Note
...@@ -117,8 +117,8 @@ module API ...@@ -117,8 +117,8 @@ module API
noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id]) notes = readable_discussion_notes(noteable, params[:discussion_id])
return not_found!("Discussion") if notes.empty? break not_found!("Discussion") if notes.empty?
return bad_request!("Discussion is an individual note.") unless notes.first.part_of_discussion? break bad_request!("Discussion is an individual note.") unless notes.first.part_of_discussion?
opts = { opts = {
note: params[:body], note: params[:body],
......
...@@ -31,7 +31,7 @@ module API ...@@ -31,7 +31,7 @@ module API
key = params[:key] key = params[:key]
variable = user_group.variables.find_by(key: key) variable = user_group.variables.find_by(key: key)
return not_found!('GroupVariable') unless variable break not_found!('GroupVariable') unless variable
present variable, with: Entities::Variable present variable, with: Entities::Variable
end end
...@@ -67,7 +67,7 @@ module API ...@@ -67,7 +67,7 @@ module API
put ':id/variables/:key' do put ':id/variables/:key' do
variable = user_group.variables.find_by(key: params[:key]) variable = user_group.variables.find_by(key: params[:key])
return not_found!('GroupVariable') unless variable break not_found!('GroupVariable') unless variable
variable_params = declared_params(include_missing: false).except(:key) variable_params = declared_params(include_missing: false).except(:key)
......
...@@ -50,7 +50,7 @@ module API ...@@ -50,7 +50,7 @@ module API
access_checker.check(params[:action], params[:changes]) access_checker.check(params[:action], params[:changes])
@project ||= access_checker.project @project ||= access_checker.project
rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e
return { status: false, message: e.message } break { status: false, message: e.message }
end end
log_user_activity(actor) log_user_activity(actor)
...@@ -142,21 +142,21 @@ module API ...@@ -142,21 +142,21 @@ module API
if key if key
key.update_last_used_at key.update_last_used_at
else else
return { 'success' => false, 'message' => 'Could not find the given key' } break { 'success' => false, 'message' => 'Could not find the given key' }
end end
if key.is_a?(DeployKey) if key.is_a?(DeployKey)
return { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
end end
user = key.user user = key.user
unless user unless user
return { success: false, message: 'Could not find a user for the given key' } break { success: false, message: 'Could not find a user for the given key' }
end end
unless user.two_factor_enabled? unless user.two_factor_enabled?
return { success: false, message: 'Two-factor authentication is not enabled for this user' } break { success: false, message: 'Two-factor authentication is not enabled for this user' }
end end
codes = nil codes = nil
......
...@@ -310,7 +310,7 @@ module API ...@@ -310,7 +310,7 @@ module API
issue = find_project_issue(params[:issue_iid]) issue = find_project_issue(params[:issue_iid])
return not_found!('UserAgentDetail') unless issue.user_agent_detail break not_found!('UserAgentDetail') unless issue.user_agent_detail
present issue.user_agent_detail, with: Entities::UserAgentDetail present issue.user_agent_detail, with: Entities::UserAgentDetail
end end
......
...@@ -77,7 +77,7 @@ module API ...@@ -77,7 +77,7 @@ module API
build = find_build!(params[:job_id]) build = find_build!(params[:job_id])
authorize!(:update_build, build) authorize!(:update_build, build)
return not_found!(build) unless build.artifacts? break not_found!(build) unless build.artifacts?
build.keep_artifacts! build.keep_artifacts!
......
...@@ -120,7 +120,7 @@ module API ...@@ -120,7 +120,7 @@ module API
build = find_build!(params[:job_id]) build = find_build!(params[:job_id])
authorize!(:update_build, build) authorize!(:update_build, build)
return forbidden!('Job is not retryable') unless build.retryable? break forbidden!('Job is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user) build = Ci::Build.retry(build, current_user)
...@@ -138,7 +138,7 @@ module API ...@@ -138,7 +138,7 @@ module API
build = find_build!(params[:job_id]) build = find_build!(params[:job_id])
authorize!(:erase_build, build) authorize!(:erase_build, build)
return forbidden!('Job is not erasable!') unless build.erasable? break forbidden!('Job is not erasable!') unless build.erasable?
build.erase(erased_by: current_user) build.erase(erased_by: current_user)
present build, with: Entities::Job present build, with: Entities::Job
......
...@@ -145,7 +145,7 @@ module API ...@@ -145,7 +145,7 @@ module API
snippet = Snippet.find_by!(id: params[:snippet_id], project_id: params[:id]) snippet = Snippet.find_by!(id: params[:snippet_id], project_id: params[:id])
return not_found!('UserAgentDetail') unless snippet.user_agent_detail break not_found!('UserAgentDetail') unless snippet.user_agent_detail
present snippet.user_agent_detail, with: Entities::UserAgentDetail present snippet.user_agent_detail, with: Entities::UserAgentDetail
end end
......
...@@ -402,7 +402,7 @@ module API ...@@ -402,7 +402,7 @@ module API
end end
unless user_project.allowed_to_share_with_group? unless user_project.allowed_to_share_with_group?
return render_api_error!("The project sharing with group is disabled", 400) break render_api_error!("The project sharing with group is disabled", 400)
end end
link = user_project.project_group_links.new(declared_params(include_missing: false)) link = user_project.project_group_links.new(declared_params(include_missing: false))
......
...@@ -29,7 +29,7 @@ module API ...@@ -29,7 +29,7 @@ module API
project.runners.create(attributes) project.runners.create(attributes)
end end
return forbidden! unless runner break forbidden! unless runner
if runner.id if runner.id
present runner, with: Entities::RunnerRegistrationDetails present runner, with: Entities::RunnerRegistrationDetails
...@@ -83,7 +83,7 @@ module API ...@@ -83,7 +83,7 @@ module API
if current_runner.runner_queue_value_latest?(params[:last_update]) if current_runner.runner_queue_value_latest?(params[:last_update])
header 'X-GitLab-Last-Update', params[:last_update] header 'X-GitLab-Last-Update', params[:last_update]
Gitlab::Metrics.add_event(:build_not_found_cached) Gitlab::Metrics.add_event(:build_not_found_cached)
return no_content! break no_content!
end end
new_update = current_runner.ensure_runner_queue_value new_update = current_runner.ensure_runner_queue_value
...@@ -152,7 +152,7 @@ module API ...@@ -152,7 +152,7 @@ module API
stream_size = job.trace.append(request.body.read, content_range[0].to_i) stream_size = job.trace.append(request.body.read, content_range[0].to_i)
if stream_size < 0 if stream_size < 0
return error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{-stream_size}" }) break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{-stream_size}" })
end end
status 202 status 202
......
...@@ -94,7 +94,7 @@ module API ...@@ -94,7 +94,7 @@ module API
end end
put ':id' do put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id)) snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet authorize! :update_personal_snippet, snippet
...@@ -120,7 +120,7 @@ module API ...@@ -120,7 +120,7 @@ module API
end end
delete ':id' do delete ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id)) snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
authorize! :destroy_personal_snippet, snippet authorize! :destroy_personal_snippet, snippet
...@@ -135,7 +135,7 @@ module API ...@@ -135,7 +135,7 @@ module API
end end
get ":id/raw" do get ":id/raw" do
snippet = snippets_for_current_user.find_by(id: params.delete(:id)) snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
env['api.format'] = :txt env['api.format'] = :txt
content_type 'text/plain' content_type 'text/plain'
...@@ -153,7 +153,7 @@ module API ...@@ -153,7 +153,7 @@ module API
snippet = Snippet.find_by!(id: params[:id]) snippet = Snippet.find_by!(id: params[:id])
return not_found!('UserAgentDetail') unless snippet.user_agent_detail break not_found!('UserAgentDetail') unless snippet.user_agent_detail
present snippet.user_agent_detail, with: Entities::UserAgentDetail present snippet.user_agent_detail, with: Entities::UserAgentDetail
end end
......
...@@ -62,7 +62,7 @@ module API ...@@ -62,7 +62,7 @@ module API
authorize! :admin_build, user_project authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger break not_found!('Trigger') unless trigger
present trigger, with: Entities::Trigger present trigger, with: Entities::Trigger
end end
...@@ -99,7 +99,7 @@ module API ...@@ -99,7 +99,7 @@ module API
authorize! :admin_build, user_project authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger break not_found!('Trigger') unless trigger
if trigger.update(declared_params(include_missing: false)) if trigger.update(declared_params(include_missing: false))
present trigger, with: Entities::Trigger present trigger, with: Entities::Trigger
...@@ -119,7 +119,7 @@ module API ...@@ -119,7 +119,7 @@ module API
authorize! :admin_build, user_project authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger break not_found!('Trigger') unless trigger
if trigger.update(owner: current_user) if trigger.update(owner: current_user)
status :ok status :ok
...@@ -140,7 +140,7 @@ module API ...@@ -140,7 +140,7 @@ module API
authorize! :admin_build, user_project authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger break not_found!('Trigger') unless trigger
destroy_conditionally!(trigger) destroy_conditionally!(trigger)
end end
......
...@@ -51,7 +51,7 @@ module API ...@@ -51,7 +51,7 @@ module API
get ':id/repository/commits/:sha/builds' do get ':id/repository/commits/:sha/builds' do
authorize_read_builds! authorize_read_builds!
return not_found! unless user_project.commit(params[:sha]) break not_found! unless user_project.commit(params[:sha])
pipelines = user_project.pipelines.where(sha: params[:sha]) pipelines = user_project.pipelines.where(sha: params[:sha])
builds = user_project.builds.where(pipeline: pipelines).order('id DESC') builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
...@@ -153,7 +153,7 @@ module API ...@@ -153,7 +153,7 @@ module API
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:update_build, build) authorize!(:update_build, build)
return forbidden!('Build is not retryable') unless build.retryable? break forbidden!('Build is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user) build = Ci::Build.retry(build, current_user)
...@@ -171,7 +171,7 @@ module API ...@@ -171,7 +171,7 @@ module API
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:erase_build, build) authorize!(:erase_build, build)
return forbidden!('Build is not erasable!') unless build.erasable? break forbidden!('Build is not erasable!') unless build.erasable?
build.erase(erased_by: current_user) build.erase(erased_by: current_user)
present build, with: ::API::V3::Entities::Build present build, with: ::API::V3::Entities::Build
...@@ -188,7 +188,7 @@ module API ...@@ -188,7 +188,7 @@ module API
build = get_build!(params[:build_id]) build = get_build!(params[:build_id])
authorize!(:update_build, build) authorize!(:update_build, build)
return not_found!(build) unless build.artifacts? break not_found!(build) unless build.artifacts?
build.keep_artifacts! build.keep_artifacts!
......
...@@ -423,7 +423,7 @@ module API ...@@ -423,7 +423,7 @@ module API
end end
unless user_project.allowed_to_share_with_group? unless user_project.allowed_to_share_with_group?
return render_api_error!("The project sharing with group is disabled", 400) break render_api_error!("The project sharing with group is disabled", 400)
end end
link = user_project.project_group_links.new(declared_params(include_missing: false)) link = user_project.project_group_links.new(declared_params(include_missing: false))
......
...@@ -90,7 +90,7 @@ module API ...@@ -90,7 +90,7 @@ module API
end end
put ':id' do put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id)) snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet authorize! :update_personal_snippet, snippet
...@@ -114,7 +114,7 @@ module API ...@@ -114,7 +114,7 @@ module API
end end
delete ':id' do delete ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id)) snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
authorize! :destroy_personal_snippet, snippet authorize! :destroy_personal_snippet, snippet
snippet.destroy snippet.destroy
...@@ -129,7 +129,7 @@ module API ...@@ -129,7 +129,7 @@ module API
end end
get ":id/raw" do get ":id/raw" do
snippet = snippets_for_current_user.find_by(id: params.delete(:id)) snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
env['api.format'] = :txt env['api.format'] = :txt
content_type 'text/plain' content_type 'text/plain'
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment