Commit ca3ea76c authored by Alessio Caiazza's avatar Alessio Caiazza

Merge branch 'master' into 11-1-stable-prepare-rc5

parents 7240d175 68d138e8
...@@ -120,6 +120,10 @@ All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/ ...@@ -120,6 +120,10 @@ All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/
Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help. Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help.
## Why?
[Read here](https://about.gitlab.com/why/)
## Is it any good? ## Is it any good?
[Yes](https://news.ycombinator.com/item?id=3067434) [Yes](https://news.ycombinator.com/item?id=3067434)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import _ from 'underscore'; import _ from 'underscore';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip'; import Tooltip from '~/vue_shared/directives/tooltip';
import { truncateSha } from '~/lib/utils/text_utility'; import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
...@@ -12,6 +13,7 @@ export default { ...@@ -12,6 +13,7 @@ export default {
ClipboardButton, ClipboardButton,
EditButton, EditButton,
Icon, Icon,
FileIcon,
}, },
directives: { directives: {
Tooltip, Tooltip,
...@@ -139,18 +141,20 @@ export default { ...@@ -139,18 +141,20 @@ export default {
:name="collapseIcon" :name="collapseIcon"
:size="16" :size="16"
aria-hidden="true" aria-hidden="true"
class="diff-toggle-caret" class="diff-toggle-caret append-right-5"
@click.stop="handleToggle" @click.stop="handleToggle"
/> />
<a <a
ref="titleWrapper" ref="titleWrapper"
:href="titleLink" :href="titleLink"
class="append-right-4"
> >
<i <file-icon
:class="`fa-${icon}`" :file-name="filePath"
class="fa fa-fw" :size="18"
aria-hidden="true" aria-hidden="true"
></i> css-classes="js-file-icon append-right-5"
/>
<span v-if="diffFile.renamedFile"> <span v-if="diffFile.renamedFile">
<strong <strong
v-tooltip v-tooltip
......
...@@ -24,19 +24,21 @@ export default { ...@@ -24,19 +24,21 @@ export default {
...mapGetters(['commit']), ...mapGetters(['commit']),
parallelDiffLines() { parallelDiffLines() {
return this.diffLines.map(line => { return this.diffLines.map(line => {
const parallelLine = Object.assign({}, line);
if (line.left) { if (line.left) {
Object.assign(line, { left: trimFirstCharOfLineContent(line.left) }); parallelLine.left = trimFirstCharOfLineContent(line.left);
} else { } else {
Object.assign(line, { left: { type: EMPTY_CELL_TYPE } }); parallelLine.left = { type: EMPTY_CELL_TYPE };
} }
if (line.right) { if (line.right) {
Object.assign(line, { right: trimFirstCharOfLineContent(line.right) }); parallelLine.right = trimFirstCharOfLineContent(line.right);
} else { } else {
Object.assign(line, { right: { type: EMPTY_CELL_TYPE } }); parallelLine.right = { type: EMPTY_CELL_TYPE };
} }
return line; return parallelLine;
}); });
}, },
diffLinesLength() { diffLinesLength() {
......
...@@ -155,18 +155,21 @@ export function addContextLines(options) { ...@@ -155,18 +155,21 @@ export function addContextLines(options) {
} }
} }
export function trimFirstCharOfLineContent(line) { /**
if (!line.richText) { * Trims the first char of the `richText` property when it's either a space or a diff symbol.
return line; * @param {Object} line
} * @returns {Object}
*/
const firstChar = line.richText.charAt(0); export function trimFirstCharOfLineContent(line = {}) {
const parsedLine = Object.assign({}, line);
if (firstChar === ' ' || firstChar === '+' || firstChar === '-') {
Object.assign(line, { if (line.richText) {
richText: line.richText.substring(1), const firstChar = parsedLine.richText.charAt(0);
});
if (firstChar === ' ' || firstChar === '+' || firstChar === '-') {
parsedLine.richText = line.richText.substring(1);
}
} }
return line; return parsedLine;
} }
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index'; import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
export default { export default {
components: { components: {
DiffFileHeader, DiffFileHeader,
SkeletonLoadingContainer, SkeletonLoadingContainer,
},
props: {
discussion: {
type: Object,
required: true,
}, },
}, props: {
data() { discussion: {
return { type: Object,
error: false, required: true,
}; },
},
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
}),
hasTruncatedDiffLines() {
return this.discussion.truncatedDiffLines && this.discussion.truncatedDiffLines.length !== 0;
}, },
isDiscussionsExpanded() { data() {
return true; // TODO: @fatihacet - Fix this. return {
error: false,
};
}, },
isCollapsed() { computed: {
return this.diffFile.collapsed || false; ...mapState({
}, noteableData: state => state.notes.noteableData,
isImageDiff() { }),
return !this.diffFile.text; hasTruncatedDiffLines() {
}, return this.discussion.truncatedDiffLines &&
diffFileClass() { this.discussion.truncatedDiffLines.length !== 0;
const { text } = this.diffFile; },
return text ? 'text-file' : 'js-image-file'; isDiscussionsExpanded() {
}, return true; // TODO: @fatihacet - Fix this.
diffFile() { },
return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true }); isCollapsed() {
}, return this.diffFile.collapsed || false;
imageDiffHtml() { },
return this.discussion.imageDiffHtml; isImageDiff() {
}, return !this.diffFile.text;
currentUser() { },
return this.noteableData.current_user; diffFileClass() {
}, const { text } = this.diffFile;
userColorScheme() { return text ? 'text-file' : 'js-image-file';
return window.gon.user_color_scheme; },
}, diffFile() {
normalizedDiffLines() { return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true });
const lines = this.discussion.truncatedDiffLines || []; },
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
currentUser() {
return this.noteableData.current_user;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
normalizedDiffLines() {
if (this.discussion.truncatedDiffLines) {
return this.discussion.truncatedDiffLines.map(line =>
trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)),
);
}
return lines.map(line => trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line))); return [];
},
}, },
}, mounted() {
mounted() { if (this.isImageDiff) {
if (this.isImageDiff) { const canCreateNote = false;
const canCreateNote = false; const renderCommentBadge = true;
const renderCommentBadge = true; imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge); } else if (!this.hasTruncatedDiffLines) {
} else if (!this.hasTruncatedDiffLines) { this.fetchDiff();
this.fetchDiff(); }
}
},
methods: {
...mapActions(['fetchDiscussionDiffLines']),
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
}, },
fetchDiff() { methods: {
this.error = false; ...mapActions(['fetchDiscussionDiffLines']),
this.fetchDiscussionDiffLines(this.discussion) rowTag(html) {
.then(this.highlight) return html.outerHTML ? 'tr' : 'template';
.catch(() => { },
this.error = true; fetchDiff() {
}); this.error = false;
this.fetchDiscussionDiffLines(this.discussion)
.then(this.highlight)
.catch(() => {
this.error = true;
});
},
}, },
}, };
};
</script> </script>
<template> <template>
......
...@@ -85,9 +85,9 @@ export const allDiscussions = (state, getters) => { ...@@ -85,9 +85,9 @@ export const allDiscussions = (state, getters) => {
export const resolvedDiscussionsById = state => { export const resolvedDiscussionsById = state => {
const map = {}; const map = {};
state.discussions.forEach(n => { state.discussions.filter(d => d.resolvable).forEach(n => {
if (n.notes) { if (n.notes) {
const resolved = n.notes.every(note => note.resolved && !note.system); const resolved = n.notes.filter(note => note.resolvable).every(note => note.resolved);
if (resolved) { if (resolved) {
map[n.id] = n; map[n.id] = n;
......
import initTerminal from '~/terminal/';
document.addEventListener('DOMContentLoaded', initTerminal);
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
methods: { methods: {
isValid(form) { isValid(form) {
return !form || return !form ||
form.find('.js-vue-markdown-field').length || form.find('.js-vue-markdown-field').length &&
$(this.$el).closest('form') === form[0]; $(this.$el).closest('form')[0] === form[0];
}, },
previewMarkdownTab(event, form) { previewMarkdownTab(event, form) {
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
} }
svg { svg {
vertical-align: text-bottom; vertical-align: middle;
} }
} }
......
...@@ -10,6 +10,7 @@ module PreviewMarkdown ...@@ -10,6 +10,7 @@ module PreviewMarkdown
when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] } when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] }
when 'snippets' then { skip_project_check: true } when 'snippets' then { skip_project_check: true }
when 'groups' then { group: group } when 'groups' then { group: group }
when 'projects' then { issuable_state_filter_enabled: true }
else {} else {}
end end
......
...@@ -7,7 +7,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -7,7 +7,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
skip_cross_project_access_check :index, :starred skip_cross_project_access_check :index, :starred
def index def index
@projects = load_projects(params.merge(non_public: true)).page(params[:page]) @projects = load_projects(params.merge(non_public: true))
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -25,7 +25,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -25,7 +25,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def starred def starred
@projects = load_projects(params.merge(starred: true)) @projects = load_projects(params.merge(starred: true))
.includes(:forked_from_project, :tags).page(params[:page]) .includes(:forked_from_project, :tags)
@groups = [] @groups = []
...@@ -51,6 +51,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -51,6 +51,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
.new(params: finder_params, current_user: current_user) .new(params: finder_params, current_user: current_user)
.execute .execute
.includes(:route, :creator, namespace: [:route, :owner]) .includes(:route, :creator, namespace: [:route, :owner])
.page(finder_params[:page])
prepare_projects_for_rendering(projects) prepare_projects_for_rendering(projects)
end end
......
...@@ -2,11 +2,12 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -2,11 +2,12 @@ class Projects::JobsController < Projects::ApplicationController
include SendFileUpload include SendFileUpload
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, before_action :authorize_read_build!
only: [:index, :show, :status, :raw, :trace]
before_action :authorize_update_build!, before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase] except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase]
before_action :authorize_erase_build!, only: [:erase] before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_workhorse_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
layout 'project' layout 'project'
...@@ -134,6 +135,15 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -134,6 +135,15 @@ class Projects::JobsController < Projects::ApplicationController
end end
end end
def terminal
end
# GET .../terminal.ws : implemented in gitlab-workhorse
def terminal_websocket_authorize
set_workhorse_internal_api_content_type
render json: Gitlab::Workhorse.terminal_websocket(@build.terminal_specification)
end
private private
def authorize_update_build! def authorize_update_build!
...@@ -144,6 +154,14 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -144,6 +154,14 @@ class Projects::JobsController < Projects::ApplicationController
return access_denied! unless can?(current_user, :erase_build, build) return access_denied! unless can?(current_user, :erase_build, build)
end end
def authorize_use_build_terminal!
return access_denied! unless can?(current_user, :create_build_terminal, build)
end
def verify_api_request!
Gitlab::Workhorse.verify_api_request!(request.headers)
end
def raw_send_params def raw_send_params
{ type: 'text/plain; charset=utf-8', disposition: 'inline' } { type: 'text/plain; charset=utf-8', disposition: 'inline' }
end end
......
...@@ -177,6 +177,7 @@ module ProjectsHelper ...@@ -177,6 +177,7 @@ module ProjectsHelper
controller.action_name, controller.action_name,
Gitlab::CurrentSettings.cache_key, Gitlab::CurrentSettings.cache_key,
"cross-project:#{can?(current_user, :read_cross_project)}", "cross-project:#{can?(current_user, :read_cross_project)}",
max_project_member_access_cache_key(project),
'v2.6' 'v2.6'
] ]
......
...@@ -31,6 +31,14 @@ module UsersHelper ...@@ -31,6 +31,14 @@ module UsersHelper
current_user_menu_items.include?(item) current_user_menu_items.include?(item)
end end
def max_project_member_access(project)
current_user&.max_member_access_for_project(project.id) || Gitlab::Access::NO_ACCESS
end
def max_project_member_access_cache_key(project)
"access:#{max_project_member_access(project)}"
end
private private
def get_profile_tabs def get_profile_tabs
......
...@@ -26,4 +26,8 @@ class Board < ActiveRecord::Base ...@@ -26,4 +26,8 @@ class Board < ActiveRecord::Base
def closed_list def closed_list
lists.merge(List.closed).take lists.merge(List.closed).take
end end
def scoped?
false
end
end end
...@@ -27,7 +27,13 @@ module Ci ...@@ -27,7 +27,13 @@ module Ci
has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :metadata, class_name: 'Ci::BuildMetadata' has_one :metadata, class_name: 'Ci::BuildMetadata'
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
accepts_nested_attributes_for :runner_session
delegate :timeout, to: :metadata, prefix: true, allow_nil: true delegate :timeout, to: :metadata, prefix: true, allow_nil: true
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project delegate :gitlab_deploy_token, to: :project
## ##
...@@ -174,6 +180,10 @@ module Ci ...@@ -174,6 +180,10 @@ module Ci
after_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
after_transition running: any do |build|
Ci::BuildRunnerSession.where(build: build).delete_all
end
end end
def ensure_metadata def ensure_metadata
...@@ -376,6 +386,10 @@ module Ci ...@@ -376,6 +386,10 @@ module Ci
trace.exist? trace.exist?
end end
def has_old_trace?
old_trace.present?
end
def trace=(data) def trace=(data)
raise NotImplementedError raise NotImplementedError
end end
...@@ -385,6 +399,8 @@ module Ci ...@@ -385,6 +399,8 @@ module Ci
end end
def erase_old_trace! def erase_old_trace!
return unless has_old_trace?
update_column(:trace, nil) update_column(:trace, nil)
end end
...@@ -584,6 +600,10 @@ module Ci ...@@ -584,6 +600,10 @@ module Ci
super(options).merge(when: read_attribute(:when)) super(options).merge(when: read_attribute(:when))
end end
def has_terminal?
running? && runner_session_url.present?
end
private private
def update_artifacts_size def update_artifacts_size
......
module Ci
# The purpose of this class is to store Build related runner session.
# Data will be removed after transitioning from running to any state.
class BuildRunnerSession < ActiveRecord::Base
extend Gitlab::Ci::Model
self.table_name = 'ci_builds_runner_session'
belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session
validates :build, presence: true
validates :url, url: { protocols: %w(https) }
def terminal_specification
return {} unless url.present?
{
subprotocols: ['terminal.gitlab.com'].freeze,
url: "#{url}/exec".sub("https://", "wss://"),
headers: { Authorization: authorization.presence }.compact,
ca_pem: certificate.presence
}
end
end
end
...@@ -240,7 +240,7 @@ class KubernetesService < DeploymentService ...@@ -240,7 +240,7 @@ class KubernetesService < DeploymentService
end end
def deprecation_validation def deprecation_validation
return if active_changed?(from: true, to: false) return if active_changed?(from: true, to: false) || (new_record? && !active?)
if deprecated? if deprecated?
errors[:base] << deprecation_message errors[:base] << deprecation_message
......
...@@ -281,9 +281,9 @@ class Service < ActiveRecord::Base ...@@ -281,9 +281,9 @@ class Service < ActiveRecord::Base
def self.build_from_template(project_id, template) def self.build_from_template(project_id, template)
service = template.dup service = template.dup
service.active = false unless service.valid?
service.template = false service.template = false
service.project_id = project_id service.project_id = project_id
service.active = false if service.active? && !service.valid?
service service
end end
......
...@@ -18,6 +18,10 @@ module Ci ...@@ -18,6 +18,10 @@ module Ci
@subject.project.branch_allows_collaboration?(@user, @subject.ref) @subject.project.branch_allows_collaboration?(@user, @subject.ref)
end end
condition(:terminal, scope: :subject) do
@subject.has_terminal?
end
rule { protected_ref }.policy do rule { protected_ref }.policy do
prevent :update_build prevent :update_build
prevent :erase_build prevent :erase_build
...@@ -29,5 +33,7 @@ module Ci ...@@ -29,5 +33,7 @@ module Ci
enable :update_build enable :update_build
enable :update_commit_status enable :update_commit_status
end end
rule { can?(:update_build) & terminal }.enable :create_build_terminal
end end
end end
...@@ -13,7 +13,7 @@ module Ci ...@@ -13,7 +13,7 @@ module Ci
@runner = runner @runner = runner
end end
def execute def execute(params = {})
builds = builds =
if runner.instance_type? if runner.instance_type?
builds_for_shared_runner builds_for_shared_runner
...@@ -41,6 +41,8 @@ module Ci ...@@ -41,6 +41,8 @@ module Ci
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
begin begin
build.runner_id = runner.id build.runner_id = runner.id
build.runner_session_attributes = params[:session] if params[:session].present?
build.run! build.run!
register_success(build) register_success(build)
......
...@@ -32,8 +32,9 @@ module Issues ...@@ -32,8 +32,9 @@ module Issues
def filter_assignee(issuable) def filter_assignee(issuable)
return if params[:assignee_ids].blank? return if params[:assignee_ids].blank?
# The number of assignees is limited by one for GitLab CE unless issuable.allows_multiple_assignees?
params[:assignee_ids] = params[:assignee_ids][0, 1] params[:assignee_ids] = params[:assignee_ids].take(1)
end
assignee_ids = params[:assignee_ids].select { |assignee_id| assignee_can_read?(issuable, assignee_id) } assignee_ids = params[:assignee_ids].select { |assignee_id| assignee_can_read?(issuable, assignee_id) }
......
...@@ -17,11 +17,7 @@ ...@@ -17,11 +17,7 @@
= link_to _("Help"), help_path = link_to _("Help"), help_path
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile) - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
%li.divider %li.divider
%li = render 'shared/user_dropdown_contributing_link'
= link_to "https://about.gitlab.com/contributing", target: '_blank', class: 'text-nowrap' do
= _("Contribute to GitLab")
= sprite_icon('external-link', size: 16)
%li.divider
- if current_user_menu?(:sign_out) - if current_user_menu?(:sign_out)
%li %li
= link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link" = link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link"
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } } %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container .sidebar-container
.blocks-container .blocks-container
- if can?(current_user, :create_build_terminal, @build)
.block
= link_to terminal_project_job_path(@project, @build), class: 'terminal-button pull-right btn visible-md-block visible-lg-block', title: 'Terminal' do
Terminal
#js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } } #js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } }
...@@ -14,8 +18,8 @@ ...@@ -14,8 +18,8 @@
#{time_ago_with_tooltip(@build.artifacts_expire_at)} #{time_ago_with_tooltip(@build.artifacts_expire_at)}
- elsif @build.has_expiring_artifacts? - elsif @build.has_expiring_artifacts?
%p.build-detail-row %p.build-detail-row
The artifacts will be removed in The artifacts will be removed
%span= time_ago_with_tooltip @build.artifacts_expire_at #{time_ago_with_tooltip(@build.artifacts_expire_at)}
- if @build.artifacts? - if @build.artifacts?
.btn-group.d-flex{ role: :group } .btn-group.d-flex{ role: :group }
......
- @no_container = true
- add_to_breadcrumbs 'Jobs', project_jobs_path(@project)
- add_to_breadcrumbs "##{@build.id}", project_job_path(@project, @build)
- breadcrumb_title 'Terminal'
- page_title 'Terminal', "#{@build.name} (##{@build.id})", 'Jobs'
- content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm/xterm"
.terminal-container{ class: container_class }
#terminal{ data: { project_path: terminal_project_job_path(@project, @build, format: :ws) } }
...@@ -117,6 +117,9 @@ ...@@ -117,6 +117,9 @@
%li %li
JaCoCo (Java/Kotlin) JaCoCo (Java/Kotlin)
%code Total.*?([0-9]{1,3})% %code Total.*?([0-9]{1,3})%
%li
go test -cover (Go)
%code coverage: \d+.\d+% of statements
= f.submit _('Save changes'), class: "btn btn-save" = f.submit _('Save changes'), class: "btn btn-save"
......
%li
= link_to "https://about.gitlab.com/contributing", target: '_blank', class: 'text-nowrap' do
= _("Contribute to GitLab")
= sprite_icon('external-link', size: 16)
%li.divider
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
- group = local_assigns.fetch(:group, false) - group = local_assigns.fetch(:group, false)
- @no_breadcrumb_container = true - @no_breadcrumb_container = true
- @no_container = true - @no_container = true
- @content_class = "issue-boards-content" - @content_class = "issue-boards-content js-focus-mode-board"
- breadcrumb_title _("Issue Board") - breadcrumb_title _("Issue Boards")
- page_title _("Boards") - page_title _("Boards")
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
...@@ -11,10 +11,11 @@ ...@@ -11,10 +11,11 @@
-# haml-lint:disable InlineJavaScript -# haml-lint:disable InlineJavaScript
%script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board" %script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal %script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal
%script#js-board-promotion{ type: "text/x-template" }= render_if_exists "shared/promotions/promote_issue_board"
#board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" } #board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
.d-none.d-sm-none.d-md-block .d-none.d-sm-none.d-md-block
= render 'shared/issuable/search_bar', type: :boards = render 'shared/issuable/search_bar', type: :boards, board: board
.boards-list .boards-list
.boards-app-loading.text-center{ "v-if" => "loading" } .boards-app-loading.text-center{ "v-if" => "loading" }
......
- type = local_assigns.fetch(:type) - type = local_assigns.fetch(:type)
- board = local_assigns.fetch(:board, nil)
- block_css_class = type != :boards_modal ? 'row-content-block second-block' : '' - block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
- full_path = @project.present? ? @project.full_path : @group.full_path - full_path = @project.present? ? @project.full_path : @group.full_path
- user_can_admin_list = board && can?(current_user, :admin_list, board.parent)
.issues-filters .issues-filters
.issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal } .issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal }
- if type == :boards
#js-multiple-boards-switcher.inline.boards-switcher{ "v-cloak" => true }
= render_if_exists "shared/boards/switcher", board: board
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:search].present? - if params[:search].present?
= hidden_field_tag :search, params[:search] = hidden_field_tag :search, params[:search]
...@@ -99,13 +104,18 @@ ...@@ -99,13 +104,18 @@
%gl-emoji %gl-emoji
%span.js-data-value.prepend-left-10 %span.js-data-value.prepend-left-10
{{name}} {{name}}
= render_if_exists 'shared/issuable/filter_weight', type: type
%button.clear-search.hidden{ type: 'button' } %button.clear-search.hidden{ type: 'button' }
= icon('times') = icon('times')
.filter-dropdown-container .filter-dropdown-container
- if type == :boards - if type == :boards
- if can?(current_user, :admin_list, board.parent) .js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
= render_if_exists 'shared/issuable/board_create_list_dropdown', board: board - if user_can_admin_list
= render 'shared/issuable/board_create_list_dropdown', board: board
- if @project - if @project
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } } #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
#js-toggle-focus-btn
- elsif type != :boards_modal - elsif type != :boards_modal
= render 'shared/sort_dropdown' = render 'shared/sort_dropdown'
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- ci = false unless local_assigns[:ci] == true - ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true
- user = local_assigns[:user] - user = local_assigns[:user]
- access = user&.max_member_access_for_project(project.id) unless user.nil? - access = max_project_member_access(project)
- css_class = '' unless local_assigns[:css_class] - css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project) - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
......
...@@ -5,7 +5,7 @@ class ArchiveTraceWorker ...@@ -5,7 +5,7 @@ class ArchiveTraceWorker
include PipelineBackgroundQueue include PipelineBackgroundQueue
def perform(job_id) def perform(job_id)
Ci::Build.find_by(id: job_id).try do |job| Ci::Build.without_archived_trace.find_by(id: job_id).try do |job|
job.trace.archive! job.trace.archive!
end end
end end
......
...@@ -12,6 +12,7 @@ module Ci ...@@ -12,6 +12,7 @@ module Ci
Ci::Build.finished.with_live_trace.find_each(batch_size: 100) do |build| Ci::Build.finished.with_live_trace.find_each(batch_size: 100) do |build|
begin begin
build.trace.archive! build.trace.archive!
rescue ::Gitlab::Ci::Trace::AlreadyArchivedError
rescue => e rescue => e
failed_archive_counter.increment failed_archive_counter.increment
Rails.logger.error "Failed to archive stale live trace. id: #{build.id} message: #{e.message}" Rails.logger.error "Failed to archive stale live trace. id: #{build.id} message: #{e.message}"
......
---
title: Fix double "in" in time to artifact deletion message
merge_request: 20357
author: "@bbodenmiller"
type: fixed
---
title: Present state indication on GFM preview
merge_request:
author:
type: added
---
title: Fixed bug when editing a comment in an issue,the preview mode is toggled in
the main textarea
merge_request: 20112
author: Constance Okoghenun
type: fixed
---
title: Improves performance of mr code, by fixing the state being mutated outside
of the store in the util function trimFirstCharOfLineContent and in map operations.
Avoids map operation in an empty array. Adds specs to the trimFirstCharOfLineContent
function
merge_request: 20380
author: filipa
type: performance
---
title: Deactivate new KubernetesService created from active template to prevent project creation from failing
merge_request:
author:
type: fixed
---
title: Check if archived trace exist before archive it
merge_request: 20297
author:
type: fixed
---
title: Fix wrong role badge displayed in projects dashboard
merge_request: 20374
author:
type: fixed
---
title: Add Web Terminal for Ci Builds
merge_request:
author: Vicky Chijwani
type: added
---
title: Updated Gitaly fail-fast timeout values
merge_request: !20259
author:
type: performance
---
title: Remove redundant query when removing trace
merge_request: 20324
author:
type: performance
...@@ -279,6 +279,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -279,6 +279,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :erase post :erase
get :trace, defaults: { format: 'json' } get :trace, defaults: { format: 'json' }
get :raw get :raw
get :terminal
get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', constraints: { format: nil }
end end
resource :artifacts, only: [] do resource :artifacts, only: [] do
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateCiBuildsRunnerSession < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
create_table :ci_builds_runner_session, id: :bigserial do |t|
t.integer :build_id, null: false
t.string :url, null: false
t.string :certificate
t.string :authorization
t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade
t.index :build_id, unique: true
end
end
end
...@@ -358,6 +358,15 @@ ActiveRecord::Schema.define(version: 20180629191052) do ...@@ -358,6 +358,15 @@ ActiveRecord::Schema.define(version: 20180629191052) do
add_index "ci_builds_metadata", ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree add_index "ci_builds_metadata", ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree
add_index "ci_builds_metadata", ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree add_index "ci_builds_metadata", ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree
create_table "ci_builds_runner_session", id: :bigserial, force: :cascade do |t|
t.integer "build_id", null: false
t.string "url", null: false
t.string "certificate"
t.string "authorization"
end
add_index "ci_builds_runner_session", ["build_id"], name: "index_ci_builds_runner_session_on_build_id", unique: true, using: :btree
create_table "ci_group_variables", force: :cascade do |t| create_table "ci_group_variables", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.text "value" t.text "value"
...@@ -2191,6 +2200,7 @@ ActiveRecord::Schema.define(version: 20180629191052) do ...@@ -2191,6 +2200,7 @@ ActiveRecord::Schema.define(version: 20180629191052) do
add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_builds_metadata", "projects", on_delete: :cascade add_foreign_key "ci_builds_metadata", "projects", on_delete: :cascade
add_foreign_key "ci_builds_runner_session", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
......
...@@ -511,6 +511,39 @@ GET /projects/:id ...@@ -511,6 +511,39 @@ GET /projects/:id
} }
``` ```
If the project is a fork, and you provide a valid token to authenticate, the
`forked_from_project` field will appear in the response.
```json
{
"id":3,
...
"forked_from_project":{
"id":13083,
"description":"GitLab Community Edition",
"name":"GitLab Community Edition",
"name_with_namespace":"GitLab.org / GitLab Community Edition",
"path":"gitlab-ce",
"path_with_namespace":"gitlab-org/gitlab-ce",
"created_at":"2013-09-26T06:02:36.000Z",
"default_branch":"master",
"tag_list":[],
"ssh_url_to_repo":"git@gitlab.com:gitlab-org/gitlab-ce.git",
"http_url_to_repo":"https://gitlab.com/gitlab-org/gitlab-ce.git",
"web_url":"https://gitlab.com/gitlab-org/gitlab-ce",
"avatar_url":"https://assets.gitlab-static.net/uploads/-/system/project/avatar/13083/logo-extra-whitespace.png",
"star_count":3812,
"forks_count":3561,
"last_activity_at":"2018-01-02T11:40:26.570Z"
}
...
}
```
## Get project users ## Get project users
Get the users list of a project. Get the users list of a project.
......
...@@ -57,9 +57,9 @@ so, the CI/CD job must be named `container_scanning` and the artifact path must ...@@ -57,9 +57,9 @@ so, the CI/CD job must be named `container_scanning` and the artifact path must
[Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html). [Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
CAUTION: **Caution:** CAUTION: **Caution:**
Container Scanning was previously using `sast:container` for job name and Before GitLab 11.0, Container Scanning was previously using `sast:container` for job name and
`gl-sast-container-report.json` for the artifact name. While these old names `gl-sast-container-report.json` for the artifact name. While these old names
are still maintained they have been deprecated with GitLab 11.0 and may be removed are still maintained, they have been deprecated with GitLab 11.0 and may be removed
in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml` in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
configuration to reflect that change. configuration to reflect that change.
......
...@@ -1203,6 +1203,7 @@ module API ...@@ -1203,6 +1203,7 @@ module API
class RunnerInfo < Grape::Entity class RunnerInfo < Grape::Entity
expose :metadata_timeout, as: :timeout expose :metadata_timeout, as: :timeout
expose :runner_session_url
end end
class Step < Grape::Entity class Step < Grape::Entity
......
...@@ -81,6 +81,11 @@ module API ...@@ -81,6 +81,11 @@ module API
requires :token, type: String, desc: %q(Runner's authentication token) requires :token, type: String, desc: %q(Runner's authentication token)
optional :last_update, type: String, desc: %q(Runner's queue last_update token) optional :last_update, type: String, desc: %q(Runner's queue last_update token)
optional :info, type: Hash, desc: %q(Runner's metadata) optional :info, type: Hash, desc: %q(Runner's metadata)
optional :session, type: Hash, desc: %q(Runner's session data) do
optional :url, type: String, desc: %q(Session's url)
optional :certificate, type: String, desc: %q(Session's certificate)
optional :authorization, type: String, desc: %q(Session's authorization)
end
end end
post '/request' do post '/request' do
authenticate_runner! authenticate_runner!
...@@ -90,14 +95,16 @@ module API ...@@ -90,14 +95,16 @@ module API
break no_content! break no_content!
end end
if current_runner.runner_queue_value_latest?(params[:last_update]) runner_params = declared_params(include_missing: false)
header 'X-GitLab-Last-Update', params[:last_update]
if current_runner.runner_queue_value_latest?(runner_params[:last_update])
header 'X-GitLab-Last-Update', runner_params[:last_update]
Gitlab::Metrics.add_event(:build_not_found_cached) Gitlab::Metrics.add_event(:build_not_found_cached)
break no_content! break no_content!
end end
new_update = current_runner.ensure_runner_queue_value new_update = current_runner.ensure_runner_queue_value
result = ::Ci::RegisterJobService.new(current_runner).execute result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
if result.valid? if result.valid?
if result.build if result.build
......
...@@ -6,6 +6,7 @@ module Gitlab ...@@ -6,6 +6,7 @@ module Gitlab
LEASE_TIMEOUT = 1.hour LEASE_TIMEOUT = 1.hour
ArchiveError = Class.new(StandardError) ArchiveError = Class.new(StandardError)
AlreadyArchivedError = Class.new(StandardError)
attr_reader :job attr_reader :job
...@@ -81,7 +82,9 @@ module Gitlab ...@@ -81,7 +82,9 @@ module Gitlab
def write(mode) def write(mode)
stream = Gitlab::Ci::Trace::Stream.new do stream = Gitlab::Ci::Trace::Stream.new do
if current_path if trace_artifact
raise AlreadyArchivedError, 'Could not write to the archived trace'
elsif current_path
File.open(current_path, mode) File.open(current_path, mode)
elsif Feature.enabled?('ci_enable_live_trace') elsif Feature.enabled?('ci_enable_live_trace')
Gitlab::Ci::Trace::ChunkedIO.new(job) Gitlab::Ci::Trace::ChunkedIO.new(job)
...@@ -98,14 +101,17 @@ module Gitlab ...@@ -98,14 +101,17 @@ module Gitlab
end end
def erase! def erase!
trace_artifact&.destroy ##
# Erase the archived trace
paths.each do |trace_path| trace_artifact&.destroy!
FileUtils.rm(trace_path, force: true)
end ##
# Erase the live trace
job.trace_chunks.fast_destroy_all job.trace_chunks.fast_destroy_all # Destroy chunks of a live trace
job.erase_old_trace! FileUtils.rm_f(current_path) if current_path # Remove a trace file of a live trace
job.erase_old_trace! if job.has_old_trace? # Remove a trace in database of a live trace
ensure
@current_path = nil
end end
def archive! def archive!
...@@ -117,7 +123,7 @@ module Gitlab ...@@ -117,7 +123,7 @@ module Gitlab
private private
def unsafe_archive! def unsafe_archive!
raise ArchiveError, 'Already archived' if trace_artifact raise AlreadyArchivedError, 'Could not archive again' if trace_artifact
raise ArchiveError, 'Job is not finished yet' unless job.complete? raise ArchiveError, 'Job is not finished yet' unless job.complete?
if job.trace_chunks.any? if job.trace_chunks.any?
......
...@@ -63,8 +63,12 @@ module Gitlab ...@@ -63,8 +63,12 @@ module Gitlab
# This saves us an RPC round trip. # This saves us an RPC round trip.
return nil if commit_id.include?(':') return nil if commit_id.include?(':')
commit = repo.wrapped_gitaly_errors do commit = repo.gitaly_migrate(:find_commit) do |is_enabled|
repo.gitaly_commit_client.find_commit(commit_id) if is_enabled
repo.gitaly_commit_client.find_commit(commit_id)
else
rugged_find(repo, commit_id)
end
end end
decorate(repo, commit) if commit decorate(repo, commit) if commit
...@@ -74,6 +78,12 @@ module Gitlab ...@@ -74,6 +78,12 @@ module Gitlab
nil nil
end end
def rugged_find(repo, commit_id)
obj = repo.rev_parse_target(commit_id)
obj.is_a?(Rugged::Commit) ? obj : nil
end
# Get last commit for HEAD # Get last commit for HEAD
# #
# Ex. # Ex.
......
...@@ -12,8 +12,14 @@ module Gitlab ...@@ -12,8 +12,14 @@ module Gitlab
end end
def conflicts def conflicts
@conflicts ||= @target_repository.wrapped_gitaly_errors do @conflicts ||= begin
gitaly_conflicts_client(@target_repository).list_conflict_files.to_a @target_repository.gitaly_migrate(:conflicts_list_conflict_files) do |is_enabled|
if is_enabled
gitaly_conflicts_client(@target_repository).list_conflict_files.to_a
else
rugged_list_conflict_files
end
end
end end
rescue GRPC::FailedPrecondition => e rescue GRPC::FailedPrecondition => e
raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message) raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message)
...@@ -22,8 +28,12 @@ module Gitlab ...@@ -22,8 +28,12 @@ module Gitlab
end end
def resolve_conflicts(source_repository, resolution, source_branch:, target_branch:) def resolve_conflicts(source_repository, resolution, source_branch:, target_branch:)
source_repository.wrapped_gitaly_errors do source_repository.gitaly_migrate(:conflicts_resolve_conflicts) do |is_enabled|
gitaly_conflicts_client(source_repository).resolve_conflicts(@target_repository, resolution, source_branch, target_branch) if is_enabled
gitaly_conflicts_client(source_repository).resolve_conflicts(@target_repository, resolution, source_branch, target_branch)
else
rugged_resolve_conflicts(source_repository, resolution, source_branch, target_branch)
end
end end
end end
...@@ -51,6 +61,57 @@ module Gitlab ...@@ -51,6 +61,57 @@ module Gitlab
def gitaly_conflicts_client(repository) def gitaly_conflicts_client(repository)
repository.gitaly_conflicts_client(@our_commit_oid, @their_commit_oid) repository.gitaly_conflicts_client(@our_commit_oid, @their_commit_oid)
end end
def write_resolved_file_to_index(repository, index, file, params)
if params[:sections]
resolved_lines = file.resolve_lines(params[:sections])
new_file = resolved_lines.map { |line| line[:full_line] }.join("\n")
new_file << "\n" if file.our_blob.data.end_with?("\n")
elsif params[:content]
new_file = file.resolve_content(params[:content])
end
our_path = file.our_path
oid = repository.rugged.write(new_file, :blob)
index.add(path: our_path, oid: oid, mode: file.our_mode)
index.conflict_remove(our_path)
end
def rugged_list_conflict_files
target_index = @target_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid)
# We don't need to do `with_repo_branch_commit` here, because the target
# project always fetches source refs when creating merge request diffs.
conflict_files(@target_repository, target_index)
end
def rugged_resolve_conflicts(source_repository, resolution, source_branch, target_branch)
source_repository.with_repo_branch_commit(@target_repository, target_branch) do
index = source_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid)
conflicts = conflict_files(source_repository, index)
resolution.files.each do |file_params|
conflict_file = conflict_for_path(conflicts, file_params[:old_path], file_params[:new_path])
write_resolved_file_to_index(source_repository, index, conflict_file, file_params)
end
unless index.conflicts.empty?
missing_files = index.conflicts.map { |file| file[:ours][:path] }
raise ResolutionError, "Missing resolutions for the following files: #{missing_files.join(', ')}"
end
commit_params = {
message: resolution.commit_message,
parents: [@our_commit_oid, @their_commit_oid]
}
source_repository.commit_index(resolution.user, source_branch, index, commit_params)
end
end
end end
end end
end end
......
...@@ -555,8 +555,12 @@ module Gitlab ...@@ -555,8 +555,12 @@ module Gitlab
# diff options. The +options+ hash can also include :break_rewrites to # diff options. The +options+ hash can also include :break_rewrites to
# split larger rewrites into delete/add pairs. # split larger rewrites into delete/add pairs.
def diff(from, to, options = {}, *paths) def diff(from, to, options = {}, *paths)
iterator = wrapped_gitaly_errors do iterator = gitaly_migrate(:diff_between) do |is_enabled|
gitaly_commit_client.diff(from, to, options.merge(paths: paths)) if is_enabled
gitaly_commit_client.diff(from, to, options.merge(paths: paths))
else
diff_patches(from, to, options, *paths)
end
end end
Gitlab::Git::DiffCollection.new(iterator, options) Gitlab::Git::DiffCollection.new(iterator, options)
...@@ -1587,6 +1591,17 @@ module Gitlab ...@@ -1587,6 +1591,17 @@ module Gitlab
tmp_entry tmp_entry
end end
# Return the Rugged patches for the diff between +from+ and +to+.
def diff_patches(from, to, options = {}, *paths)
options ||= {}
break_rewrites = options[:break_rewrites]
actual_options = Gitlab::Git::Diff.filter_diff_options(options.merge(paths: paths))
diff = rugged.diff(from, to, actual_options)
diff.find_similar!(break_rewrites: break_rewrites)
diff.each_patch
end
def sort_branches(branches, sort_by) def sort_branches(branches, sort_by)
case sort_by case sort_by
when 'name' when 'name'
......
...@@ -13,7 +13,7 @@ module Gitlab ...@@ -13,7 +13,7 @@ module Gitlab
oid: oid, oid: oid,
limit: limit limit: limit
) )
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request) response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request, timeout: GitalyClient.fast_timeout)
data = '' data = ''
blob = nil blob = nil
...@@ -43,7 +43,7 @@ module Gitlab ...@@ -43,7 +43,7 @@ module Gitlab
blob_ids: blob_ids blob_ids: blob_ids
) )
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request) response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
map_lfs_pointers(response) map_lfs_pointers(response)
end end
...@@ -66,7 +66,7 @@ module Gitlab ...@@ -66,7 +66,7 @@ module Gitlab
:blob_service, :blob_service,
:get_blobs, :get_blobs,
request, request,
timeout: GitalyClient.default_timeout timeout: GitalyClient.fast_timeout
) )
GitalyClient::BlobsStitcher.new(response) GitalyClient::BlobsStitcher.new(response)
...@@ -85,7 +85,7 @@ module Gitlab ...@@ -85,7 +85,7 @@ module Gitlab
request.not_in_refs += not_in request.not_in_refs += not_in
end end
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request) response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
map_lfs_pointers(response) map_lfs_pointers(response)
end end
...@@ -96,7 +96,7 @@ module Gitlab ...@@ -96,7 +96,7 @@ module Gitlab
revision: encode_binary(revision) revision: encode_binary(revision)
) )
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request) response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
map_lfs_pointers(response) map_lfs_pointers(response)
end end
......
...@@ -70,7 +70,7 @@ module Gitlab ...@@ -70,7 +70,7 @@ module Gitlab
def commit_deltas(commit) def commit_deltas(commit)
request = Gitaly::CommitDeltaRequest.new(diff_from_parent_request_params(commit)) request = Gitaly::CommitDeltaRequest.new(diff_from_parent_request_params(commit))
response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request) response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request, timeout: GitalyClient.fast_timeout)
response.flat_map { |msg| msg.deltas } response.flat_map { |msg| msg.deltas }
end end
...@@ -302,7 +302,7 @@ module Gitlab ...@@ -302,7 +302,7 @@ module Gitlab
end end
end end
response = GitalyClient.call(@repository.storage, :commit_service, :filter_shas_with_signatures, enum) response = GitalyClient.call(@repository.storage, :commit_service, :filter_shas_with_signatures, enum, timeout: GitalyClient.fast_timeout)
response.flat_map do |msg| response.flat_map do |msg|
msg.shas.map { |sha| EncodingHelper.encode!(sha) } msg.shas.map { |sha| EncodingHelper.encode!(sha) }
...@@ -330,7 +330,7 @@ module Gitlab ...@@ -330,7 +330,7 @@ module Gitlab
def get_commit_signatures(commit_ids) def get_commit_signatures(commit_ids)
request = Gitaly::GetCommitSignaturesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids) request = Gitaly::GetCommitSignaturesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids)
response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_signatures, request) response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_signatures, request, timeout: GitalyClient.fast_timeout)
signatures = Hash.new { |h, k| h[k] = [''.b, ''.b] } signatures = Hash.new { |h, k| h[k] = [''.b, ''.b] }
current_commit_id = nil current_commit_id = nil
...@@ -349,7 +349,7 @@ module Gitlab ...@@ -349,7 +349,7 @@ module Gitlab
def get_commit_messages(commit_ids) def get_commit_messages(commit_ids)
request = Gitaly::GetCommitMessagesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids) request = Gitaly::GetCommitMessagesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids)
response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_messages, request) response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_messages, request, timeout: GitalyClient.fast_timeout)
messages = Hash.new { |h, k| h[k] = ''.b } messages = Hash.new { |h, k| h[k] = ''.b }
current_commit_id = nil current_commit_id = nil
......
...@@ -46,7 +46,7 @@ module Gitlab ...@@ -46,7 +46,7 @@ module Gitlab
end end
end end
response = GitalyClient.call(@repository.storage, :conflicts_service, :resolve_conflicts, req_enum, remote_storage: target_repository.storage) response = GitalyClient.call(@repository.storage, :conflicts_service, :resolve_conflicts, req_enum, remote_storage: target_repository.storage, timeout: GitalyClient.medium_timeout)
if response.resolution_error.present? if response.resolution_error.present?
raise Gitlab::Git::Conflict::Resolver::ResolutionError, response.resolution_error raise Gitlab::Git::Conflict::Resolver::ResolutionError, response.resolution_error
......
...@@ -8,31 +8,31 @@ module Gitlab ...@@ -8,31 +8,31 @@ module Gitlab
def exists?(name) def exists?(name)
request = Gitaly::NamespaceExistsRequest.new(storage_name: @storage, name: name) request = Gitaly::NamespaceExistsRequest.new(storage_name: @storage, name: name)
gitaly_client_call(:namespace_exists, request).exists gitaly_client_call(:namespace_exists, request, timeout: GitalyClient.fast_timeout).exists
end end
def add(name) def add(name)
request = Gitaly::AddNamespaceRequest.new(storage_name: @storage, name: name) request = Gitaly::AddNamespaceRequest.new(storage_name: @storage, name: name)
gitaly_client_call(:add_namespace, request) gitaly_client_call(:add_namespace, request, timeout: GitalyClient.fast_timeout)
end end
def remove(name) def remove(name)
request = Gitaly::RemoveNamespaceRequest.new(storage_name: @storage, name: name) request = Gitaly::RemoveNamespaceRequest.new(storage_name: @storage, name: name)
gitaly_client_call(:remove_namespace, request) gitaly_client_call(:remove_namespace, request, timeout: nil)
end end
def rename(from, to) def rename(from, to)
request = Gitaly::RenameNamespaceRequest.new(storage_name: @storage, from: from, to: to) request = Gitaly::RenameNamespaceRequest.new(storage_name: @storage, from: from, to: to)
gitaly_client_call(:rename_namespace, request) gitaly_client_call(:rename_namespace, request, timeout: GitalyClient.fast_timeout)
end end
private private
def gitaly_client_call(type, request) def gitaly_client_call(type, request, timeout: nil)
GitalyClient.call(@storage, :namespace_service, type, request) GitalyClient.call(@storage, :namespace_service, type, request, timeout: timeout)
end end
end end
end end
......
...@@ -17,7 +17,7 @@ module Gitlab ...@@ -17,7 +17,7 @@ module Gitlab
user: Gitlab::Git::User.from_gitlab(user).to_gitaly user: Gitlab::Git::User.from_gitlab(user).to_gitaly
) )
response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_tag, request) response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_tag, request, timeout: GitalyClient.medium_timeout)
if pre_receive_error = response.pre_receive_error.presence if pre_receive_error = response.pre_receive_error.presence
raise Gitlab::Git::PreReceiveError, pre_receive_error raise Gitlab::Git::PreReceiveError, pre_receive_error
...@@ -33,7 +33,7 @@ module Gitlab ...@@ -33,7 +33,7 @@ module Gitlab
message: encode_binary(message.to_s) message: encode_binary(message.to_s)
) )
response = GitalyClient.call(@repository.storage, :operation_service, :user_create_tag, request) response = GitalyClient.call(@repository.storage, :operation_service, :user_create_tag, request, timeout: GitalyClient.medium_timeout)
if pre_receive_error = response.pre_receive_error.presence if pre_receive_error = response.pre_receive_error.presence
raise Gitlab::Git::PreReceiveError, pre_receive_error raise Gitlab::Git::PreReceiveError, pre_receive_error
elsif response.exists elsif response.exists
...@@ -276,7 +276,8 @@ module Gitlab ...@@ -276,7 +276,8 @@ module Gitlab
:operation_service, :operation_service,
:"user_#{rpc}", :"user_#{rpc}",
request, request,
remote_storage: start_repository.storage remote_storage: start_repository.storage,
timeout: GitalyClient.medium_timeout
) )
handle_cherry_pick_or_revert_response(response) handle_cherry_pick_or_revert_response(response)
......
...@@ -12,7 +12,7 @@ module Gitlab ...@@ -12,7 +12,7 @@ module Gitlab
def branches def branches
request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo) request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request) response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout)
consume_find_all_branches_response(response) consume_find_all_branches_response(response)
end end
...@@ -23,26 +23,26 @@ module Gitlab ...@@ -23,26 +23,26 @@ module Gitlab
merged_only: true, merged_only: true,
merged_branches: branch_names.map { |s| encode_binary(s) } merged_branches: branch_names.map { |s| encode_binary(s) }
) )
response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request) response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request, timeout: GitalyClient.fast_timeout)
consume_find_all_branches_response(response) consume_find_all_branches_response(response)
end end
def default_branch_name def default_branch_name
request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo) request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request) response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request, timeout: GitalyClient.fast_timeout)
Gitlab::Git.branch_name(response.name) Gitlab::Git.branch_name(response.name)
end end
def branch_names def branch_names
request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo) request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request) response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request, timeout: GitalyClient.fast_timeout)
consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) } consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) }
end end
def tag_names def tag_names
request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo) request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_tag_names, request) response = GitalyClient.call(@storage, :ref_service, :find_all_tag_names, request, timeout: GitalyClient.fast_timeout)
consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) } consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) }
end end
...@@ -67,19 +67,19 @@ module Gitlab ...@@ -67,19 +67,19 @@ module Gitlab
def local_branches(sort_by: nil) def local_branches(sort_by: nil)
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
request.sort_by = sort_by_param(sort_by) if sort_by request.sort_by = sort_by_param(sort_by) if sort_by
response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request) response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request, timeout: GitalyClient.fast_timeout)
consume_find_local_branches_response(response) consume_find_local_branches_response(response)
end end
def tags def tags
request = Gitaly::FindAllTagsRequest.new(repository: @gitaly_repo) request = Gitaly::FindAllTagsRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_tags, request) response = GitalyClient.call(@storage, :ref_service, :find_all_tags, request, timeout: GitalyClient.medium_timeout)
consume_tags_response(response) consume_tags_response(response)
end end
def ref_exists?(ref_name) def ref_exists?(ref_name)
request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: encode_binary(ref_name)) request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: encode_binary(ref_name))
response = GitalyClient.call(@storage, :ref_service, :ref_exists, request) response = GitalyClient.call(@storage, :ref_service, :ref_exists, request, timeout: GitalyClient.fast_timeout)
response.value response.value
rescue GRPC::InvalidArgument => e rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message raise ArgumentError, e.message
...@@ -91,7 +91,7 @@ module Gitlab ...@@ -91,7 +91,7 @@ module Gitlab
name: encode_binary(branch_name) name: encode_binary(branch_name)
) )
response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request) response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request, timeout: GitalyClient.medium_timeout)
branch = response.branch branch = response.branch
return unless branch return unless branch
...@@ -140,7 +140,7 @@ module Gitlab ...@@ -140,7 +140,7 @@ module Gitlab
except_with_prefix: except_with_prefixes.map { |r| encode_binary(r) } except_with_prefix: except_with_prefixes.map { |r| encode_binary(r) }
) )
response = GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request) response = GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request, timeout: GitalyClient.fast_timeout)
raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present? raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
end end
...@@ -153,7 +153,7 @@ module Gitlab ...@@ -153,7 +153,7 @@ module Gitlab
limit: limit limit: limit
) )
stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request) stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request, timeout: GitalyClient.medium_timeout)
consume_ref_contains_sha_response(stream, :tag_names) consume_ref_contains_sha_response(stream, :tag_names)
end end
...@@ -166,14 +166,14 @@ module Gitlab ...@@ -166,14 +166,14 @@ module Gitlab
limit: limit limit: limit
) )
stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request) stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request, timeout: GitalyClient.medium_timeout)
consume_ref_contains_sha_response(stream, :branch_names) consume_ref_contains_sha_response(stream, :branch_names)
end end
def get_tag_messages(tag_ids) def get_tag_messages(tag_ids)
request = Gitaly::GetTagMessagesRequest.new(repository: @gitaly_repo, tag_ids: tag_ids) request = Gitaly::GetTagMessagesRequest.new(repository: @gitaly_repo, tag_ids: tag_ids)
response = GitalyClient.call(@repository.storage, :ref_service, :get_tag_messages, request) response = GitalyClient.call(@repository.storage, :ref_service, :get_tag_messages, request, timeout: GitalyClient.fast_timeout)
messages = Hash.new { |h, k| h[k] = ''.b } messages = Hash.new { |h, k| h[k] = ''.b }
current_tag_id = nil current_tag_id = nil
......
...@@ -28,13 +28,13 @@ module Gitlab ...@@ -28,13 +28,13 @@ module Gitlab
mirror_refmaps: Array.wrap(mirror_refmaps).map(&:to_s) mirror_refmaps: Array.wrap(mirror_refmaps).map(&:to_s)
) )
GitalyClient.call(@storage, :remote_service, :add_remote, request) GitalyClient.call(@storage, :remote_service, :add_remote, request, timeout: GitalyClient.fast_timeout)
end end
def remove_remote(name) def remove_remote(name)
request = Gitaly::RemoveRemoteRequest.new(repository: @gitaly_repo, name: name) request = Gitaly::RemoveRemoteRequest.new(repository: @gitaly_repo, name: name)
response = GitalyClient.call(@storage, :remote_service, :remove_remote, request) response = GitalyClient.call(@storage, :remote_service, :remove_remote, request, timeout: GitalyClient.fast_timeout)
response.result response.result
end end
......
...@@ -21,7 +21,7 @@ module Gitlab ...@@ -21,7 +21,7 @@ module Gitlab
def cleanup def cleanup
request = Gitaly::CleanupRequest.new(repository: @gitaly_repo) request = Gitaly::CleanupRequest.new(repository: @gitaly_repo)
GitalyClient.call(@storage, :repository_service, :cleanup, request) GitalyClient.call(@storage, :repository_service, :cleanup, request, timeout: GitalyClient.fast_timeout)
end end
def garbage_collect(create_bitmap) def garbage_collect(create_bitmap)
...@@ -41,13 +41,13 @@ module Gitlab ...@@ -41,13 +41,13 @@ module Gitlab
def repository_size def repository_size
request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo) request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :repository_service, :repository_size, request) response = GitalyClient.call(@storage, :repository_service, :repository_size, request, timeout: GitalyClient.medium_timeout)
response.size response.size
end end
def apply_gitattributes(revision) def apply_gitattributes(revision)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision)) request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision))
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request) GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request, timeout: GitalyClient.fast_timeout)
rescue GRPC::InvalidArgument => ex rescue GRPC::InvalidArgument => ex
raise Gitlab::Git::Repository::InvalidRef, ex raise Gitlab::Git::Repository::InvalidRef, ex
end end
...@@ -55,7 +55,7 @@ module Gitlab ...@@ -55,7 +55,7 @@ module Gitlab
def info_attributes def info_attributes
request = Gitaly::GetInfoAttributesRequest.new(repository: @gitaly_repo) request = Gitaly::GetInfoAttributesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :repository_service, :get_info_attributes, request) response = GitalyClient.call(@storage, :repository_service, :get_info_attributes, request, timeout: GitalyClient.fast_timeout)
response.each_with_object("") do |message, attributes| response.each_with_object("") do |message, attributes|
attributes << message.attributes attributes << message.attributes
end end
...@@ -82,7 +82,7 @@ module Gitlab ...@@ -82,7 +82,7 @@ module Gitlab
def create_repository def create_repository
request = Gitaly::CreateRepositoryRequest.new(repository: @gitaly_repo) request = Gitaly::CreateRepositoryRequest.new(repository: @gitaly_repo)
GitalyClient.call(@storage, :repository_service, :create_repository, request) GitalyClient.call(@storage, :repository_service, :create_repository, request, timeout: GitalyClient.medium_timeout)
end end
def has_local_branches? def has_local_branches?
...@@ -98,7 +98,7 @@ module Gitlab ...@@ -98,7 +98,7 @@ module Gitlab
revisions: revisions.map { |r| encode_binary(r) } revisions: revisions.map { |r| encode_binary(r) }
) )
response = GitalyClient.call(@storage, :repository_service, :find_merge_base, request) response = GitalyClient.call(@storage, :repository_service, :find_merge_base, request, timeout: GitalyClient.fast_timeout)
response.base.presence response.base.presence
end end
...@@ -258,7 +258,7 @@ module Gitlab ...@@ -258,7 +258,7 @@ module Gitlab
) )
request.old_revision = old_ref.b unless old_ref.nil? request.old_revision = old_ref.b unless old_ref.nil?
response = GitalyClient.call(@storage, :repository_service, :write_ref, request) response = GitalyClient.call(@storage, :repository_service, :write_ref, request, timeout: GitalyClient.fast_timeout)
raise Gitlab::Git::CommandError, encode!(response.error) if response.error.present? raise Gitlab::Git::CommandError, encode!(response.error) if response.error.present?
...@@ -288,7 +288,7 @@ module Gitlab ...@@ -288,7 +288,7 @@ module Gitlab
def calculate_checksum def calculate_checksum
request = Gitaly::CalculateChecksumRequest.new(repository: @gitaly_repo) request = Gitaly::CalculateChecksumRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :repository_service, :calculate_checksum, request) response = GitalyClient.call(@storage, :repository_service, :calculate_checksum, request, timeout: GitalyClient.fast_timeout)
response.checksum.presence response.checksum.presence
rescue GRPC::DataLoss => e rescue GRPC::DataLoss => e
raise Gitlab::Git::Repository::InvalidRepository.new(e) raise Gitlab::Git::Repository::InvalidRepository.new(e)
...@@ -297,12 +297,12 @@ module Gitlab ...@@ -297,12 +297,12 @@ module Gitlab
def raw_changes_between(from, to) def raw_changes_between(from, to)
request = Gitaly::GetRawChangesRequest.new(repository: @gitaly_repo, from_revision: from, to_revision: to) request = Gitaly::GetRawChangesRequest.new(repository: @gitaly_repo, from_revision: from, to_revision: to)
GitalyClient.call(@storage, :repository_service, :get_raw_changes, request) GitalyClient.call(@storage, :repository_service, :get_raw_changes, request, timeout: GitalyClient.fast_timeout)
end end
def search_files_by_name(ref, query) def search_files_by_name(ref, query)
request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query) request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query)
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request).flat_map(&:files) GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
end end
def search_files_by_content(ref, query) def search_files_by_content(ref, query)
......
...@@ -69,7 +69,7 @@ module Gitlab ...@@ -69,7 +69,7 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details) commit_details: gitaly_commit_details(commit_details)
) )
GitalyClient.call(@repository.storage, :wiki_service, :wiki_delete_page, request) GitalyClient.call(@repository.storage, :wiki_service, :wiki_delete_page, request, timeout: GitalyClient.medium_timeout)
end end
def find_page(title:, version: nil, dir: nil) def find_page(title:, version: nil, dir: nil)
...@@ -80,14 +80,14 @@ module Gitlab ...@@ -80,14 +80,14 @@ module Gitlab
directory: encode_binary(dir) directory: encode_binary(dir)
) )
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_page, request) response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_page, request, timeout: GitalyClient.fast_timeout)
wiki_page_from_iterator(response) wiki_page_from_iterator(response)
end end
def get_all_pages def get_all_pages
request = Gitaly::WikiGetAllPagesRequest.new(repository: @gitaly_repo) request = Gitaly::WikiGetAllPagesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request) response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request, timeout: GitalyClient.medium_timeout)
pages = [] pages = []
loop do loop do
...@@ -113,7 +113,7 @@ module Gitlab ...@@ -113,7 +113,7 @@ module Gitlab
per_page: options[:per_page] || Gollum::Page.per_page per_page: options[:per_page] || Gollum::Page.per_page
) )
stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request) stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request, timeout: GitalyClient.medium_timeout)
versions = [] versions = []
stream.each do |message| stream.each do |message|
...@@ -132,7 +132,7 @@ module Gitlab ...@@ -132,7 +132,7 @@ module Gitlab
revision: encode_binary(revision) revision: encode_binary(revision)
) )
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_file, request) response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_file, request, timeout: GitalyClient.fast_timeout)
wiki_file = nil wiki_file = nil
response.each do |message| response.each do |message|
......
...@@ -32,6 +32,12 @@ module QA ...@@ -32,6 +32,12 @@ module QA
end end
def self.configure! def self.configure!
RSpec.configure do |config|
config.define_derived_metadata(file_path: %r{/qa/specs/features/}) do |metadata|
metadata[:type] = :feature
end
end
return if Capybara.drivers.include?(:chrome) return if Capybara.drivers.include?(:chrome)
Capybara.register_driver :chrome do |app| Capybara.register_driver :chrome do |app|
......
...@@ -562,4 +562,105 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -562,4 +562,105 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end end
end end
end end
describe 'GET #terminal' do
before do
project.add_developer(user)
sign_in(user)
end
context 'when job exists' do
context 'and it has a terminal' do
let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) }
it 'has a job' do
get_terminal(id: job.id)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:build).id).to eq(job.id)
end
end
context 'and does not have a terminal' do
let!(:job) { create(:ci_build, :running, pipeline: pipeline) }
it 'returns not_found' do
get_terminal(id: job.id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when job does not exist' do
it 'renders not_found' do
get_terminal(id: 1234)
expect(response).to have_gitlab_http_status(:not_found)
end
end
def get_terminal(**extra_params)
params = {
namespace_id: project.namespace.to_param,
project_id: project
}
get :terminal, params.merge(extra_params)
end
end
describe 'GET #terminal_websocket_authorize' do
let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) }
before do
project.add_developer(user)
sign_in(user)
end
context 'with valid workhorse signature' do
before do
allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_return(nil)
end
context 'and valid id' do
it 'returns the terminal for the job' do
expect(Gitlab::Workhorse)
.to receive(:terminal_websocket)
.and_return(workhorse: :response)
get_terminal_websocket(id: job.id)
expect(response).to have_gitlab_http_status(200)
expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(response.body).to eq('{"workhorse":"response"}')
end
end
context 'and invalid id' do
it 'returns 404' do
get_terminal_websocket(id: 1234)
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'with invalid workhorse signature' do
it 'aborts with an exception' do
allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_raise(JWT::DecodeError)
expect { get_terminal_websocket(id: job.id) }.to raise_error(JWT::DecodeError)
end
end
def get_terminal_websocket(**extra_params)
params = {
namespace_id: project.namespace.to_param,
project_id: project
}
get :terminal_websocket_authorize, params.merge(extra_params)
end
end
end end
...@@ -74,7 +74,7 @@ describe Projects::PipelinesController do ...@@ -74,7 +74,7 @@ describe Projects::PipelinesController do
expect(stages.count).to eq 3 expect(stages.count).to eq 3
end end
expect(queries.count).to be_within(3).of(30) expect(queries.count).to be_within(5).of(30)
end end
end end
......
...@@ -616,13 +616,40 @@ describe ProjectsController do ...@@ -616,13 +616,40 @@ describe ProjectsController do
end end
describe 'POST #preview_markdown' do describe 'POST #preview_markdown' do
it 'renders json in a correct format' do before do
sign_in(user) sign_in(user)
end
it 'renders json in a correct format' do
post :preview_markdown, namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' post :preview_markdown, namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text'
expect(JSON.parse(response.body).keys).to match_array(%w(body references)) expect(JSON.parse(response.body).keys).to match_array(%w(body references))
end end
context 'state filter on references' do
let(:issue) { create(:issue, :closed, project: public_project) }
let(:merge_request) { create(:merge_request, :closed, target_project: public_project) }
it 'renders JSON body with state filter for issues' do
post :preview_markdown, namespace_id: public_project.namespace,
id: public_project,
text: issue.to_reference
json_response = JSON.parse(response.body)
expect(json_response['body']).to match(/\##{issue.iid} \(closed\)/)
end
it 'renders JSON body with state filter for MRs' do
post :preview_markdown, namespace_id: public_project.namespace,
id: public_project,
text: merge_request.to_reference
json_response = JSON.parse(response.body)
expect(json_response['body']).to match(/\!#{merge_request.iid} \(closed\)/)
end
end
end end
describe '#ensure_canonical_path' do describe '#ensure_canonical_path' do
......
...@@ -248,5 +248,11 @@ FactoryBot.define do ...@@ -248,5 +248,11 @@ FactoryBot.define do
failed failed
failure_reason 2 failure_reason 2
end end
trait :with_runner_session do
after(:build) do |build|
build.build_runner_session(url: 'ws://localhost')
end
end
end end
end end
...@@ -29,6 +29,34 @@ describe 'Dashboard Projects' do ...@@ -29,6 +29,34 @@ describe 'Dashboard Projects' do
end end
end end
context 'when user has access to the project' do
it 'shows role badge' do
visit dashboard_projects_path
page.within '.user-access-role' do
expect(page).to have_content('Developer')
end
end
context 'when role changes', :use_clean_rails_memory_store_fragment_caching do
it 'displays the right role' do
visit dashboard_projects_path
page.within '.user-access-role' do
expect(page).to have_content('Developer')
end
project.members.last.update(access_level: 40)
visit dashboard_projects_path
page.within '.user-access-role' do
expect(page).to have_content('Maintainer')
end
end
end
end
context 'when last_repository_updated_at, last_activity_at and update_at are present' do context 'when last_repository_updated_at, last_activity_at and update_at are present' do
it 'shows the last_repository_updated_at attribute as the update date' do it 'shows the last_repository_updated_at attribute as the update date' do
project.update_attributes!(last_repository_updated_at: Time.now, last_activity_at: 1.hour.ago) project.update_attributes!(last_repository_updated_at: Time.now, last_activity_at: 1.hour.ago)
......
...@@ -63,6 +63,14 @@ describe "User comments on issue", :js do ...@@ -63,6 +63,14 @@ describe "User comments on issue", :js do
page.within(".current-note-edit-form") do page.within(".current-note-edit-form") do
fill_in("note[note]", with: comment) fill_in("note[note]", with: comment)
find('textarea').send_keys [:control, :shift, 'p']
expect(page).to have_selector('.current-note-edit-form .md-preview-holder')
expect(page.find('.current-note-edit-form .md-preview-holder p')).to have_content(comment)
end
expect(page).to have_selector('.new-note .note-textarea')
page.within(".current-note-edit-form") do
click_button("Save comment") click_button("Save comment")
end end
......
...@@ -80,6 +80,7 @@ describe ProjectsHelper do ...@@ -80,6 +80,7 @@ describe ProjectsHelper do
before do before do
allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(user, :read_cross_project) { true } allow(helper).to receive(:can?).with(user, :read_cross_project) { true }
allow(user).to receive(:max_member_access_for_project).and_return(40)
end end
it "includes the route" do it "includes the route" do
...@@ -125,6 +126,10 @@ describe ProjectsHelper do ...@@ -125,6 +126,10 @@ describe ProjectsHelper do
expect(helper.project_list_cache_key(project)).to include("pipeline-status/#{project.commit.sha}-success") expect(helper.project_list_cache_key(project)).to include("pipeline-status/#{project.commit.sha}-success")
end end
it "includes the user max member access" do
expect(helper.project_list_cache_key(project)).to include('access:40')
end
end end
describe '#load_pipeline_status' do describe '#load_pipeline_status' do
......
...@@ -280,11 +280,11 @@ describe('diff_file_header', () => { ...@@ -280,11 +280,11 @@ describe('diff_file_header', () => {
}); });
}); });
it('displays an icon in the title', () => { it('displays an file icon in the title', () => {
vm = mountComponent(Component, props); vm = mountComponent(Component, props);
expect(vm.$el.querySelector('svg.js-file-icon use').getAttribute('xlink:href')).toContain(
const icon = vm.$el.querySelector(`i[class="fa fa-fw fa-${vm.icon}"]`); 'ruby',
expect(icon).not.toBe(null); );
}); });
describe('file paths', () => { describe('file paths', () => {
......
...@@ -176,4 +176,35 @@ describe('DiffsStoreUtils', () => { ...@@ -176,4 +176,35 @@ describe('DiffsStoreUtils', () => {
expect(linesWithReferences[1].metaData.newPos).toEqual(3); expect(linesWithReferences[1].metaData.newPos).toEqual(3);
}); });
}); });
describe('trimFirstCharOfLineContent', () => {
it('trims the line when it starts with a space', () => {
expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({ richText: 'diff' });
});
it('trims the line when it starts with a +', () => {
expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({ richText: 'diff' });
});
it('trims the line when it starts with a -', () => {
expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({ richText: 'diff' });
});
it('does not trims the line when it starts with a letter', () => {
expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({ richText: 'diff' });
});
it('does not modify the provided object', () => {
const lineObj = {
richText: ' diff',
};
utils.trimFirstCharOfLineContent(lineObj);
expect(lineObj).toEqual({ richText: ' diff' });
});
it('handles a undefined or null parameter', () => {
expect(utils.trimFirstCharOfLineContent()).toEqual({});
});
});
}); });
...@@ -80,6 +80,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont ...@@ -80,6 +80,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_discussions_json(merge_request, example.description) render_discussions_json(merge_request, example.description)
end end
it 'merge_requests/resolved_diff_discussion.json' do |example|
note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request)
create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id)
render_discussions_json(merge_request, example.description)
end
context 'with image diff' do context 'with image diff' do
let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images") } let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images") }
let(:image_path) { "files/images/ee_repo_logo.png" } let(:image_path) { "files/images/ee_repo_logo.png" }
......
...@@ -32,12 +32,12 @@ describe('DiscussionCounter component', () => { ...@@ -32,12 +32,12 @@ describe('DiscussionCounter component', () => {
{ {
...discussionMock, ...discussionMock,
id: discussionMock.id, id: discussionMock.id,
notes: [{ ...discussionMock.notes[0], resolved: true }], notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }],
}, },
{ {
...discussionMock, ...discussionMock,
id: discussionMock.id + 1, id: discussionMock.id + 1,
notes: [{ ...discussionMock.notes[0], resolved: false }], notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: false }],
}, },
]; ];
const firstDiscussionId = discussionMock.id + 1; const firstDiscussionId = discussionMock.id + 1;
......
...@@ -4,22 +4,23 @@ import noteableDiscussion from '~/notes/components/noteable_discussion.vue'; ...@@ -4,22 +4,23 @@ import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
describe('noteable_discussion component', () => { describe('noteable_discussion component', () => {
const Component = Vue.extend(noteableDiscussion);
let store; let store;
let vm; let vm;
beforeEach(() => { preloadFixtures(discussionWithTwoUnresolvedNotes);
const Component = Vue.extend(noteableDiscussion);
beforeEach(() => {
store = createStore(); store = createStore();
store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock); store.dispatch('setNotesData', notesDataMock);
vm = new Component({ vm = new Component({
store, store,
propsData: { propsData: { discussion: discussionMock },
discussion: discussionMock,
},
}).$mount(); }).$mount();
}); });
...@@ -84,7 +85,9 @@ describe('noteable_discussion component', () => { ...@@ -84,7 +85,9 @@ describe('noteable_discussion component', () => {
}); });
it('is true if there are two unresolved discussions', done => { it('is true if there are two unresolved discussions', done => {
spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([{}, {}]); const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
discussion.notes[0].resolved = false;
vm.$store.dispatch('setInitialNotes', [discussion, discussion]);
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
...@@ -105,12 +108,12 @@ describe('noteable_discussion component', () => { ...@@ -105,12 +108,12 @@ describe('noteable_discussion component', () => {
{ {
...discussionMock, ...discussionMock,
id: discussionMock.id + 1, id: discussionMock.id + 1,
notes: [{ ...discussionMock.notes[0], resolved: true }], notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }],
}, },
{ {
...discussionMock, ...discussionMock,
id: discussionMock.id + 2, id: discussionMock.id + 2,
notes: [{ ...discussionMock.notes[0], resolved: false }], notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: false }],
}, },
]; ];
const nextDiscussionId = discussionMock.id + 2; const nextDiscussionId = discussionMock.id + 2;
......
...@@ -303,6 +303,7 @@ export const discussionMock = { ...@@ -303,6 +303,7 @@ export const discussionMock = {
}, },
], ],
individual_note: false, individual_note: false,
resolvable: true,
}; };
export const loggedOutnoteableData = { export const loggedOutnoteableData = {
......
...@@ -7,9 +7,13 @@ import { ...@@ -7,9 +7,13 @@ import {
collapseNotesMock, collapseNotesMock,
} from '../mock_data'; } from '../mock_data';
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
describe('Getters Notes Store', () => { describe('Getters Notes Store', () => {
let state; let state;
preloadFixtures(discussionWithTwoUnresolvedNotes);
beforeEach(() => { beforeEach(() => {
state = { state = {
discussions: [individualNote], discussions: [individualNote],
...@@ -22,12 +26,26 @@ describe('Getters Notes Store', () => { ...@@ -22,12 +26,26 @@ describe('Getters Notes Store', () => {
noteableData: noteableDataMock, noteableData: noteableDataMock,
}; };
}); });
describe('discussions', () => { describe('discussions', () => {
it('should return all discussions in the store', () => { it('should return all discussions in the store', () => {
expect(getters.discussions(state)).toEqual([individualNote]); expect(getters.discussions(state)).toEqual([individualNote]);
}); });
}); });
describe('resolvedDiscussionsById', () => {
it('ignores unresolved system notes', () => {
const [discussion] = getJSONFixture(discussionWithTwoUnresolvedNotes);
discussion.notes[0].resolved = true;
discussion.notes[1].resolved = false;
state.discussions.push(discussion);
expect(getters.resolvedDiscussionsById(state)).toEqual({
[discussion.id]: discussion,
});
});
});
describe('Collapsed notes', () => { describe('Collapsed notes', () => {
const stateCollapsedNotes = { const stateCollapsedNotes = {
discussions: collapseNotesMock, discussions: collapseNotesMock,
......
...@@ -51,7 +51,7 @@ describe('Markdown field header component', () => { ...@@ -51,7 +51,7 @@ describe('Markdown field header component', () => {
spyOn(vm, '$emit'); spyOn(vm, '$emit');
$(document).triggerHandler('markdown-preview:show', [ $(document).triggerHandler('markdown-preview:show', [
$('<form><textarea class="markdown-area"></textarea></textarea></form>'), $('<form><div class="js-vue-markdown-field"><textarea class="markdown-area"></textarea></div></form>'),
]); ]);
expect(vm.$emit).not.toHaveBeenCalled(); expect(vm.$emit).not.toHaveBeenCalled();
......
require 'spec_helper'
describe Ci::BuildRunnerSession, model: true do
let!(:build) { create(:ci_build, :with_runner_session) }
subject { build.runner_session }
it { is_expected.to belong_to(:build) }
it { is_expected.to validate_presence_of(:build) }
it { is_expected.to validate_presence_of(:url).with_message('must be a valid URL') }
describe '#terminal_specification' do
let(:terminal_specification) { subject.terminal_specification }
it 'returns empty hash if no url' do
subject.url = ''
expect(terminal_specification).to be_empty
end
context 'when url is present' do
it 'returns ca_pem nil if empty certificate' do
subject.certificate = ''
expect(terminal_specification[:ca_pem]).to be_nil
end
it 'adds Authorization header if authorization is present' do
subject.authorization = 'whatever'
expect(terminal_specification[:headers]).to include(Authorization: 'whatever')
end
end
end
end
...@@ -19,6 +19,7 @@ describe Ci::Build do ...@@ -19,6 +19,7 @@ describe Ci::Build do
it { is_expected.to belong_to(:erased_by) } it { is_expected.to belong_to(:erased_by) }
it { is_expected.to have_many(:deployments) } it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:trace_sections)} it { is_expected.to have_many(:trace_sections)}
it { is_expected.to have_one(:runner_session)}
it { is_expected.to validate_presence_of(:ref) } it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) } it { is_expected.to respond_to(:trace) }
...@@ -42,6 +43,20 @@ describe Ci::Build do ...@@ -42,6 +43,20 @@ describe Ci::Build do
end end
end end
describe 'status' do
context 'when transitioning to any state from running' do
it 'removes runner_session' do
%w(success drop cancel).each do |event|
build = FactoryBot.create(:ci_build, :running, :with_runner_session, pipeline: pipeline)
build.fire_events!(event)
expect(build.reload.runner_session).to be_nil
end
end
end
end
describe '.manual_actions' do describe '.manual_actions' do
let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) } let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) }
let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) } let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) }
...@@ -499,6 +514,22 @@ describe Ci::Build do ...@@ -499,6 +514,22 @@ describe Ci::Build do
end end
end end
describe '#has_old_trace?' do
subject { build.has_old_trace? }
context 'when old trace exists' do
before do
build.update_column(:trace, 'old trace')
end
it { is_expected.to be_truthy }
end
context 'when old trace does not exist' do
it { is_expected.to be_falsy }
end
end
describe '#trace=' do describe '#trace=' do
it "expect to fail trace=" do it "expect to fail trace=" do
expect { build.trace = "new" }.to raise_error(NotImplementedError) expect { build.trace = "new" }.to raise_error(NotImplementedError)
...@@ -518,16 +549,32 @@ describe Ci::Build do ...@@ -518,16 +549,32 @@ describe Ci::Build do
end end
describe '#erase_old_trace!' do describe '#erase_old_trace!' do
subject { build.send(:read_attribute, :trace) } subject { build.erase_old_trace! }
before do context 'when old trace exists' do
build.send(:write_attribute, :trace, 'old trace') before do
build.update_column(:trace, 'old trace')
end
it "erases old trace" do
subject
expect(build.old_trace).to be_nil
end
it "executes UPDATE query" do
recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.log.select { |l| l.match?(/UPDATE.*ci_builds/) }.count).to eq(1)
end
end end
it "expect to receive data from database" do context 'when old trace does not exist' do
build.erase_old_trace! it 'does not execute UPDATE query' do
recorded = ActiveRecord::QueryRecorder.new { subject }
is_expected.to be_nil expect(recorded.log.select { |l| l.match?(/UPDATE.*ci_builds/) }.count).to eq(0)
end
end end
end end
...@@ -2605,4 +2652,39 @@ describe Ci::Build do ...@@ -2605,4 +2652,39 @@ describe Ci::Build do
end end
end end
end end
describe '#has_terminal?' do
let(:states) { described_class.state_machines[:status].states.keys - [:running] }
subject { build.has_terminal? }
it 'returns true if the build is running and it has a runner_session_url' do
build.build_runner_session(url: 'whatever')
build.status = :running
expect(subject).to be_truthy
end
context 'returns false' do
it 'when runner_session_url is empty' do
build.status = :running
expect(subject).to be_falsey
end
context 'unless the build is running' do
before do
build.build_runner_session(url: 'whatever')
end
it do
states.each do |state|
build.status = state
is_expected.to be_falsey
end
end
end
end
end
end end
...@@ -78,7 +78,7 @@ describe Service do ...@@ -78,7 +78,7 @@ describe Service do
context 'when template is invalid' do context 'when template is invalid' do
it 'sets service template to inactive when template is invalid' do it 'sets service template to inactive when template is invalid' do
project = create(:project) project = create(:project)
template = JiraService.new(template: true, active: true) template = KubernetesService.new(template: true, active: true)
template.save(validate: false) template.save(validate: false)
service = described_class.build_from_template(project.id, template) service = described_class.build_from_template(project.id, template)
......
...@@ -851,6 +851,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do ...@@ -851,6 +851,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it 'does not update job status and job trace' do it 'does not update job status and job trace' do
update_job(state: 'success', trace: 'BUILD TRACE UPDATED') update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
job.reload
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(403)
expect(response.header['Job-Status']).to eq 'failed' expect(response.header['Job-Status']).to eq 'failed'
expect(job.trace.raw).to eq 'Job failed' expect(job.trace.raw).to eq 'Job failed'
......
...@@ -548,8 +548,21 @@ module Ci ...@@ -548,8 +548,21 @@ module Ci
end end
end end
def execute(runner) context 'when runner_session params are' do
described_class.new(runner).execute.build it 'present sets runner session configuration in the build' do
runner_session_params = { session: { 'url' => 'https://example.com' } }
expect(execute(specific_runner, runner_session_params).runner_session.attributes)
.to include(runner_session_params[:session])
end
it 'not present it does not configure the runner session' do
expect(execute(specific_runner).runner_session).to be_nil
end
end
def execute(runner, params = {})
described_class.new(runner).execute(params).build
end end
end end
end end
...@@ -32,7 +32,7 @@ describe Ci::RetryBuildService do ...@@ -32,7 +32,7 @@ describe Ci::RetryBuildService do
runner_id tag_taggings taggings tags trigger_request_id runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried failure_reason user_id auto_canceled_by_id retried failure_reason
artifacts_file_store artifacts_metadata_store artifacts_file_store artifacts_metadata_store
metadata trace_chunks].freeze metadata runner_session trace_chunks].freeze
shared_examples 'build duplication' do shared_examples 'build duplication' do
let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } let(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
......
...@@ -84,5 +84,23 @@ describe MergeRequests::Conflicts::ListService do ...@@ -84,5 +84,23 @@ describe MergeRequests::Conflicts::ListService do
expect(service.can_be_resolved_in_ui?).to be_falsey expect(service.can_be_resolved_in_ui?).to be_falsey
end end
context 'with gitaly disabled', :skip_gitaly_mock do
it 'returns a falsey value when the MR has a missing ref after a force push' do
merge_request = create_merge_request('conflict-resolvable')
service = conflicts_service(merge_request)
allow_any_instance_of(Rugged::Repository).to receive(:merge_commits).and_raise(Rugged::OdbError)
expect(service.can_be_resolved_in_ui?).to be_falsey
end
it 'returns a falsey value when the MR has a missing revision after a force push' do
merge_request = create_merge_request('conflict-resolvable')
service = conflicts_service(merge_request)
allow(merge_request).to receive_message_chain(:target_branch_head, :raw, :id).and_return(Gitlab::Git::BLANK_SHA)
expect(service.can_be_resolved_in_ui?).to be_falsey
end
end
end end
end end
...@@ -123,6 +123,17 @@ describe MergeRequests::Conflicts::ResolveService do ...@@ -123,6 +123,17 @@ describe MergeRequests::Conflicts::ResolveService do
expect(merge_request_from_fork.source_branch_head.parents.map(&:id)) expect(merge_request_from_fork.source_branch_head.parents.map(&:id))
.to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813', target_head]) .to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813', target_head])
end end
context 'when gitaly is disabled', :skip_gitaly_mock do
it 'gets conflicts from the source project' do
# REFACTOR NOTE: We used to test that `project.repository.rugged` wasn't
# used in this case, but since the refactor, for simplification,
# we always use that repository for read only operations.
expect(forked_project.repository.rugged).to receive(:merge_commits).and_call_original
subject
end
end
end end
end end
......
...@@ -170,6 +170,17 @@ RSpec.configure do |config| ...@@ -170,6 +170,17 @@ RSpec.configure do |config|
redis_queues_cleanup! redis_queues_cleanup!
end end
config.around(:each, :use_clean_rails_memory_store_fragment_caching) do |example|
caching_store = ActionController::Base.cache_store
ActionController::Base.cache_store = ActiveSupport::Cache::MemoryStore.new
ActionController::Base.perform_caching = true
example.run
ActionController::Base.perform_caching = false
ActionController::Base.cache_store = caching_store
end
# The :each scope runs "inside" the example, so this hook ensures the DB is in the # The :each scope runs "inside" the example, so this hook ensures the DB is in the
# correct state before any examples' before hooks are called. This prevents a # correct state before any examples' before hooks are called. This prevents a
# problem where `ScheduleIssuesClosedAtTypeChange` (or any migration that depends # problem where `ScheduleIssuesClosedAtTypeChange` (or any migration that depends
......
...@@ -24,7 +24,9 @@ module WaitForRequests ...@@ -24,7 +24,9 @@ module WaitForRequests
# Wait for client-side AJAX requests # Wait for client-side AJAX requests
def wait_for_requests def wait_for_requests
wait_for('JS requests complete') { finished_all_js_requests? } wait_for('JS requests complete', max_wait_time: 2 * Capybara.default_max_wait_time) do
finished_all_js_requests?
end
end end
# Wait for active Rack requests and client-side AJAX requests # Wait for active Rack requests and client-side AJAX requests
......
...@@ -138,6 +138,28 @@ shared_examples_for 'common trace features' do ...@@ -138,6 +138,28 @@ shared_examples_for 'common trace features' do
end end
end end
describe '#write' do
subject { trace.send(:write, mode) { } }
let(:mode) { 'wb' }
context 'when arhicved trace does not exist yet' do
it 'does not raise an error' do
expect { subject }.not_to raise_error
end
end
context 'when arhicved trace already exists' do
before do
create(:ci_job_artifact, :trace, job: build)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Ci::Trace::AlreadyArchivedError)
end
end
end
describe '#set' do describe '#set' do
before do before do
trace.set("12") trace.set("12")
...@@ -574,7 +596,7 @@ shared_examples_for 'trace with disabled live trace feature' do ...@@ -574,7 +596,7 @@ shared_examples_for 'trace with disabled live trace feature' do
it 'does not archive' do it 'does not archive' do
expect_any_instance_of(described_class).not_to receive(:archive_stream!) expect_any_instance_of(described_class).not_to receive(:archive_stream!)
expect { subject }.to raise_error('Already archived') expect { subject }.to raise_error(Gitlab::Ci::Trace::AlreadyArchivedError)
expect(build.job_artifacts_trace.file.exists?).to be_truthy expect(build.job_artifacts_trace.file.exists?).to be_truthy
end end
end end
...@@ -589,6 +611,55 @@ shared_examples_for 'trace with disabled live trace feature' do ...@@ -589,6 +611,55 @@ shared_examples_for 'trace with disabled live trace feature' do
end end
end end
end end
describe '#erase!' do
subject { trace.erase! }
context 'when it is a live trace' do
context 'when trace is stored in database' do
let(:build) { create(:ci_build) }
before do
build.update_column(:trace, 'sample trace')
end
it { expect(trace.raw).not_to be_nil }
it "removes trace" do
subject
expect(trace.raw).to be_nil
end
end
context 'when trace is stored in file storage' do
let(:build) { create(:ci_build, :trace_live) }
it { expect(trace.raw).not_to be_nil }
it "removes trace" do
subject
expect(trace.raw).to be_nil
end
end
end
context 'when it is an archived trace' do
let(:build) { create(:ci_build, :trace_artifact) }
it "has trace at first" do
expect(trace.raw).not_to be_nil
end
it "removes trace" do
subject
build.reload
expect(trace.raw).to be_nil
end
end
end
end end
shared_examples_for 'trace with enabled live trace feature' do shared_examples_for 'trace with enabled live trace feature' do
...@@ -761,7 +832,7 @@ shared_examples_for 'trace with enabled live trace feature' do ...@@ -761,7 +832,7 @@ shared_examples_for 'trace with enabled live trace feature' do
it 'does not archive' do it 'does not archive' do
expect_any_instance_of(described_class).not_to receive(:archive_stream!) expect_any_instance_of(described_class).not_to receive(:archive_stream!)
expect { subject }.to raise_error('Already archived') expect { subject }.to raise_error(Gitlab::Ci::Trace::AlreadyArchivedError)
expect(build.job_artifacts_trace.file.exists?).to be_truthy expect(build.job_artifacts_trace.file.exists?).to be_truthy
end end
end end
...@@ -776,4 +847,35 @@ shared_examples_for 'trace with enabled live trace feature' do ...@@ -776,4 +847,35 @@ shared_examples_for 'trace with enabled live trace feature' do
end end
end end
end end
describe '#erase!' do
subject { trace.erase! }
context 'when it is a live trace' do
let(:build) { create(:ci_build, :trace_live) }
it { expect(trace.raw).not_to be_nil }
it "removes trace" do
subject
expect(trace.raw).to be_nil
end
end
context 'when it is an archived trace' do
let(:build) { create(:ci_build, :trace_artifact) }
it "has trace at first" do
expect(trace.raw).not_to be_nil
end
it "removes trace" do
subject
build.reload
expect(trace.raw).to be_nil
end
end
end
end end
...@@ -25,24 +25,36 @@ describe Ci::ArchiveTracesCronWorker do ...@@ -25,24 +25,36 @@ describe Ci::ArchiveTracesCronWorker do
end end
end end
context 'when a job was succeeded' do context 'when a job succeeded' do
let!(:build) { create(:ci_build, :success, :trace_live) } let!(:build) { create(:ci_build, :success, :trace_live) }
it_behaves_like 'archives trace' it_behaves_like 'archives trace'
context 'when archive raised an exception' do context 'when a trace had already been archived' do
let!(:build) { create(:ci_build, :success, :trace_artifact, :trace_live) } let!(:build) { create(:ci_build, :success, :trace_live, :trace_artifact) }
let!(:build2) { create(:ci_build, :success, :trace_live) } let!(:build2) { create(:ci_build, :success, :trace_live) }
it 'archives valid targets' do it 'continues to archive live traces' do
expect(Rails.logger).to receive(:error).with("Failed to archive stale live trace. id: #{build.id} message: Already archived")
subject subject
build2.reload build2.reload
expect(build2.job_artifacts_trace).to be_exist expect(build2.job_artifacts_trace).to be_exist
end end
end end
context 'when an unexpected exception happened during archiving' do
let!(:build) { create(:ci_build, :success, :trace_live) }
before do
allow_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!).and_raise('Unexpected error')
end
it 'puts a log' do
expect(Rails.logger).to receive(:error).with("Failed to archive stale live trace. id: #{build.id} message: Unexpected error")
subject
end
end
end end
context 'when a job was cancelled' do context 'when a job was cancelled' do
......
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