Commit 65b4627d authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into feature/sm/35954-create-kubernetes-cluster-on-gke-from-k8s-service

parents 6d4e2829 bd970a51
Pipeline #12410621 failed with stages
in 79 minutes and 49 seconds
......@@ -63,4 +63,5 @@ eslint-report.html
/.gitlab_workhorse_secret
/webpack-report/
/locale/**/LC_MESSAGES
/locale/**/*.time_stamp
/.rspec
......@@ -398,7 +398,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.38.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.39.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -273,7 +273,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.38.0)
gitaly-proto (0.39.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -1027,7 +1027,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.38.0)
gitaly-proto (~> 0.39.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
......
......@@ -197,6 +197,11 @@ month. When we say 'the most recent monthly release', this can refer to either
the version currently running on GitLab.com, or the most recent version
available in the package repositories.
A regression issue should be labeled with the appropriate [subject label](../CONTRIBUTING.md#subject-labels-wiki-container-registry-ldap-api-etc)
and [team label](../CONTRIBUTING.md#team-labels-ci-discussion-edge-platform-etc),
just like any other issue, to help GitLab team members focus on issues that are
relevant to [their area of responsibility](https://about.gitlab.com/handbook/engineering/workflow/#choosing-something-to-work-on).
## Release retrospective and kickoff
- [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective)
......
......@@ -298,7 +298,7 @@ class CopyAsGFM {
const documentFragment = getSelectedFragment();
if (!documentFragment) return;
const el = transformer(documentFragment.cloneNode(true));
const el = transformer(documentFragment.cloneNode(true), e.currentTarget);
if (!el) return;
e.preventDefault();
......@@ -338,55 +338,64 @@ class CopyAsGFM {
}
static transformGFMSelection(documentFragment) {
const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
switch (gfmEls.length) {
const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
switch (gfmElements.length) {
case 0: {
return documentFragment;
}
case 1: {
return gfmEls[0];
return gfmElements[0];
}
default: {
const allGfmEl = document.createElement('div');
const allGfmElement = document.createElement('div');
for (let i = 0; i < gfmEls.length; i += 1) {
const lineEl = gfmEls[i];
allGfmEl.appendChild(lineEl);
allGfmEl.appendChild(document.createTextNode('\n\n'));
for (let i = 0; i < gfmElements.length; i += 1) {
const gfmElement = gfmElements[i];
allGfmElement.appendChild(gfmElement);
allGfmElement.appendChild(document.createTextNode('\n\n'));
}
return allGfmEl;
return allGfmElement;
}
}
}
static transformCodeSelection(documentFragment) {
const lineEls = documentFragment.querySelectorAll('.line');
static transformCodeSelection(documentFragment, target) {
let lineSelector = '.line';
let codeEl;
if (lineEls.length > 1) {
codeEl = document.createElement('pre');
codeEl.className = 'code highlight';
if (target) {
const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
if (lineClass) {
lineSelector = `.line_content.${lineClass} ${lineSelector}`;
}
}
const lineElements = documentFragment.querySelectorAll(lineSelector);
let codeElement;
if (lineElements.length > 1) {
codeElement = document.createElement('pre');
codeElement.className = 'code highlight';
const lang = lineEls[0].getAttribute('lang');
const lang = lineElements[0].getAttribute('lang');
if (lang) {
codeEl.setAttribute('lang', lang);
codeElement.setAttribute('lang', lang);
}
} else {
codeEl = document.createElement('code');
codeElement = document.createElement('code');
}
if (lineEls.length > 0) {
for (let i = 0; i < lineEls.length; i += 1) {
const lineEl = lineEls[i];
codeEl.appendChild(lineEl);
codeEl.appendChild(document.createTextNode('\n'));
if (lineElements.length > 0) {
for (let i = 0; i < lineElements.length; i += 1) {
const lineElement = lineElements[i];
codeElement.appendChild(lineElement);
codeElement.appendChild(document.createTextNode('\n'));
}
} else {
codeEl.appendChild(documentFragment);
codeElement.appendChild(documentFragment);
}
return codeEl;
return codeElement;
}
static nodeToGFM(node, respectWhitespaceParam = false) {
......
......@@ -24,7 +24,8 @@ class Diff {
if (!isBound) {
$(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this))
.on('mousedown', 'td.line_content.parallel', this.handleParallelLineDown.bind(this));
isBound = true;
}
......@@ -100,6 +101,18 @@ class Diff {
this.highlightSelectedLine();
}
handleParallelLineDown(e) {
const line = $(e.currentTarget);
const table = line.closest('table');
table.removeClass('left-side-selected right-side-selected');
const lineClass = ['left-side', 'right-side'].filter(name => line.hasClass(name))[0];
if (lineClass) {
table.addClass(`${lineClass}-selected`);
}
}
diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
......
import Jed from 'jed';
import sprintf from './sprintf';
/**
This is required to require all the translation folders in the current directory
this saves us having to do this manually & keep up to date with new languages
......@@ -66,4 +68,5 @@ export { lang };
export { gettext as __ };
export { ngettext as n__ };
export { pgettext as s__ };
export { sprintf };
export default locale;
import _ from 'underscore';
/**
Very limited implementation of sprintf supporting only named parameters.
@param input (translated) text with parameters (e.g. '%{num_users} users use us')
@param parameters object mapping parameter names to values (e.g. { num_users: 5 })
@param escapeParameters whether parameter values should be escaped (see http://underscorejs.org/#escape)
@returns {String} the text with parameters replaces (e.g. '5 users use us')
@see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf
@see https://gitlab.com/gitlab-org/gitlab-ce/issues/37992
**/
export default (input, parameters, escapeParameters = true) => {
let output = input;
if (parameters) {
Object.keys(parameters).forEach((parameterName) => {
const parameterValue = parameters[parameterName];
const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue;
output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
});
}
return output;
}
......@@ -27,7 +27,7 @@ export default {
<button
v-if="showDisabledButton"
type="button"
class="btn btn-success btn-sm"
class="js-disabled-merge-button btn btn-success btn-sm"
disabled="true">
Merge
</button>
......
......@@ -16,9 +16,9 @@ export default {
<div class="media-body">
<mr-widget-author-and-time
actionText="Closed by"
:author="mr.closedBy"
:dateTitle="mr.updatedAt"
:dateReadable="mr.closedAt"
:author="mr.closedEvent.author"
:dateTitle="mr.closedEvent.updatedAt"
:dateReadable="mr.closedEvent.formattedUpdatedAt"
/>
<section class="mr-info-list">
<p>
......
......@@ -10,27 +10,37 @@ export default {
},
template: `
<div class="mr-widget-body media">
<status-icon status="failed" showDisabledButton />
<status-icon
status="failed"
showDisabledButton />
<div class="media-body space-children">
<span class="bold">
There are merge conflicts<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge">
Resolve these conflicts or ask someone with write access to this repository to merge it locally
</span>
<span
v-if="mr.shouldBeRebased"
class="bold">
Fast-forward merge is not possible.
To merge this request, first rebase locally.
</span>
<a
v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath"
class="btn btn-default btn-xs js-resolve-conflicts-button">
Resolve conflicts
</a>
<a
v-if="mr.canMerge"
class="btn btn-default btn-xs js-merge-locally-button"
data-toggle="modal"
href="#modal_merge_info">
Merge locally
</a>
<template v-else>
<span class="bold">
There are merge conflicts<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge">
Resolve these conflicts or ask someone with write access to this repository to merge it locally
</span>
</span>
<a
v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath"
class="js-resolve-conflicts-button btn btn-default btn-xs">
Resolve conflicts
</a>
<a
v-if="mr.canMerge"
class="js-merge-locally-button btn btn-default btn-xs"
data-toggle="modal"
href="#modal_merge_info">
Merge locally
</a>
</template>
</div>
</div>
`,
......
......@@ -69,9 +69,9 @@ export default {
<div class="space-children">
<mr-widget-author-and-time
actionText="Merged by"
:author="mr.mergedBy"
:dateTitle="mr.updatedAt"
:dateReadable="mr.mergedAt" />
:author="mr.mergedEvent.author"
:date-title="mr.mergedEvent.updatedAt"
:date-readable="mr.mergedEvent.formattedUpdatedAt" />
<a
v-if="mr.canRevertInCurrentMR"
v-tooltip
......
......@@ -284,10 +284,16 @@ export default {
:mr="mr"
:is-merge-button-disabled="isMergeButtonDisabled" />
<span
v-if="mr.ffOnlyEnabled"
class="js-fast-forward-message">
Fast-forward merge without a merge commit
</span>
<button
v-else
@click="toggleCommitMessageEditor"
:disabled="isMergeButtonDisabled"
class="btn btn-default btn-xs"
class="js-modify-commit-message-button btn btn-default btn-xs"
type="button">
Modify commit message
</button>
......
......@@ -37,10 +37,8 @@ export default class MergeRequestStore {
}
this.updatedAt = data.updated_at;
this.mergedAt = MergeRequestStore.getEventDate(data.merge_event);
this.closedAt = MergeRequestStore.getEventDate(data.closed_event);
this.mergedBy = MergeRequestStore.getAuthorObject(data.merge_event);
this.closedBy = MergeRequestStore.getAuthorObject(data.closed_event);
this.mergedEvent = MergeRequestStore.getEventObject(data.merge_event);
this.closedEvent = MergeRequestStore.getEventObject(data.closed_event);
this.setToMWPSBy = MergeRequestStore.getAuthorObject({ author: data.merge_user || {} });
this.mergeUserId = data.merge_user_id;
this.currentUserId = gon.current_user_id;
......@@ -57,6 +55,8 @@ export default class MergeRequestStore {
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false;
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = !!data.should_be_rebased;
this.statusPath = data.status_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
......@@ -118,6 +118,14 @@ export default class MergeRequestStore {
}
}
static getEventObject(event) {
return {
author: MergeRequestStore.getAuthorObject(event),
updatedAt: gl.utils.formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
formattedUpdatedAt: MergeRequestStore.getEventDate(event),
};
}
static getAuthorObject(event) {
if (!event) {
return {};
......@@ -131,6 +139,14 @@ export default class MergeRequestStore {
};
}
static getEventUpdatedAtDate(event) {
if (!event) {
return '';
}
return event.updated_at;
}
static getEventDate(event) {
const timeagoInstance = new Timeago();
......@@ -138,7 +154,7 @@ export default class MergeRequestStore {
return '';
}
return timeagoInstance.format(event.updated_at);
return timeagoInstance.format(MergeRequestStore.getEventUpdatedAtDate(event));
}
}
......@@ -2,6 +2,7 @@ import {
__,
n__,
s__,
sprintf,
} from '../locale';
export default (Vue) => {
......@@ -37,6 +38,7 @@ export default (Vue) => {
@returns {String} Translated context based text
**/
s__,
sprintf,
},
});
};
......@@ -873,6 +873,13 @@
min-width: 100%;
}
}
header.navbar-gitlab-new .header-content .dropdown {
.dropdown-menu {
left: 0;
min-width: 100%;
}
}
}
@include new-style-dropdown('.breadcrumbs-list .dropdown ');
......
......@@ -229,6 +229,10 @@ ul.content-list {
.label-default {
color: $gl-text-color-secondary;
}
.avatar-cell {
align-self: flex-start;
}
}
.panel > .content-list > li {
......
......@@ -77,6 +77,18 @@
word-wrap: break-word;
}
}
&.left-side-selected {
td.line_content.parallel.right-side {
@include user-select(none);
}
}
&.right-side-selected {
td.line_content.parallel.left-side {
@include user-select(none);
}
}
}
tr.line_holder.parallel {
......
......@@ -15,9 +15,9 @@ module NotesActions
notes = notes_finder.execute
.inc_relations_for_view
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes_json[:notes] =
if noteable.discussions_rendered_on_frontend?
......
......@@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_create_issue!, only: [:new, :create]
# Allow modify issue
before_action :authorize_update_issue!, only: [:edit, :update, :move]
before_action :authorize_update_issue!, only: [:update, :move]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
......@@ -63,10 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController
respond_with(@issue)
end
def edit
respond_with(@issue)
end
def show
@noteable = @issue
@note = @project.notes.new(noteable: @issue)
......@@ -126,10 +122,6 @@ class Projects::IssuesController < Projects::ApplicationController
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
respond_to do |format|
format.html do
recaptcha_check_with_fallback { render :edit }
end
format.json do
render_issue_json
end
......
......@@ -18,16 +18,12 @@ class Projects::WikisController < Projects::ApplicationController
response.headers['Content-Security-Policy'] = "default-src 'none'"
response.headers['X-Content-Security-Policy'] = "default-src 'none'"
if file.on_disk?
send_file file.on_disk_path, disposition: 'inline'
else
send_data(
file.raw_data,
type: file.mime_type,
disposition: 'inline',
filename: file.name
)
end
send_data(
file.raw_data,
type: file.mime_type,
disposition: 'inline',
filename: file.name
)
else
return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPage.new(@project_wiki)
......
......@@ -344,6 +344,7 @@ class ProjectsController < Projects::ApplicationController
:tag_list,
:visibility_level,
:template_name,
:merge_method,
project_feature_attributes: %i[
builds_access_level
......
......@@ -33,19 +33,21 @@ module DiffHelper
end
def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false)
content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}"
cls = ['diff-line-num', 'unfold', 'js-unfold']
cls << 'js-unfold-bottom' if bottom
content_line_class = %w[line_content match]
content_line_class << 'parallel' if view == :parallel
line_num_class = %w[diff-line-num unfold js-unfold]
line_num_class << 'js-unfold-bottom' if bottom
html = ''
if old_pos
html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos })
html << content unless view == :inline
html << content_tag(:td, '...', class: [*line_num_class, 'old_line'], data: { linenumber: old_pos })
html << content_tag(:td, text, class: [*content_line_class, 'left-side']) if view == :parallel
end
if new_pos
html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos })
html << content
html << content_tag(:td, '...', class: [*line_num_class, 'new_line'], data: { linenumber: new_pos })
html << content_tag(:td, text, class: [*content_line_class, ('right-side' if view == :parallel)])
end
html.html_safe
......
......@@ -524,6 +524,14 @@ class MergeRequest < ActiveRecord::Base
true
end
def ff_merge_possible?
project.repository.ancestor?(target_branch_sha, diff_head_sha)
end
def should_be_rebased?
project.ff_merge_must_be_possible? && !ff_merge_possible?
end
def can_cancel_merge_when_pipeline_succeeds?(current_user)
can_be_merged_by?(current_user) || self.author == current_user
end
......
......@@ -64,6 +64,7 @@ class Project < ActiveRecord::Base
# Storage specific hooks
after_initialize :use_hashed_storage
after_create :check_repository_absence!
after_create :ensure_storage_path_exists
after_save :ensure_storage_path_exists, if: :namespace_id_changed?
......@@ -229,7 +230,7 @@ class Project < ActiveRecord::Base
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :check_repository_path_availability, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? }
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
......@@ -1026,7 +1027,9 @@ class Project < ActiveRecord::Base
expires_full_path_cache # we need to clear cache to validate renames correctly
if gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git")
# Check if repository with same path already exists on disk we can
# skip this for the hashed storage because the path does not change
if legacy_storage? && repository_with_same_path_already_exists?
errors.add(:base, 'There is already a repository with that name on disk')
return false
end
......@@ -1567,6 +1570,34 @@ class Project < ActiveRecord::Base
persisted? && path_changed?
end
def merge_method
if self.merge_requests_ff_only_enabled
:ff
elsif self.merge_requests_rebase_enabled
:rebase_merge
else
:merge
end
end
def merge_method=(method)
case method.to_s
when "ff"
self.merge_requests_ff_only_enabled = true
self.merge_requests_rebase_enabled = true
when "rebase_merge"
self.merge_requests_ff_only_enabled = false
self.merge_requests_rebase_enabled = true
when "merge"
self.merge_requests_ff_only_enabled = false
self.merge_requests_rebase_enabled = false
end
end
def ff_merge_must_be_possible?
self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled
end
def migrate_to_hashed_storage!
return if hashed_storage?
......@@ -1614,6 +1645,19 @@ class Project < ActiveRecord::Base