Commit 581c10e3 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 68d3f33d
......@@ -116,16 +116,20 @@ export default {
// We have 10+ awarded user, join them with comma and add `and x more`.
if (remainingAwardList.length) {
title = sprintf(__(`%{listToShow}, and %{awardsListLength} more.`), {
listToShow: namesToShow.join(', '),
awardsListLength: remainingAwardList.length,
});
title = sprintf(
__(`%{listToShow}, and %{awardsListLength} more.`),
{
listToShow: namesToShow.join(', '),
awardsListLength: remainingAwardList.length,
},
false,
);
} else if (namesToShow.length > 1) {
// Join all names with comma but not the last one, it will be added with and text.
title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
// If we have more than 2 users we need an extra comma before and text.
title += namesToShow.length > 2 ? ',' : '';
title += sprintf(__(` and %{sliced}`), { sliced: namesToShow.slice(-1) }); // Append and text
title += sprintf(__(` and %{sliced}`), { sliced: namesToShow.slice(-1) }, false); // Append and text
} else {
// We have only 2 users so join them with and.
title = namesToShow.join(__(' and '));
......
......@@ -1052,18 +1052,19 @@ class Repository
return rebase_deprecated(user, merge_request)
end
MergeRequest.transaction do
raw.rebase(
user,
merge_request.id,
branch: merge_request.source_branch,
branch_sha: merge_request.source_branch_sha,
remote_repository: merge_request.target_project.repository.raw,
remote_branch: merge_request.target_branch
) do |commit_id|
merge_request.update!(rebase_commit_sha: commit_id, merge_error: nil)
end
raw.rebase(
user,
merge_request.id,
branch: merge_request.source_branch,
branch_sha: merge_request.source_branch_sha,
remote_repository: merge_request.target_project.repository.raw,
remote_branch: merge_request.target_branch
) do |commit_id|
merge_request.update!(rebase_commit_sha: commit_id, merge_error: nil)
end
rescue StandardError => error
merge_request.update!(rebase_commit_sha: nil)
raise error
end
def squash(user, merge_request, message)
......
......@@ -13,7 +13,6 @@ class PersonalSnippetPolicy < BasePolicy
rule { is_author | admin }.policy do
enable :read_personal_snippet
enable :update_personal_snippet
enable :destroy_personal_snippet
enable :admin_personal_snippet
enable :create_note
end
......
---
title: Fix award emoji tooltip being escaped twice if multiple people voted
merge_request: 19273
author: Brian T
type: fixed
---
title: Remove destroy_personal_snippet ability
merge_request: 20717
author:
type: fixed
---
title: Remove DB transaction from Rebase operation
merge_request: 20739
author:
type: fixed
......@@ -31,7 +31,7 @@ enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
enable_sidekiq_memory_killer = ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.nonzero?
use_sidekiq_daemon_memory_killer = ENV["SIDEKIQ_DAEMON_MEMORY_KILLER"].to_i.nonzero?
use_sidekiq_legacy_memory_killer = !use_sidekiq_daemon_memory_killer
use_request_store = ENV['SIDEKIQ_REQUEST_STORE'].to_i.nonzero?
use_request_store = ENV.fetch('SIDEKIQ_REQUEST_STORE', 1).to_i.nonzero?
Sidekiq.configure_server do |config|
config.redis = queues_config_hash
......
......@@ -51,14 +51,16 @@ def presented_no_changelog_labels
NO_CHANGELOG_LABELS.map { |label| "~#{label}" }.join(', ')
end
def sanitized_mr_title
gitlab.mr_json["title"].gsub(/^WIP: */, '').gsub(/`/, '\\\`')
end
changelog_needed = (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
changelog_found = git.added_files.find { |path| path =~ %r{\A(ee/)?(changelogs/unreleased)(-ee)?/} }
mr_title = gitlab.mr_json["title"].gsub(/^WIP: */, '')
if git.modified_files.include?("CHANGELOG.md")
fail "**CHANGELOG.md was edited.** Please remove the additions and create a CHANGELOG entry.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: mr_title, labels: presented_no_changelog_labels)
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: sanitized_mr_title, labels: presented_no_changelog_labels)
end
if changelog_needed
......@@ -66,6 +68,6 @@ if changelog_needed
check_changelog(changelog_found)
else
message "**[CHANGELOG missing](https://docs.gitlab.com/ce/development/changelog.html)**: If this merge request [doesn't need a CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html#what-warrants-a-changelog-entry), feel free to ignore this message.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: mr_title, labels: presented_no_changelog_labels)
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: sanitized_mr_title, labels: presented_no_changelog_labels)
end
end
......@@ -49,6 +49,13 @@ branch names globally in Push Rules, you can now sleep without the anxiety
of your developers' mistakes. Every branch that doesn't match your push rule
will get rejected.
### Custom Push Rules **(CORE ONLY)**
It's possible to create custom push rules rather than the push rules available in
**Admin area > Push Rules** by using more advanced server-side Git hooks.
See [custom server-side Git hooks](../administration/custom_hooks.md) for more information.
## Enabling push rules
NOTE: **Note:**
......
......@@ -75,7 +75,7 @@ To define specs for each environment:
1. Set the status and rollout strategy of the additional spec. This status and rollout strategy combination takes precedence over the default spec since we always use the most specific match available.
1. Click **Create feature flag** or **Update feature flag**.
![Feature flag specs list](img/specs_list.png)
![Feature flag specs list](img/specs_list_v12_6.png)
NOTE: **NOTE**
We'd highly recommend you to use the [Environment](../../../ci/environments.md)
......@@ -119,17 +119,15 @@ CAUTION: **Caution:**
If this strategy is selected, then the Unleash client **must** be given a user
ID for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
### Target users strategy
#### User IDs
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/8240) in GitLab 12.2.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/8240) in GitLab 12.2. [Updated](https://gitlab.com/gitlab-org/gitlab/issues/34363) to be defined per environment in GitLab 12.6.
A feature flag may be enabled for a list of target users. It is implemented
using the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
activation strategy.
The feature will always be enabled for all users in the list across all environments even if the matching environment spec **Status** is disabled.
![Feature flag target users](img/target_users_v12_2.png)
User IDs should be a comma separated list of values. For example, `user@example.com, user2@example.com`, or `username1,username2,username3`, etc.
CAUTION: **Caution:**
The Unleash client **must** be given a user ID for the feature to be enabled for
......
......@@ -131,7 +131,7 @@ module API
snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet
authorize! :destroy_personal_snippet, snippet
authorize! :admin_personal_snippet, snippet
destroy_conditionally!(snippet)
end
......
import { shallowMount } from '@vue/test-utils';
import MonitoringComponent from '~/environments/components/environment_monitoring.vue';
import Icon from '~/vue_shared/components/icon.vue';
describe('Monitoring Component', () => {
let wrapper;
const monitoringUrl = 'https://gitlab.com';
const createWrapper = () => {
wrapper = shallowMount(MonitoringComponent, {
sync: false,
attachToDocument: true,
propsData: {
monitoringUrl,
},
});
};
const findIcons = () => wrapper.findAll(Icon);
const findIconsByName = name => findIcons().filter(icon => icon.props('name') === name);
beforeEach(() => {
createWrapper();
});
describe('computed', () => {
it('title', () => {
expect(wrapper.vm.title).toBe('Monitoring');
});
});
it('should render a link to environment monitoring page', () => {
expect(wrapper.attributes('href')).toEqual(monitoringUrl);
expect(findIconsByName('chart').length).toBe(1);
expect(wrapper.attributes('data-original-title')).toBe('Monitoring');
expect(wrapper.attributes('aria-label')).toBe('Monitoring');
});
});
import $ from 'jquery';
import { shallowMount } from '@vue/test-utils';
import StopComponent from '~/environments/components/environment_stop.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import eventHub from '~/environments/event_hub';
$.fn.tooltip = () => {};
describe('Stop Component', () => {
let wrapper;
const createWrapper = () => {
wrapper = shallowMount(StopComponent, {
sync: false,
attachToDocument: true,
propsData: {
environment: {},
},
});
};
const findButton = () => wrapper.find(LoadingButton);
beforeEach(() => {
jest.spyOn(window, 'confirm');
createWrapper();
});
it('should render a button to stop the environment', () => {
expect(findButton().exists()).toBe(true);
expect(wrapper.attributes('data-original-title')).toEqual('Stop environment');
});
it('emits requestStopEnvironment in the event hub when button is clicked', () => {
jest.spyOn(eventHub, '$emit');
findButton().vm.$emit('click');
expect(eventHub.$emit).toHaveBeenCalledWith('requestStopEnvironment', wrapper.vm.environment);
});
});
import Vue from 'vue';
import terminalComp from '~/environments/components/environment_terminal_button.vue';
import { shallowMount } from '@vue/test-utils';
import TerminalComponent from '~/environments/components/environment_terminal_button.vue';
describe('Stop Component', () => {
let component;
let wrapper;
const terminalPath = '/path';
const mountWithProps = props => {
const TerminalComponent = Vue.extend(terminalComp);
component = new TerminalComponent({
wrapper = shallowMount(TerminalComponent, {
sync: false,
attachToDocument: true,
propsData: props,
}).$mount();
});
};
beforeEach(() => {
......@@ -18,18 +19,18 @@ describe('Stop Component', () => {
describe('computed', () => {
it('title', () => {
expect(component.title).toEqual('Terminal');
expect(wrapper.vm.title).toEqual('Terminal');
});
});
it('should render a link to open a web terminal with the provided path', () => {
expect(component.$el.tagName).toEqual('A');
expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
expect(component.$el.getAttribute('href')).toEqual(terminalPath);
expect(wrapper.is('a')).toBe(true);
expect(wrapper.attributes('data-original-title')).toBe('Terminal');
expect(wrapper.attributes('aria-label')).toBe('Terminal');
expect(wrapper.attributes('href')).toBe(terminalPath);
});
it('should render a non-disabled button', () => {
expect(component.$el.classList).not.toContain('disabled');
expect(wrapper.classes()).not.toContain('disabled');
});
});
import Vue from 'vue';
import monitoringComp from '~/environments/components/environment_monitoring.vue';
describe('Monitoring Component', () => {
let MonitoringComponent;
let component;
const monitoringUrl = 'https://gitlab.com';
beforeEach(() => {
MonitoringComponent = Vue.extend(monitoringComp);
component = new MonitoringComponent({
propsData: {
monitoringUrl,
},
}).$mount();
});
describe('computed', () => {
it('title', () => {
expect(component.title).toEqual('Monitoring');
});
});
it('should render a link to environment monitoring page', () => {
expect(component.$el.getAttribute('href')).toEqual(monitoringUrl);
expect(component.$el.querySelector('.fa-area-chart')).toBeDefined();
expect(component.$el.getAttribute('data-original-title')).toEqual('Monitoring');
expect(component.$el.getAttribute('aria-label')).toEqual('Monitoring');
});
});
import Vue from 'vue';
import stopComp from '~/environments/components/environment_stop.vue';
describe('Stop Component', () => {
let StopComponent;
let component;
beforeEach(() => {
StopComponent = Vue.extend(stopComp);
spyOn(window, 'confirm').and.returnValue(true);
component = new StopComponent({
propsData: {
environment: {},
},
}).$mount();
});
it('should render a button to stop the environment', () => {
expect(component.$el.tagName).toEqual('BUTTON');
expect(component.$el.getAttribute('data-original-title')).toEqual('Stop environment');
});
});
......@@ -61,6 +61,66 @@ describe('note_awards_list component', () => {
expect(vm.$el.querySelector('.js-add-award')).toBeDefined();
});
describe('when the user name contains special HTML characters', () => {
const createAwardEmoji = (_, index) => ({
name: 'art',
user: { id: index, name: `&<>"\`'-${index}`, username: `user-${index}` },
});
const mountComponent = () => {
const Component = Vue.extend(awardsNote);
vm = new Component({
store,
propsData: {
awards: awardsMock,
noteAuthorId: 0,
noteId: '545',
canAwardEmoji: true,
toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
},
}).$mount();
};
const findTooltip = () =>
vm.$el.querySelector('[data-original-title]').getAttribute('data-original-title');
it('should only escape & and " characters', () => {
awardsMock = [...new Array(1)].map(createAwardEmoji);
mountComponent();
const escapedName = awardsMock[0].user.name.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
expect(vm.$el.querySelector('[data-original-title]').outerHTML).toContain(escapedName);
});
it('should not escape special HTML characters twice when only 1 person awarded', () => {
awardsMock = [...new Array(1)].map(createAwardEmoji);
mountComponent();
awardsMock.forEach(award => {
expect(findTooltip()).toContain(award.user.name);
});
});
it('should not escape special HTML characters twice when 2 people awarded', () => {
awardsMock = [...new Array(2)].map(createAwardEmoji);
mountComponent();
awardsMock.forEach(award => {
expect(findTooltip()).toContain(award.user.name);
});
});
it('should not escape special HTML characters twice when more than 10 people awarded', () => {
awardsMock = [...new Array(11)].map(createAwardEmoji);
mountComponent();
// Testing only the first 10 awards since 11 onward will not be displayed.
awardsMock.slice(0, 10).forEach(award => {
expect(findTooltip()).toContain(award.user.name);
});
});
});
describe('when the user cannot award emoji', () => {
beforeEach(() => {
const Component = Vue.extend(awardsNote);
......
......@@ -1530,7 +1530,7 @@ describe Repository do
expect(merge_request.reload.rebase_commit_sha).to eq(new_sha)
end
it 'does rollback when an error is encountered in the second step' do
it 'does rollback when a PreReceiveError is encountered in the second step' do
second_response = double(pre_receive_error: 'my_error', git_error: nil)
mock_gitaly(second_response)
......@@ -1541,6 +1541,17 @@ describe Repository do
expect(merge_request.reload.rebase_commit_sha).to be_nil
end
it 'does rollback when a GitError is encountered in the second step' do
second_response = double(pre_receive_error: nil, git_error: 'git error')
mock_gitaly(second_response)
expect do
repository.rebase(user, merge_request)
end.to raise_error(Gitlab::Git::Repository::GitError)
expect(merge_request.reload.rebase_commit_sha).to be_nil
end
def mock_gitaly(second_response)
responses = [
double(rebase_sha: new_sha).as_null_object,
......
......@@ -11,8 +11,7 @@ describe PersonalSnippetPolicy do
let(:author_permissions) do
[
:update_personal_snippet,
:admin_personal_snippet,
:destroy_personal_snippet
:admin_personal_snippet
]
end
......
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