Commit 0e8e7558 authored by Amit Rathi's avatar Amit Rathi

Merge branch 'master' into certmanager-temp

parents 206f6747 b45a3033
......@@ -318,6 +318,7 @@ review-docs-cleanup:
cloud-native-image:
image: ruby:2.4-alpine
before_script: []
dependencies: []
stage: test
allow_failure: true
variables:
......@@ -632,6 +633,7 @@ rails5_gemfile_lock_check:
ee_compat_check:
<<: *rake-exec
dependencies: []
except:
- master
- tags
......@@ -860,9 +862,7 @@ coverage:
lint:javascript:report:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
stage: post-test
dependencies:
- compile-assets
- setup-test-env
dependencies: []
before_script: []
script:
- date
......@@ -916,6 +916,7 @@ gitlab_git_test:
variables:
SETUP_DB: "false"
before_script: []
dependencies: []
cache: {}
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
......@@ -926,6 +927,7 @@ no_ee_check:
variables:
SETUP_DB: "false"
before_script: []
dependencies: []
cache: {}
script:
- scripts/no-ee-check
......
......@@ -124,7 +124,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.8'
gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.6.4'
gem 'gitlab-markup', '~> 1.6.5'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'redcarpet', '~> 3.4'
gem 'commonmarker', '~> 0.17'
......@@ -204,6 +204,9 @@ gem 'redis-rails', '~> 5.0.2'
gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0'
# Discord integration
gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
# HipChat integration
gem 'hipchat', '~> 1.5.0'
......@@ -339,7 +342,7 @@ group :development, :test do
gem 'minitest', '~> 5.7.0'
# Generate Fake data
gem 'ffaker', '~> 2.4'
gem 'ffaker', '~> 2.10'
gem 'capybara', '~> 2.15'
gem 'capybara-screenshot', '~> 1.0.0'
......@@ -354,7 +357,7 @@ group :development, :test do
gem 'rubocop-rspec', '~> 1.22.1'
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
gem 'haml_lint', '~> 0.28.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
......
......@@ -162,6 +162,8 @@ GEM
rotp (~> 2.0)
diff-lcs (1.3)
diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
docile (1.1.5)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
......@@ -200,7 +202,7 @@ GEM
multi_json
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.4.0)
ffaker (2.10.0)
ffi (1.9.25)
flipper (0.13.0)
flipper-active_record (0.13.0)
......@@ -272,7 +274,7 @@ GEM
gitaly-proto (0.123.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-markup (1.6.4)
gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1)
......@@ -335,11 +337,11 @@ GEM
haml (5.0.4)
temple (>= 0.8.0)
tilt
haml_lint (0.26.0)
haml_lint (0.28.0)
haml (>= 4.0, < 5.1)
rainbow
rake (>= 10, < 13)
rubocop (>= 0.49.0)
rubocop (>= 0.50.0)
sysexits (~> 1.1)
hamlit (2.8.8)
temple (>= 0.8.0)
......@@ -449,9 +451,9 @@ GEM
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.0)
mime-types (3.1)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mime-types-data (3.2018.0812)
mimemagic (0.3.0)
mini_magick (4.8.0)
mini_mime (1.0.1)
......@@ -596,7 +598,7 @@ GEM
get_process_mem (~> 0.2)
puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3)
rack (1.6.10)
rack (1.6.11)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
......@@ -608,7 +610,7 @@ GEM
httpclient (>= 2.4)
multi_json (>= 1.3.6)
rack (>= 1.1)
rack-protection (2.0.3)
rack-protection (2.0.4)
rack
rack-proxy (0.6.0)
rack
......@@ -676,7 +678,7 @@ GEM
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis-store (1.6.0)
redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4)
......@@ -802,7 +804,7 @@ GEM
rack
shoulda-matchers (3.1.2)
activesupport (>= 4.0.0)
sidekiq (5.2.1)
sidekiq (5.2.3)
connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
......@@ -965,6 +967,7 @@ DEPENDENCIES
devise (~> 4.4)
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
discordrb-webhooks-blackst0ne (~> 3.3)
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.5)
ed25519 (~> 1.2)
......@@ -974,7 +977,7 @@ DEPENDENCIES
factory_bot_rails (~> 4.8.2)
faraday (~> 0.12)
fast_blank
ffaker (~> 2.4)
ffaker (~> 2.10)
flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
......@@ -995,7 +998,7 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0)
gitlab-markup (~> 1.6.4)
gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
......@@ -1010,7 +1013,7 @@ DEPENDENCIES
graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0)
grpc (~> 1.15.0)
haml_lint (~> 0.26.0)
haml_lint (~> 0.28.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
......
......@@ -165,6 +165,8 @@ GEM
rotp (~> 2.0)
diff-lcs (1.3)
diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
docile (1.1.5)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
......@@ -203,7 +205,7 @@ GEM
multi_json
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.4.0)
ffaker (2.10.0)
ffi (1.9.25)
flipper (0.13.0)
flipper-active_record (0.13.0)
......@@ -275,7 +277,7 @@ GEM
gitaly-proto (0.123.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-markup (1.6.4)
gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1)
......@@ -338,11 +340,11 @@ GEM
haml (5.0.4)
temple (>= 0.8.0)
tilt
haml_lint (0.26.0)
haml_lint (0.28.0)
haml (>= 4.0, < 5.1)
rainbow
rake (>= 10, < 13)
rubocop (>= 0.49.0)
rubocop (>= 0.50.0)
sysexits (~> 1.1)
hamlit (2.8.8)
temple (>= 0.8.0)
......@@ -544,7 +546,7 @@ GEM
orm_adapter (0.5.0)
os (1.0.0)
parallel (1.12.1)
parser (2.5.1.2)
parser (2.5.3.0)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.0.1)
......@@ -612,7 +614,7 @@ GEM
httpclient (>= 2.4)
multi_json (>= 1.3.6)
rack (>= 1.1)
rack-protection (2.0.3)
rack-protection (2.0.4)
rack
rack-proxy (0.6.0)
rack
......@@ -685,7 +687,7 @@ GEM
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis-store (1.6.0)
redis (>= 2.2, < 5)
regexp_parser (0.5.0)
representable (3.0.4)
......@@ -810,7 +812,7 @@ GEM
rack
shoulda-matchers (3.1.2)
activesupport (>= 4.0.0)
sidekiq (5.2.1)
sidekiq (5.2.3)
connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
......@@ -974,6 +976,7 @@ DEPENDENCIES
devise (~> 4.4)
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
discordrb-webhooks-blackst0ne (~> 3.3)
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.5)
ed25519 (~> 1.2)
......@@ -983,7 +986,7 @@ DEPENDENCIES
factory_bot_rails (~> 4.8.2)
faraday (~> 0.12)
fast_blank
ffaker (~> 2.4)
ffaker (~> 2.10)
flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
......@@ -1004,7 +1007,7 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0)
gitlab-markup (~> 1.6.4)
gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
......@@ -1019,7 +1022,7 @@ DEPENDENCIES
graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0)
grpc (~> 1.15.0)
haml_lint (~> 0.26.0)
haml_lint (~> 0.28.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
......
......@@ -128,6 +128,7 @@ export default {
eventHub.$once('fetchedNotesData', this.setDiscussions);
},
methods: {
...mapActions(['startTaskList']),
...mapActions('diffs', [
'setBaseConfig',
'fetchDiffFiles',
......@@ -157,7 +158,13 @@ export default {
if (this.isNotesFetched && !this.assignedDiscussions && !this.isLoading) {
this.assignedDiscussions = true;
requestIdleCallback(() => this.assignDiscussionsToDiff(), { timeout: 1000 });
requestIdleCallback(
() =>
this.assignDiscussionsToDiff()
.then(this.$nextTick)
.then(this.startTaskList),
{ timeout: 1000 },
);
}
},
adjustView() {
......
......@@ -35,7 +35,7 @@ export default {
if (search === '') return this.renderTreeList ? this.tree : this.allBlobs;
return this.allBlobs.filter(f => f.name.toLowerCase().indexOf(search) >= 0);
return this.allBlobs.filter(f => f.path.toLowerCase().indexOf(search) >= 0);
},
rowDisplayTextKey() {
if (this.renderTreeList && this.search.trim() === '') {
......
......@@ -190,6 +190,7 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => {
.then(result => dispatch('updateDiscussion', result.discussion, { root: true }))
.then(discussion => dispatch('assignDiscussionsToDiff', [discussion]))
.then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.fileHash))
.then(() => dispatch('startTaskList', null, { root: true }))
.catch(() => createFlash(s__('MergeRequests|Saving the comment failed')));
};
......
......@@ -65,7 +65,13 @@ export default {
const { highlightedDiffLines, parallelDiffLines } = diffFile;
removeMatchLine(diffFile, lineNumbers, bottom);
const lines = addLineReferences(contextLines, lineNumbers, bottom);
const lines = addLineReferences(contextLines, lineNumbers, bottom).map(line => ({
...line,
lineCode: line.lineCode || `${fileHash}_${line.oldLine}_${line.newLine}`,
discussions: line.discussions || [],
}));
addContextLines({
inlineLines: highlightedDiffLines,
parallelLines: parallelDiffLines,
......
......@@ -80,8 +80,8 @@ export const fetchJob = ({ state, dispatch }) => {
export const receiveJobSuccess = ({ commit }, data = {}) => {
commit(types.RECEIVE_JOB_SUCCESS, data);
if (data.favicon) {
setFaviconOverlay(data.favicon);
if (data.status && data.status.favicon) {
setFaviconOverlay(data.status.favicon);
} else {
resetFavicon();
}
......
......@@ -6,7 +6,6 @@ import Autosize from 'autosize';
import { __, sprintf } from '~/locale';
import Flash from '../../flash';
import Autosave from '../../autosave';
import TaskList from '../../task_list';
import {
capitalizeFirstCharacter,
convertToCamelCase,
......@@ -146,7 +145,6 @@ export default {
});
this.initAutoSave();
this.initTaskList();
},
methods: {
...mapActions([
......@@ -298,13 +296,6 @@ Please check your network connection and try again.`;
]);
}
},
initTaskList() {
return new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
},
resizeTextarea() {
this.$nextTick(() => {
Autosize.update(this.$refs.textarea);
......
......@@ -4,7 +4,6 @@ import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue';
import noteForm from './note_form.vue';
import TaskList from '../../task_list';
import autosave from '../mixins/autosave';
export default {
......@@ -37,14 +36,12 @@ export default {
},
mounted() {
this.renderGFM();
this.initTaskList();
if (this.isEditing) {
this.initAutoSave(this.note);
}
},
updated() {
this.initTaskList();
this.renderGFM();
if (this.isEditing) {
......@@ -59,15 +56,6 @@ export default {
renderGFM() {
$(this.$refs['note-body']).renderGFM();
},
initTaskList() {
if (this.canEdit) {
this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
}
},
handleFormUpdate(note, parentElement, callback) {
this.$emit('handleFormUpdate', note, parentElement, callback);
},
......
......@@ -46,6 +46,7 @@ export default {
'is-requesting being-posted': this.isRequesting,
'disabled-content': this.isDeleting,
target: this.isTarget,
'is-editable': this.note.current_user.can_edit,
};
},
canResolve() {
......
......@@ -122,6 +122,7 @@ export default {
setTargetNoteHash: 'setTargetNoteHash',
toggleDiscussion: 'toggleDiscussion',
setNotesFetchedState: 'setNotesFetchedState',
startTaskList: 'startTaskList',
}),
getComponentName(discussion) {
if (discussion.isSkeletonNote) {
......@@ -157,6 +158,7 @@ export default {
this.isFetching = false;
})
.then(() => this.$nextTick())
.then(() => this.startTaskList())
.then(() => this.checkLocationHash())
.catch(() => {
this.setLoadingState(false);
......
import Vue from 'vue';
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import Visibility from 'visibilityjs';
import TaskList from '../../task_list';
import Flash from '../../flash';
import Poll from '../../lib/utils/poll';
import * as types from './mutation_types';
......@@ -58,12 +60,13 @@ export const deleteNote = ({ commit, dispatch }, note) =>
dispatch('updateMergeRequestWidget');
});
export const updateNote = ({ commit }, { endpoint, note }) =>
export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
service
.updateNote(endpoint, note)
.then(res => res.json())
.then(res => {
commit(types.UPDATE_NOTE, res);
dispatch('startTaskList');
});
export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
......@@ -85,6 +88,7 @@ export const createNewNote = ({ commit, dispatch }, { endpoint, data }) =>
commit(types.ADD_NEW_NOTE, res);
dispatch('updateMergeRequestWidget');
dispatch('startTaskList');
}
return res;
});
......@@ -260,6 +264,8 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
commit(types.ADD_NEW_NOTE, note);
}
});
dispatch('startTaskList');
}
commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
......@@ -368,5 +374,16 @@ export const setCommentsDisabled = ({ commit }, data) => {
commit(types.DISABLE_COMMENTS, data);
};
export const startTaskList = ({ dispatch }) =>
Vue.nextTick(
() =>
new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes .is-editable',
onSuccess: () => dispatch('startTaskList'),
}),
);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -40,10 +40,8 @@ export default {
failed: __('Failed to deploy to'),
},
data() {
const features = window.gon.features || {};
return {
isStopping: false,
enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges,
};
},
computed: {
......@@ -74,10 +72,7 @@ export default {
: '';
},
shouldRenderDropdown() {
return (
this.enableCiEnvironmentsStatusChanges &&
(this.deployment.changes && this.deployment.changes.length > 0)
);
return this.deployment.changes && this.deployment.changes.length > 0;
},
},
methods: {
......
$system-note-icon-size: 32px;
$system-note-svg-size: 16px;
$note-form-margin-left: 70px;
$note-form-margin-left: 72px;
@mixin vertical-line($left) {
&::before {
......@@ -54,7 +54,7 @@ $note-form-margin-left: 70px;
}
.main-notes-list {
@include vertical-line(39px);
@include vertical-line(36px);
}
.notes {
......@@ -268,7 +268,7 @@ $note-form-margin-left: 70px;
}
.system-note {
padding: 6px $gl-padding-24;
padding: 6px 20px;
margin: $gl-padding-24 0;
background-color: transparent;
......
......@@ -122,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController
@lines.map! do |line|
# These are marked as context lines but are loaded from blobs.
# We also have context lines loaded from diffs in other places.
diff_line = Gitlab::Diff::Line.new(line, 'context', nil, nil, nil)
diff_line = Gitlab::Diff::Line.new(line, expanded_diff_line_type, nil, nil, nil)
diff_line.rich_text = line
diff_line
end
......@@ -132,6 +132,11 @@ class Projects::BlobController < Projects::ApplicationController
render json: DiffLineSerializer.new.represent(@lines)
end
def expanded_diff_line_type
# Context lines can't receive comments.
Feature.enabled?(:comment_in_any_diff_line, @project) ? nil : 'context'
end
def add_match_line
return unless @form.unfold?
......
......@@ -22,6 +22,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def render_diffs
@environment = @merge_request.environments_for(current_user).last
notes_grouped_by_path = renderable_notes.group_by { |note| note.position.file_path }
@diffs.diff_files.each do |diff_file|
notes = notes_grouped_by_path.fetch(diff_file.file_path, [])
notes.each { |note| diff_file.unfold_diff_lines(note.position) }
end
@diffs.write_cache
......@@ -108,4 +114,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
@notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes), @merge_request)
end
def renderable_notes
define_diff_comment_vars unless @notes
@notes
end
end
......@@ -14,9 +14,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
before_action do
push_frontend_feature_flag(:ci_environments_status_changes)
end
def index
@merge_requests = @issuables
......
......@@ -83,7 +83,7 @@ module ImportHelper
private
def github_project_url(full_path)
URI.join(github_root_url, full_path).to_s
Gitlab::Utils.append_path(github_root_url, full_path)
end
def github_root_url
......@@ -95,6 +95,6 @@ module ImportHelper
end
def gitea_project_url(full_path)
URI.join(@gitea_host_url, full_path).to_s
Gitlab::Utils.append_path(@gitea_host_url, full_path)
end
end
......@@ -22,6 +22,8 @@ module Clusters
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) }
def token_name
"#{namespace}-token"
end
......
......@@ -83,7 +83,7 @@ module Clusters
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end
if kubernetes_namespace = cluster.kubernetes_namespaces.find_by(project: project)
if kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project)
variables.concat(kubernetes_namespace.predefined_variables)
else
# From 11.5, every Clusters::Project should have at least one
......
......@@ -13,17 +13,14 @@ module Deployable
name: expanded_environment_name
)
environment.deployments.create!(
create_deployment!(
project_id: environment.project_id,
environment: environment,
ref: ref,
tag: tag,
sha: sha,
user: user,
deployable: self,
on_stop: on_stop).tap do |_|
self.reload # Reload relationships
end
on_stop: on_stop)
end
end
end
......@@ -66,6 +66,10 @@ class DiffNote < Note
self.original_position.diff_refs == diff_refs
end
def discussion_first_note?
self == discussion.first_note
end
private
def enqueue_diff_file_creation_job
......@@ -78,10 +82,11 @@ class DiffNote < Note
end
def should_create_diff_file?
on_text? && note_diff_file.nil? && self == discussion.first_note
on_text? && note_diff_file.nil? && discussion_first_note?
end
def fetch_diff_file
file =
if note_diff_file
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
Gitlab::Diff::File.new(diff,
......@@ -98,6 +103,12 @@ class DiffNote < Note
else
original_position.diff_file(self.project.repository)
end
# Since persisted diff files already have its content "unfolded"
# there's no need to make it pass through the unfolding process.
file&.unfold_diff_lines(position) unless note_diff_file
file
end
def supported?
......
......@@ -142,7 +142,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_by_shas(shas)
return [] unless shas.present?
return MergeRequestDiffCommit.none unless shas.present?
merge_request_diff_commits.where(sha: shas)
end
......
......@@ -135,6 +135,7 @@ class Project < ActiveRecord::Base
# Project services
has_one :campfire_service
has_one :discord_service
has_one :drone_ci_service
has_one :emails_on_push_service
has_one :pipelines_email_service
......
......@@ -86,14 +86,16 @@ class BambooService < CiService
end
def read_build_page(response)
key =
if response.code != 200 || response.dig('results', 'results', 'size') == '0'
# If actual build link can't be determined, send user to build summary page.
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
build_key
else
# If actual build link is available, go to build result page.
result_key = response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key')
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key')
end
build_url("browse/#{key}")
end
def read_commit_status(response)
......@@ -117,7 +119,7 @@ class BambooService < CiService
end
def build_url(path)
URI.join("#{bamboo_url}/", path).to_s
Gitlab::Utils.append_path(bamboo_url, path)
end
def get_path(path, query_params = {})
......
# frozen_string_literal: true
require "discordrb/webhooks"
class DiscordService < ChatNotificationService
def title
"Discord Notifications"
end
def description
"Receive event notifications in Discord"
end
def self.to_param
"discord"
end
def help
"This service sends notifications about project events to Discord channels.<br />
To set up this service:
<ol>
<li><a href='https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks'>Setup a custom Incoming Webhook</a>.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>"
end
def event_field(event)
# No-op.
end
def default_channel_placeholder
# No-op.
end
def default_fields
[
{ type: "text", name: "webhook", placeholder: "e.g. https://discordapp.com/api/webhooks/…" },
{ type: "checkbox", name: "notify_only_broken_pipelines" },
{ type: "checkbox", name: "notify_only_default_branch" }
]
end
private
def notify(message, opts)
client = Discordrb::Webhooks::Client.new(url: webhook)
client.execute do |builder|
builder.content = message.pretext
end
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
......@@ -39,11 +39,9 @@ class DroneCiService < CiService
end
def commit_status_path(sha, ref)
url = [drone_url,
"gitlab/#{project.full_path}/commits/#{sha}",
"?branch=#{URI.encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
Gitlab::Utils.append_path(
drone_url,
"gitlab/#{project.full_path}/commits/#{sha}?branch=#{URI.encode(ref.to_s)}&access_token=#{token}")
end
def commit_status(sha, ref)
......@@ -74,11 +72,9 @@ class DroneCiService < CiService
end
def build_page(sha, ref)
url = [drone_url,
"gitlab/#{project.full_path}/redirect/commits/#{sha}",
"?branch=#{URI.encode(ref.to_s)}"]
URI.join(*url).to_s
Gitlab::Utils.append_path(
drone_url,
"gitlab/#{project.full_path}/redirect/commits/#{sha}?branch=#{URI.encode(ref.to_s)}")
end
def title
......
......@@ -54,7 +54,7 @@ class JiraService < IssueTrackerService
{
username: self.username,
password: self.password,
site: URI.join(url, '/').to_s,
site: URI.join(url, '/').to_s, # Intended to find the root
context_path: url.path.chomp('/'),
auth_type: :basic,
read_timeout: 120,
......
......@@ -34,10 +34,9 @@ class MockCiService < CiService
# http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
#
def build_page(sha, ref)
url = [mock_service_url,
"#{project.namespace.path}/#{project.path}/status/#{sha}"]
URI.join(*url).to_s
Gitlab::Utils.append_path(
mock_service_url,
"#{project.namespace.path}/#{project.path}/status/#{sha}")
end
# Return string with build status or :error symbol
......@@ -61,10 +60,9 @@ class MockCiService < CiService
end
def commit_status_path(sha)
url = [mock_service_url,
"#{project.namespace.path}/#{project.path}/status/#{sha}.json"]
URI.join(*url).to_s
Gitlab::Utils.append_path(
mock_service_url,
"#{project.namespace.path}/#{project.path}/status/#{sha}.json")
end
def read_commit_status(response)
......
......@@ -132,7 +132,7 @@ class TeamcityService < CiService
end
def build_url(path)
URI.join("#{teamcity_url}/", path).to_s
Gitlab::Utils.append_path(teamcity_url, path)
end
def get_path(path)
......
......@@ -253,6 +253,7 @@ class Service < ActiveRecord::Base
bugzilla
campfire
custom_issue_tracker
discord
drone_ci
emails_on_push
external_wiki
......
......@@ -37,7 +37,7 @@ class EnvironmentStatusEntity < Grape::Entity
es.deployment.try(:formatted_deployment_time)
end
expose :changes, if: ->(*) { Feature.enabled?(:ci_environments_status_changes, project) }
expose :changes
private
......
# frozen_string_literal: true
module Issuable
module Clone
class AttributesRewriter < ::Issuable::Clone::BaseService
def initialize(current_user, original_entity, new_entity)
@current_user = current_user
@original_entity = original_entity
@new_entity = new_entity
end
def execute
new_entity.update(milestone: cloneable_milestone, labels: cloneable_labels)
copy_resource_label_events
end
private
def cloneable_milestone
title = original_entity.milestone&.title
return unless title
params = { title: title, project_ids: new_entity.project&.id, group_ids: group&.id }
milestones = MilestonesFinder.new(params).execute
milestones.first
end
def cloneable_labels
params = {
project_id: new_entity.project&.id,
group_id: group&.id,
title: original_entity.labels.select(:title),
include_ancestor_groups: true
}
params[:only_group_labels] = true if new_parent.is_a?(Group)
LabelsFinder.new(current_user, params).execute
end
def copy_resource_label_events
original_entity.resource_label_events.find_in_batches do |batch|
events = batch.map do |event|
entity_key = new_entity.is_a?(Issue) ? 'issue_id' : 'epic_id'
# rubocop: disable CodeReuse/ActiveRecord
event.attributes
.except('id', 'reference', 'reference_html')
.merge(entity_key => new_entity.id, 'action' => ResourceLabelEvent.actions[event.action])
# rubocop: enable CodeReuse/ActiveRecord
end
Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, events)
end
end
def entity_key
new_entity.class.name.parameterize('_').foreign_key
end
end
end
end
# frozen_string_literal: true
module Issuable
module Clone
class BaseService < IssuableBaseService
attr_reader :original_entity, :new_entity
alias_method :old_project, :project
def execute(original_entity, new_project = nil)
@original_entity = original_entity
# Using transaction because of a high resources footprint
# on rewriting notes (unfolding references)
#
ActiveRecord::Base.transaction do
@new_entity = create_new_entity
update_new_entity
update_old_entity
create_notes
end
end
private
def update_new_entity
rewriters = [ContentRewriter, AttributesRewriter]
rewriters.each do |rewriter|
rewriter.new(current_user, original_entity, new_entity).execute
end
end
def update_old_entity
close_issue
end
def create_notes
add_note_from
add_note_to
end
def close_issue
close_service = Issues::CloseService.new(old_project, current_user)
close_service.execute(original_entity, notifications: false, system_note: false)
end
def new_parent
new_entity.project ? new_entity.project : new_entity.group
end
def group
if new_entity.project&.group && current_user.can?(:read_group, new_entity.project.group)
new_entity.project.group
end
end
end
end
end
# frozen_string_literal: true
module Issuable
module Clone
class ContentRewriter < ::Issuable::Clone::BaseService
def initialize(current_user, original_entity, new_entity)
@current_user = current_user
@original_entity = original_entity
@new_entity = new_entity
@project = original_entity.project
end
def execute
rewrite_description
rewrite_award_emoji(original_entity, new_entity)
rewrite_notes
end
private
def rewrite_description
new_entity.update(description: rewrite_content(original_entity.description))
end
def rewrite_notes
original_entity.notes_with_associations.find_each do |note|
new_note = note.dup
new_params = {
project: new_entity.project, noteable: new_entity,
note: rewrite_content(new_note.note),
created_at: note.created_at,
updated_at: note.updated_at
}
if note.system_note_metadata
new_params[:system_note_metadata] = note.system_note_metadata.dup
end
new_note.update(new_params)
rewrite_award_emoji(note, new_note)
end
end
def rewrite_content(content)
return unless content
rewriters = [Gitlab::Gfm::ReferenceRewriter, Gitlab::Gfm::UploadsRewriter]
rewriters.inject(content) do |text, klass|
rewriter = klass.new(text, old_project, current_user)
rewriter.rewrite(new_parent)
end
end
def rewrite_award_emoji(old_awardable, new_awardable)
old_awardable.award_emoji.each do |award|
new_award = award.dup
new_award.awardable = new_awardable
new_award.save
end
end
end
end
end
# frozen_string_literal: true
module Issues
class MoveService < Issues::BaseService
class MoveService < Issuable::Clone::BaseService
MoveError = Class.new(StandardError)
def execute(issue, new_project)
@old_issue = issue
@old_project = @project
@new_project = new_project
def execute(issue, target_project)
@target_project = target_project
unless issue.can_move?(current_user, new_project)
unless issue.can_move?(current_user, @target_project)
raise MoveError, 'Cannot move issue due to insufficient permissions!'
end
if @project == new_project
if @project == @target_project
raise MoveError, 'Cannot move issue to project it originates from!'
end
# Using transaction because of a high resources footprint
# on rewriting notes (unfolding references)
#
ActiveRecord::Base.transaction do
@new_issue = create_new_issue
update_new_issue
update_old_issue
end
super
notify_participants
@new_issue
new_entity
end
private
def update_new_issue
rewrite_notes
copy_resource_label_events
rewrite_issue_award_emoji
add_note_moved_from
end
def update_old_entity
super
def update_old_issue
add_note_moved_to
close_issue
mark_as_moved
end
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids,
milestone_id: cloneable_milestone_id,
project: @new_project, author: @old_issue.author,
description: rewrite_content(@old_issue.description),
assignee_ids: @old_issue.assignee_ids }
new_params = @old_issue.serializable_hash.symbolize_keys.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
end
# rubocop: disable CodeReuse/ActiveRecord
def cloneable_label_ids
params = {
project_id: @new_project.id,
title: @old_issue.labels.pluck(:title),
include_ancestor_groups: true
def create_new_entity
new_params = {
id: nil,
iid: nil,
project: @target_project,
author: original_entity.author,
assignee_ids: original_entity.assignee_ids
}
LabelsFinder.new(current_user, params).execute.pluck(:id)
end
# rubocop: enable CodeReuse/ActiveRecord
def cloneable_milestone_id
title = @old_issue.milestone&.title
return unless title
if @new_project.group && can?(current_user, :read_group, @new_project.group)
group_id = @new_project.group.id
end
params =
{ title: title, project_ids: @new_project.id, group_ids: group_id }
milestones = MilestonesFinder.new(params).execute
milestones.first&.id
end
def rewrite_notes
@old_issue.notes_with_associations.find_each do |note|
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
note: rewrite_content(new_note.note),
created_at: note.created_at,
updated_at: note.updated_at }
new_note.update(new_params)
rewrite_award_emoji(note, new_note)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def copy_resource_label_events
@old_issue.resource_label_events.find_in_batches do |batch|
events = batch.map do |event|
event.attributes
.except('id', 'reference', 'reference_html')
.merge('issue_id' => @new_issue.id, 'action' => ResourceLabelEvent.actions[event.action])
new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
CreateService.new(@target_project, @current_user, new_params).execute
end
Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, events)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def rewrite_issue_award_emoji
rewrite_award_emoji(@old_issue, @new_issue)
end
def rewrite_award_emoji(old_awardable, new_awardable)
old_awardable.award_emoji.each do |award|
new_award = award.dup
new_award.awardable = new_awardable
new_award.save
end
end
def rewrite_content(content)
return unless content
rewriters = [Gitlab::Gfm::ReferenceRewriter,
Gitlab::Gfm::UploadsRewriter]
rewriters.inject(content) do |text, klass|
rewriter = klass.new(text, @old_project, @current_user)
rewriter.rewrite(@new_project)
end
def mark_as_moved
original_entity.update(moved_to: new_entity)
end
def close_issue
close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false)
def notify_participants
notification_service.async.issue_moved(original_entity, new_entity, @current_user)
end
def add_note_moved_from
SystemNoteService.noteable_moved(@new_issue, @new_project,
@old_issue, @current_user,
def add_note_from
SystemNoteService.noteable_moved(new_entity, @target_project,
original_entity, current_user,
direction: :from)
end
def add_note_moved_to
SystemNoteService.noteable_moved(@old_issue, @old_project,
@new_issue, @current_user,
def add_note_to
SystemNoteService.noteable_moved(original_entity, old_project,
new_entity, current_user,
direction: :to)
end
def mark_as_moved
@old_issue.update(moved_to: @new_issue)
end
def notify_participants
notification_service.async.issue_moved(@old_issue, @new_issue, @current_user)
end
end
end
......@@ -29,10 +29,6 @@ module MergeRequests
# rubocop: disable CodeReuse/ActiveRecord
def clear_cache(new_diff)
# Executing the iteration we cache highlighted diffs for each diff file of
# MergeRequestDiff.
cacheable_collection(new_diff).write_cache
# Remove cache for all diffs on this MR. Do not use the association on the
# model, as that will interfere with other actions happening when
# reloading the diff.
......
# frozen_string_literal: true
module Notes
class BaseService < ::BaseService
def clear_noteable_diffs_cache(note)
if note.is_a?(DiffNote) &&
note.discussion_first_note? &&
note.position.unfolded_diff?(project.repository)
note.noteable.diffs.clear_cache
end
end
end
end
# frozen_string_literal: true
module Notes
class CreateService < ::BaseService
class CreateService < ::Notes::BaseService
def execute
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
......@@ -35,6 +35,7 @@ module Notes
if !only_commands && note.save
todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
end
if command_params.present?
......
# frozen_string_literal: true
module Notes
class DestroyService < BaseService
class DestroyService < ::Notes::BaseService
def execute(note)
TodoService.new.destroy_target(note) do |note|
note.destroy
end
clear_noteable_diffs_cache(note)
end
end
end
......@@ -149,9 +149,9 @@ class FileUploader < GitlabUploader
# return a new uploader with a file copy on another project
def self.copy_to(uploader, to_project)
moved = uploader.dup.tap do |u|
u.model = to_project
end
moved = self.new(to_project)
moved.object_store = uploader.object_store
moved.filename = uploader.filename
moved.copy_file(uploader.file)
moved
......
......@@ -34,10 +34,13 @@
.clearfix
%p
%i.fa.fa-exclamation-circle
If '[#{@concurrency} of #{@concurrency} busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
If '[#{@concurrency} of #{@concurrency} busy]' is shown, restart GitLab.
= link_to sprite_icon('question', size: 16), help_page_path('administration/restart_gitlab')
%p
%i.fa.fa-exclamation-circle
If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
= link_to sprite_icon('question', size: 16), help_page_path('administration/restart_gitlab')
......
......@@ -165,7 +165,7 @@
.input-group
.input-group-prepend
.input-group-text
#{URI.join(root_url, @project.namespace.full_path)}/
#{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
= f.text_field :path, class: 'form-control'
%ul
%li Be careful. Renaming a project's repository can have unintended side effects.
......
---
title: Update haml_lint to 0.28.0
merge_request: 22660
author: Takuya Noguchi
type: other
---
title: Update ffaker to 2.10.0
merge_request: 22661
author: Takuya Noguchi
type: other
---
title: Fix deployment jobs using nil KUBE_TOKEN due to migration issue
merge_request: 23009
author:
type: fixed
---
title: Adds CI favicon back to jobs page
merge_request:
author:
type: fixed
---
title: Relocate JSONWebToken::HMACToken from EE
merge_request: 22906
author:
type: changed
---
title: Add Discord integration
merge_request: 22684
author: "@blackst0ne"
type: added
---
title: Improve initial discussion rendering performance
merge_request: 22607
author:
type: changed
---
title: Do not reload self on hooks when creating deployment
merge_request:
author:
type: fixed
---
title: Changed merge request filtering to be by path instead of name
merge_request:
author:
type: changed
---
title: Allow commenting on any diff line in Merge Requests
merge_request: 22914
author:
type: added
---
title: Make sure there's only one slash as path separator
merge_request: 22954
author:
type: other
---
title: Remove obsolete gitlab_shell rake tasks
merge_request: 22417
author:
type: removed
......@@ -24,9 +24,32 @@ The following files require a review from the Documentation team:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
To make sure these changes are reviewed, mention `@gl-docsteam` in a separate
comment, and explain what needs to be reviewed by the team. Please don't mention
the team until your changes are ready for review.
When your content is ready for review, mention a technical writer in a separate
comment and explain what needs to be reviewed.
You are welcome to mention them sooner if you have questions about writing or updating
the documentation. GitLabbers are also welcome to use the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.
Who to ping [based on DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages):
| Stage | Tech writer |
| ------------- | ----------- |
| ~Create | `@marcia` |
| ~Configure | `@eread` |
| ~Distribution | `@axil` |
| ~Geo | `@eread` |
| ~Gitaly | `@axil` |
| ~Gitter | `@axil` |
| ~Manage | `@eread` |
| ~Monitoring | `@axil` |
| ~Packaging | `@axil` |
| ~Plan | `@mikelewis`|
| ~Release | `marcia` |
| ~Secure | `@axil` |
| ~Verify | `@eread` |
If you are not sure which category the change falls within, or the change is not
part of one of these categories, you can mention the whole team with `@gl-docsteam`.
MARKDOWN
unless gitlab.mr_labels.include?('Documentation')
......
......@@ -4,9 +4,7 @@ description: "Set and configure Git protocol v2"
# Configuring Git Protocol v2
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/46555) in GitLab 11.4.
---
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46555) in GitLab 11.4.
Git protocol v2 improves the v1 wire protocol in several ways and is
enabled by default in GitLab for HTTP requests. In order to enable SSH,
......
......@@ -10,7 +10,7 @@ Asana - Teamwork without email
Set Asana service for a project.
> This service adds commit messages as comments to Asana tasks. Once enabled, commit messages are checked for Asana task URLs (for example, `https://app.asana.com/0/123456/987654`) or task IDs starting with # (for example, `#987654`). Every task ID found will get the commit comment added to it. You can also close a task with a message containing: `fix #123456`. You can find your Api Keys here: https://asana.com/developers/documentation/getting-started/auth#api-key
> This service adds commit messages as comments to Asana tasks. Once enabled, commit messages are checked for Asana task URLs (for example, `https://app.asana.com/0/123456/987654`) or task IDs starting with # (for example, `#987654`). Every task ID found will get the commit comment added to it. You can also close a task with a message containing: `fix #123456`. You can find your Api Keys here: <https://asana.com/developers/documentation/getting-started/auth#api-key>.
```
PUT /projects/:id/services/asana
......@@ -92,7 +92,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `bamboo_url` | string | true | Bamboo root URL like https://bamboo.example.com |
| `bamboo_url` | string | true | Bamboo root URL. For example, `https://bamboo.example.com`. |
| `build_key` | string | true | Bamboo build plan key like KEY |
| `username` | string | true | A user with API access, if applicable |
| `password` | string | true | Password of the user |
......@@ -117,7 +117,7 @@ GET /projects/:id/services/bamboo
Bugzilla Issue Tracker
### Create/Edit Buildkite service
### Create/Edit Bugzilla service
Set Bugzilla service for a project.
......@@ -168,7 +168,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `token` | string | true | Buildkite project GitLab token |
| `project_url` | string | true | https://buildkite.com/example/project |
| `project_url` | string | true | `https://buildkite.com/example/project` |
| `enable_ssl_verification` | boolean | false | Enable SSL verification |
### Delete Buildkite service
......@@ -278,7 +278,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `token` | string | true | Drone CI project specific token |
| `drone_url` | string | true | http://drone.example.com |
| `drone_url` | string | true | `http://drone.example.com` |
| `enable_ssl_verification` | boolean | false | Enable SSL verification |
### Delete Drone CI service
......@@ -421,7 +421,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `webhook` | string | true | The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces... |
| `webhook` | string | true | The Hangouts Chat webhook. For example, `https://chat.googleapis.com/v1/spaces...`. |
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
| `notify_only_default_branch` | boolean | false | Send notifications only for the default branch |
| `push_events` | boolean | false | Enable notifications for push events |
......@@ -470,7 +470,7 @@ Parameters:
| `notify` | boolean | false | Enable notifications |
| `room` | string | false |Room name or ID |
| `api_version` | string | false | Leave blank for default (v2) |
| `server` | string | false | Leave blank for default. https://hipchat.example.com |
| `server` | string | false | Leave blank for default. For example, `https://hipchat.example.com`. |
### Delete HipChat service
......@@ -496,7 +496,7 @@ Send IRC messages, on update, to a list of recipients through an Irker gateway.
Set Irker (IRC gateway) service for a project.
> NOTE: Irker does NOT have built-in authentication, which makes it vulnerable to spamming IRC channels if it is hosted outside of a firewall. Please make sure you run the daemon within a secured network to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.
> NOTE: Irker does NOT have built-in authentication, which makes it vulnerable to spamming IRC channels if it is hosted outside of a firewall. Please make sure you run the daemon within a secured network to prevent abuse. For more details, read: <http://www.catb.org/~esr/irker/security.html>.
```
PUT /projects/:id/services/irker
......@@ -557,7 +557,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project, e.g., `https://jira.example.com`. |
| `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project. For example, `https://jira.example.com`. |
| `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
| `username` | string | yes | The username of the user created to be used with GitLab/JIRA. |
| `password` | string | yes | The password of the user created to be used with GitLab/JIRA. |
......@@ -589,7 +589,7 @@ PUT /projects/:id/services/kubernetes
Parameters:
- `namespace` (**required**) - The Kubernetes namespace to use
- `api_url` (**required**) - The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com
- `api_url` (**required**) - The URL to the Kubernetes cluster API. For example, `https://kubernetes.example.com`
- `token` (**required**) - The service token to authenticate against the Kubernetes cluster with
- `ca_pem` (optional) - A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)
......@@ -658,7 +658,6 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `token` | string | yes | The Slack token |
### Delete Slack slash command service
Delete Slack slash command service for a project.
......@@ -823,7 +822,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `api_url` | string | true | Prometheus API Base URL, like http://prometheus.example.com/ |
| `api_url` | string | true | Prometheus API Base URL. For example, `http://prometheus.example.com/`. |
### Delete Prometheus service
......@@ -934,7 +933,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `webhook` | string | true | https://hooks.slack.com/services/... |
| `webhook` | string | true | `https://hooks.slack.com/services/...` |
| `username` | string | false | username |
| `channel` | string | false | Default channel to use if others are not configured |
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
......@@ -988,7 +987,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `webhook` | string | true | The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/... |
| `webhook` | string | true | The Microsoft Teams webhook. For example, `https://outlook.office.com/webhook/...` |
### Delete Microsoft Teams service
......@@ -1024,7 +1023,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `webhook` | string | true | The Mattermost webhook. e.g. http://mattermost_host/hooks/... |
| `webhook` | string | true | The Mattermost webhook. For example, `http://mattermost_host/hooks/...` |
| `username` | string | false | username |
| `channel` | string | false | Default channel to use if others are not configured |
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
......@@ -1080,7 +1079,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `teamcity_url` | string | true | TeamCity root URL like https://teamcity.example.com |
| `teamcity_url` | string | true | TeamCity root URL. For example, `https://teamcity.example.com` |
| `build_type` | string | true | Build configuration ID |
| `username` | string | true | A user with permissions to trigger a manual build |
| `password` | string | true | The password of the user |
......@@ -1104,7 +1103,6 @@ GET /projects/:id/services/teamcity
[jira-doc]: ../user/project/integrations/jira.md
[old-jira-api]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/api/services.md#jira
## MockCI
Mock an external CI. See [`gitlab-org/gitlab-mock-ci-service`](https://gitlab.com/gitlab-org/gitlab-mock-ci-service) for an example of a companion mock service.
......@@ -1123,7 +1121,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `mock_service_url` | string | true | http://localhost:4004 |
| `mock_service_url` | string | true | `http://localhost:4004` |
### Delete MockCI service
......
This diff is collapsed.
This diff is collapsed.
# End-to-End Testing
# End-to-end Testing
## What is End-to-End testing?
## What is end-to-end testing?
End-to-End testing is a strategy used to check whether your application works
as expected across entire software stack and architecture, including
integration of all microservices and components that are supposed to work
End-to-end testing is a strategy used to check whether your application works
as expected across the entire software stack and architecture, including
integration of all micro-services and components that are supposed to work
together.
## How do we test GitLab?
We use [Omnibus GitLab][omnibus-gitlab] to build GitLab packages and then we
test these packages using [GitLab QA][gitlab-qa] project, which is entirely
black-box, click-driven testing framework.
test these packages using the [GitLab QA orchestrator][gitlab-qa] tool, which is
a black-box testing framework for the API and the UI.
### Testing nightly builds
We run scheduled pipeline each night to test nightly builds created by Omnibus.
You can find these nightly pipelines at [GitLab QA pipelines page][gitlab-qa-pipelines].
You can find these nightly pipelines at [gitlab-org/quality/nightly/pipelines][quality-nightly-pipelines].
### Testing staging
We run scheduled pipeline each night to test staging.
You can find these nightly pipelines at [gitlab-org/quality/staging/pipelines][quality-staging-pipelines].
### Testing code in merge requests
It is possible to run end-to-end tests (eventually being run within a
[GitLab QA pipeline][gitlab-qa-pipelines]) for a merge request by triggering
the `package-and-qa` manual action, that should be present in a merge request
widget.
the `package-and-qa` manual action in the `test` stage, that should be present
in a merge request widget (unless the merge request is from a fork).
Manual action that starts end-to-end tests is also available in merge requests
in Omnibus GitLab project.
in [Omnibus GitLab][omnibus-gitlab].
Below you can read more about how to use it and how does it work.
......@@ -35,46 +40,56 @@ Below you can read more about how to use it and how does it work.
Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines.
1. Developer triggers a manual action, that can be found in CE and EE merge
1. Developer triggers a manual action, that can be found in CE / EE merge
requests. This starts a chain of pipelines in multiple projects.
1. The script being executed triggers a pipeline in GitLab Omnibus and waits
for the resulting status. We call this a _status attribution_.
1. The script being executed triggers a pipeline in [Omnibus GitLab][omnibus-gitlab]
and waits for the resulting status. We call this a _status attribution_.
1. GitLab packages are being built in Omnibus pipeline. Packages are going to be
pushed to Container Registry.
1. GitLab packages are being built in the [Omnibus GitLab][omnibus-gitlab]
pipeline. Packages are then pushed to its Container Registry.
1. When packages are ready, and available in the registry, a final step in the
pipeline, that is now running in Omnibus, triggers a new pipeline in the GitLab
QA project. It also waits for a resulting status.
[Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new
[GitLab QA pipeline][gitlab-qa-pipelines]. It also waits for a resulting status.
1. GitLab QA pulls images from the registry, spins-up containers and runs tests
against a test environment that has been just orchestrated by the `gitlab-qa`
tool.
1. The result of the GitLab QA pipeline is being propagated upstream, through
Omnibus, back to CE / EE merge request.
1. The result of the [GitLab QA pipeline][gitlab-qa-pipelines] is being
propagated upstream, through Omnibus, back to the CE / EE merge request.
#### How do I write tests?
In order to write new tests, you first need to learn more about GitLab QA
architecture. See the [documentation about it][gitlab-qa-architecture] in
GitLab QA project.
architecture. See the [documentation about it][gitlab-qa-architecture].
Once you decided where to put test environment orchestration scenarios and
instance specs, take a look at the [relevant documentation][instance-qa-readme]
and examples in [the `qa/` directory][instance-qa-examples].
Once you decided where to put [test environment orchestration scenarios] and
[instance-level scenarios], take a look at the [GitLab QA README][instance-qa-readme],
the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing
instance-level scenarios][instance-level scenarios].
## Where can I ask for help?
You can ask question in the `#quality` channel on Slack (GitLab internal) or
you can find an issue you would like to work on in
[the issue tracker][gitlab-qa-issues] and start a new discussion there.
[the `gitlab-ce` issue tracker][gitlab-ce-issues],
[the `gitlab-ee` issue tracker][gitlab-ce-issues], or
[the `gitlab-qa` issue tracker][gitlab-qa-issues].
[omnibus-gitlab]: https://gitlab.com/gitlab-org/omnibus-gitlab
[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa
[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
[gitlab-qa-pipelines]: https://gitlab.com/gitlab-org/gitlab-qa/pipelines
[quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines
[quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines
[gitlab-qa-architecture]: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/architecture.md
[gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues
[gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues?label_name%5B%5D=new+scenario
[gitlab-ce-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name[]=QA&label_name[]=test
[gitlab-ee-issues]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name[]=QA&label_name[]=test
[test environment orchestration scenarios]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/lib/gitlab/qa/scenario
[instance-level scenarios]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/specs/features
[Page objects documentation]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page/README.md
[instance-qa-readme]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/README.md
[instance-qa-examples]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa
# Smoke Tests
It is imperative in any testing suite that we have Smoke Tests. In short, smoke tests are will run quick sanity
end-to-end functional tests from GitLab QA and are designed to run against the specified environment to ensure that
basic functionality is working.
It is imperative in any testing suite that we have Smoke Tests. In short, smoke
tests will run quick sanity end-to-end functional tests from GitLab QA and are
designed to run against the specified environment to ensure that basic
functionality is working.
Currently, our suite consists of this basic functionality coverage:
......@@ -11,6 +12,8 @@ Currently, our suite consists of this basic functionality coverage:
- Issue Creation
- Merge Request Creation
Smoke tests have the `:smoke` RSpec metadata.
---
[Return to Testing documentation](index.md)
......@@ -34,7 +34,11 @@ records should use stubs/doubles as much as possible.
Formal definition: https://en.wikipedia.org/wiki/Integration_testing
These kind of tests ensure that individual parts of the application work well together, without the overhead of the actual app environment (i.e. the browser). These tests should assert at the request/response level: status code, headers, body. They're useful to test permissions, redirections, what view is rendered etc.
These kind of tests ensure that individual parts of the application work well
together, without the overhead of the actual app environment (i.e. the browser).
These tests should assert at the request/response level: status code, headers,
body.
They're useful to test permissions, redirections, what view is rendered etc.
| Code path | Tests path | Testing engine | Notes |
| --------- | ---------- | -------------- | ----- |
......@@ -67,20 +71,40 @@ run JavaScript tests, so you can either run unit tests (e.g. test a single
JavaScript method), or integration tests (e.g. test a component that is composed
of multiple components).
## System tests or feature tests
## White-box tests at the system level (formerly known as System / Feature tests)
Formal definition: https://en.wikipedia.org/wiki/System_testing.
Formal definitions:
These kind of tests ensure the application works as expected from a user point
of view (aka black-box testing). These tests should test a happy path for a
given page or set of pages, and a test case should be added for any regression
- https://en.wikipedia.org/wiki/System_testing
- https://en.wikipedia.org/wiki/White-box_testing
These kind of tests ensure the GitLab *Rails* application (i.e.
`gitlab-ce`/`gitlab-ee`) works as expected from a *browser* point of view.
Note that:
- knowledge of the internals of the application are still required
- data needed for the tests are usually created directly using RSpec factories
- expectations are often set on the database or objects state
These tests should only be used when:
- the functionality/component being tested is small
- the internal state of the objects/database *needs* to be tested
- it cannot be tested at a lower level
For instance, to test the breadcrumbs on a given page, writing a system test
makes sense since it's a small component, which cannot be tested at the unit or
controller level.
Only test the happy path, but make sure to add a test case for any regression
that couldn't have been caught at lower levels with better tests (i.e. if a
regression is found, regression tests should be added at the lowest-level
possible).
| Tests path | Testing engine | Notes |
| ---------- | -------------- | ----- |
| `spec/features/` | [Capybara] + [RSpec] | If your spec has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. |
| `spec/features/` | [Capybara] + [RSpec] | If your test has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. |
### Consider **not** writing a system test!
......@@ -89,7 +113,7 @@ we have enough Unit & Integration tests), we shouldn't need to duplicate their
thorough testing at the System test level.
It's very easy to add tests, but a lot harder to remove or improve tests, so one
should take care of not introducing too many (slow and duplicated) specs.
should take care of not introducing too many (slow and duplicated) tests.
The reasons why we should follow these best practices are as follows:
......@@ -107,29 +131,33 @@ The reasons why we should follow these best practices are as follows:
[Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist
[RackTest]: https://github.com/teamcapybara/capybara#racktest
## Black-box tests or end-to-end tests
## Black-box tests at the system level, aka end-to-end tests
Formal definitions:
- https://en.wikipedia.org/wiki/System_testing
- https://en.wikipedia.org/wiki/Black-box_testing
GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse],
[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces
are configured and packaged by [GitLab Omnibus].
[GitLab QA] is a tool that allows to test that all these pieces integrate well
together by building a Docker image for a given version of GitLab Rails and
running feature tests (i.e. using Capybara) against it.
The QA framework and instance-level scenarios are [part of GitLab Rails] so that
they're always in-sync with the codebase (especially the views).
The actual test scenarios and steps are [part of GitLab Rails] so that they're
always in-sync with the codebase.
Note that:
### Smoke tests
Smoke tests are quick tests that may be run at any time (especially after the pre-deployment migrations).
- knowledge of the internals of the application are not required
- data needed for the tests can only be created using the GUI or the API
- expectations can only be made against the browser page and API responses
Much like feature tests - these tests run against the UI and ensure that basic functionality is working.
Every new feature should come with a [test plan].
> See [Smoke Tests](smoke.md) for more information.
| Tests path | Testing engine | Notes |
| ---------- | -------------- | ----- |
| `qa/qa/specs/features/` | [Capybara] + [RSpec] + Custom QA framework | Tests should be placed under their corresponding [Product category] |
Read a separate document about [end-to-end tests](end_to_end_tests.md) to
learn more.
> See [end-to-end tests](end_to_end_tests.md) for more information.
[multiple pieces]: ../architecture.md#components
[GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell
......@@ -138,8 +166,29 @@ learn more.
[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages
[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-runner
[GitLab Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab
[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa
[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa
[test plan]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/.gitlab/issue_templates/Test%20plan.md
[Product category]: https://about.gitlab.com/handbook/product/categories/
### Smoke tests
Smoke tests are quick tests that may be run at any time (especially after the
pre-deployment migrations).
These tests run against the UI and ensure that basic functionality is working.
> See [Smoke Tests](smoke.md) for more information.
### GitLab QA orchestrator
[GitLab QA orchestrator] is a tool that allows to test that all these pieces
integrate well together by building a Docker image for a given version of GitLab
Rails and running end-to-end tests (i.e. using Capybara) against it.
Learn more in the [GitLab QA orchestrator README][gitlab-qa-readme].
[GitLab QA orchestrator]: https://gitlab.com/gitlab-org/gitlab-qa
[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
## EE-specific tests
......
# GitLab Runner Helm Chart
> **Note:**
These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/gitlab-runner/issues).
These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/gitlab-org/gitlab-runner/issues).
The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster.
......
......@@ -36,7 +36,7 @@ Please consider using a virtual machine to run GitLab.
GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13.
You will have to use the standard MRI implementation of Ruby.
We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab
needs several Gems that have native extensions.
## Hardware requirements
......
......@@ -3,7 +3,7 @@
To enable the Auth0 OmniAuth provider, you must create an Auth0 account, and an
application.
1. Sign in to the [Auth0 Console](https://manage.auth0.com). If you need to
1. Sign in to the [Auth0 Console](https://auth0.com/auth/login). If you need to
create an account, you can do so at the same link.
1. Select "New App/API".
......
......@@ -54,6 +54,7 @@ you to use.
```
Account: Email, Read
Projects: Read
Repositories: Read
Pull Requests: Read
Issues: Read
......
......@@ -35,7 +35,7 @@ In Google's side:
1. You should now be able to see a Client ID and Client secret. Note them down
or keep this page open as you will need them later.
1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Compute > Google+ API > Enable**
1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Social > Google+ API > Enable**
1. To enable projects to access [Google Kubernetes Engine](../user/project/clusters/index.md), you must also
enable these APIs:
- Google Kubernetes Engine API
......
# Markdown
# GitLab Markdown
This markdown guide is valid for GitLab's system markdown entries and files.
This markdown guide is **valid for GitLab's system markdown entries and files**.
It is not valid for the [GitLab documentation website](https://docs.gitlab.com)
nor [GitLab's main website](https://about.gitlab.com), as they both use
[Kramdown](https://kramdown.gettalong.org) as their markdown engine.
The documentation website uses an extended Kramdown gem, [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown).
Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference._
## GitLab Flavored Markdown (GFM)
......@@ -8,21 +13,21 @@ GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specifi
You can use GFM in the following areas:
- comments
- issues
- merge requests
- milestones
- snippets (the snippet must be named with a `.md` extension)
- wiki pages
- markdown documents inside the repository
- Comments
- Issues
- Merge requests
- Milestones
- Snippets (the snippet must be named with a `.md` extension)
- Wiki pages
- Markdown documents inside repositories
You can also use other rich text files in GitLab. You might have to install a
dependency to do so. Please see the [`github-markup` gem readme](https://github.com/gitlabhq/markup#markups) for more information.
> **Notes:**
>
> For the best result, we encourage you to check this document out as rendered
> by GitLab itself: [markdown.md]
> For the best result, we encourage you to check this document out as [rendered
> by GitLab itself](markdown.md).
>
> As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown
processing of all new issues, merge requests, comments, and other Markdown content
......@@ -30,6 +35,9 @@ in the GitLab system. As of 11.3, wiki pages and Markdown files (`.md`) in the
repositories are also processed with CommonMark. Older content in issues/comments
are still processed using the [Redcarpet Ruby library][redcarpet].
>
> The documentation website had its [markdown engine migrated from Redcarpet to Kramdown](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/108)
in October 2018.
>
> _Where there are significant differences, we will try to call them out in this document._
### Transitioning to CommonMark
......
# Migrating from ClearCase
[ClearCase](https://www-03.ibm.com/software/products/en/clearcase/) is a set of
[ClearCase](https://www.ibm.com/us-en/marketplace/rational-clearcase) is a set of
tools developed by IBM which also include a centralized version control system
similar to Git.
......
......@@ -29,7 +29,7 @@ Below is a valid example of a manifest file:
```xml
<manifest>
<remote review="https://android-review.googlesource.com/" />
<remote review="https://android.googlesource.com/" />
<project path="build/make" name="platform/build" />
<project path="build/blueprint" name="platform/build/blueprint" />
......@@ -39,9 +39,9 @@ Below is a valid example of a manifest file:
As a result, the following projects will be created:
| GitLab | Import URL |
|---|---|
| https://gitlab.com/YOUR_GROUP/build/make | https://android-review.googlesource.com/platform/build |
| https://gitlab.com/YOUR_GROUP/build/blueprint | https://android-review.googlesource.com/platform/build/blueprint |
|:------------------------------------------------|:------------------------------------------------------------|
| `https://gitlab.com/YOUR_GROUP/build/make` | <https://android.googlesource.com/platform/build> |
| `https://gitlab.com/YOUR_GROUP/build/blueprint` | <https://android.googlesource.com/platform/build/blueprint> |
## Importing the repositories
......
# Discord Notifications service
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22684) in GitLab 11.5.
The Discord Notifications service sends event notifications from GitLab to the channel for which the webhook was created.
To send GitLab event notifications to a Discord channel, create a webhook in Discord and configure it in GitLab.
## Create webhook
1. Open the Discord channel you want to receive GitLab event notifications.
1. From the channel menu, select **Edit channel**.
1. Click on **Webhooks** menu item.
1. Click the **Create Webhook** button and fill in the name of the bot that will post the messages. Optionally, edit the avatar.
1. Note the URL from the **WEBHOOK URL** field.
1. Click the **Save** button.
## Configure created webhook in GitLab
With the webhook URL created in the Discord channel, you can set up the Discord Notifications service in GitLab.
1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services) in your project's settings. That is, **Project > Settings > Integrations**.
1. Select the **Discord Notifications** project service to configure it.
1. Check the **Active** checkbox to turn on the service.
1. Check the checkboxes corresponding to the GitLab events for which you want to send notifications to Discord.
1. Paste the webhook URL that you copied from the create Discord webhook step.
1. Configure the remaining options and click the **Save changes** button.
The Discord channel you created the webhook for will now receive notification of the GitLab events that were configured.
......@@ -4,9 +4,9 @@
To enable Mattermost integration you must create an incoming webhook integration:
1. Sign in to your Mattermost instance
1. Visit incoming webhooks, that will be something like: https://mattermost.example/your_team_name/integrations/incoming_webhooks/add
1. Choose a display name, description and channel, those can be overridden on GitLab
1. Sign in to your Mattermost instance.
1. Visit incoming webhooks, that will be something like: `https://mattermost.example.com/your_team_name/integrations/incoming_webhooks/add`.
1. Choose a display name, description and channel, those can be overridden on GitLab.
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
......
......@@ -30,6 +30,7 @@ Click on the service links to see further configuration instructions and details
| [Bugzilla](bugzilla.md) | Bugzilla issue tracker |
| Campfire | Simple web-based real-time group chat |
| Custom Issue Tracker | Custom issue tracker |
| [Discord Notifications](discord_notifications.md) | Receive event notifications in Discord |
| Drone CI | Continuous Integration platform built on Docker, written in Go |
| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients |
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
......
......@@ -298,6 +298,14 @@ module API
desc: 'Title'
}
],
'discord' => [
{
required: true,
name: :webhook,
type: String,
desc: 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…'
}
],
'drone-ci' => [
{
required: true,
......@@ -677,6 +685,7 @@ module API
BuildkiteService,
CampfireService,
CustomIssueTrackerService,
DiscordService,
DroneCiService,
EmailsOnPushService,
ExternalWikiService,
......
......@@ -29,6 +29,7 @@ module Banzai
end
def absolute_link_attr(uri)
# Here we really want to expand relative path to absolute path
URI.join(Gitlab.config.gitlab.url, uri).to_s
end
end
......
......@@ -88,35 +88,19 @@ module BitbucketServer
def build_url(path)
return path if path.starts_with?(root_url)
url_join_paths(root_url, path)
Gitlab::Utils.append_path(root_url, path)
end
def root_url
url_join_paths(base_uri, "/rest/api/#{api_version}")
Gitlab::Utils.append_path(base_uri, "rest/api/#{api_version}")
end
def delete_url(resource, path)
if resource == :branches
url_join_paths(base_uri, "/rest/branch-utils/#{api_version}#{path}")
Gitlab::Utils.append_path(base_uri, "rest/branch-utils/#{api_version}#{path}")
else
build_url(path)
end
end
# URI.join is stupid in that slashes are important:
#
# # URI.join('http://example.com/subpath', 'hello')
# => http://example.com/hello
#
# We really want http://example.com/subpath/hello
#
def url_join_paths(*paths)
paths.map { |path| strip_slashes(path) }.join(SEPARATOR)
end
def strip_slashes(path)
path = path[1..-1] if path.starts_with?(SEPARATOR)
path.chomp(SEPARATOR)
end
end
end
......@@ -116,7 +116,9 @@ code_quality:
license_management:
stage: test
image: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
image:
name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
entrypoint: [""]
allow_failure: true
script:
- license_management
......
......@@ -28,6 +28,7 @@ module Gitlab
@repository = repository
@diff_refs = diff_refs
@fallback_diff_refs = fallback_diff_refs
@unfolded = false
# Ensure items are collected in the the batch
new_blob_lazy
......@@ -137,6 +138,24 @@ module Gitlab
Gitlab::Diff::Parser.new.parse(raw_diff.each_line, diff_file: self).to_a
end
# Changes diff_lines according to the given position. That is,
# it checks whether the position requires blob lines into the diff
# in order to be presented.
def unfold_diff_lines(position)
return unless position
unfolder = Gitlab::Diff::LinesUnfolder.new(self, position)
if unfolder.unfold_required?
@diff_lines = unfolder.unfolded_diff_lines
@unfolded = true
end
end
def unfolded?
@unfolded
end
def highlighted_diff_lines
@highlighted_diff_lines ||=
Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
......
......@@ -5,9 +5,9 @@ module Gitlab
class Line
SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze
attr_reader :line_code, :type, :index, :old_pos, :new_pos
attr_reader :line_code, :type, :old_pos, :new_pos
attr_writer :rich_text
attr_accessor :text
attr_accessor :text, :index
def initialize(text, type, index, old_pos, new_pos, parent_file: nil, line_code: nil, rich_text: nil)
@text, @type, @index = text, type, index
......@@ -21,7 +21,14 @@ module Gitlab
end
def self.init_from_hash(hash)
new(hash[:text], hash[:type], hash[:index], hash[:old_pos], hash[:new_pos], line_code: hash[:line_code], rich_text: hash[:rich_text])
new(hash[:text],
hash[:type],
hash[:index],
hash[:old_pos],
hash[:new_pos],
parent_file: hash[:parent_file],
line_code: hash[:line_code],
rich_text: hash[:rich_text])
end
def to_hash
......
# frozen_string_literal: true
# Given a position, calculates which Blob lines should be extracted, treated and
# injected in the current diff file lines in order to present a "unfolded" diff.
module Gitlab
module Diff
class LinesUnfolder
include Gitlab::Utils::StrongMemoize
UNFOLD_CONTEXT_SIZE = 3
def initialize(diff_file, position)
@diff_file = diff_file
@blob = diff_file.old_blob
@position = position
@generate_top_match_line = true
@generate_bottom_match_line = true
# These methods update `@generate_top_match_line` and
# `@generate_bottom_match_line`.
@from_blob_line = calculate_from_blob_line!
@to_blob_line = calculate_to_blob_line!
end
# Returns merged diff lines with required blob lines with correct
# positions.
def unfolded_diff_lines
strong_memoize(:unfolded_diff_lines) do
next unless unfold_required?
merged_diff_with_blob_lines
end
end
# Returns the extracted lines from the old blob which should be merged
# with the current diff lines.
def blob_lines
strong_memoize(:blob_lines) do
# Blob lines, unlike diffs, doesn't start with an empty space for
# unchanged line, so the parsing and highlighting step can get fuzzy
# without the following change.
line_prefix = ' '
blob_as_diff_lines = @blob.data.each_line.map { |line| "#{line_prefix}#{line}" }
lines = Gitlab::Diff::Parser.new.parse(blob_as_diff_lines, diff_file: @diff_file).to_a
from = from_blob_line - 1
to = to_blob_line - 1
lines[from..to]
end
end
def unfold_required?
strong_memoize(:unfold_required) do
next false unless @diff_file.text?
next false unless @position.unchanged?
next false if @diff_file.new_file? || @diff_file.deleted_file?
next false unless @position.old_line
# Invalid position (MR import scenario)
next false if @position.old_line > @blob.lines.size
next false if @diff_file.diff_lines.empty?
next false if @diff_file.line_for_position(@position)
next false unless unfold_line
true
end
end
private
attr_reader :from_blob_line, :to_blob_line
def merged_diff_with_blob_lines
lines = @diff_file.diff_lines
match_line = unfold_line
insert_index = bottom? ? -1 : match_line.index
lines -= [match_line] unless bottom?
lines.insert(insert_index, *blob_lines_with_matches)
# The inserted blob lines have invalid indexes, so we need
# to reindex them.
reindex(lines)
lines
end
# Returns 'unchanged' blob lines with recalculated `old_pos` and
# `new_pos` and the recalculated new match line (needed if we for instance
# we unfolded once, but there are still folded lines).
def blob_lines_with_matches
old_pos = from_blob_line
new_pos = from_blob_line + offset
new_blob_lines = []
new_blob_lines.push(top_blob_match_line) if top_blob_match_line
blob_lines.each do |line|
new_blob_lines << Gitlab::Diff::Line.new(line.text, line.type, nil, old_pos, new_pos,
parent_file: @diff_file)
old_pos += 1
new_pos += 1
end
new_blob_lines.push(bottom_blob_match_line) if bottom_blob_match_line
new_blob_lines
end
def reindex(lines)
lines.each_with_index { |line, i| line.index = i }
end
def top_blob_match_line
strong_memoize(:top_blob_match_line) do
next unless @generate_top_match_line
old_pos = from_blob_line
new_pos = from_blob_line + offset
build_match_line(old_pos, new_pos)
end
end
def bottom_blob_match_line
strong_memoize(:bottom_blob_match_line) do
# The bottom line match addition is already handled on
# Diff::File#diff_lines_for_serializer
next if bottom?
next unless @generate_bottom_match_line
position = line_after_unfold_position.old_pos
old_pos = position
new_pos = position + offset
build_match_line(old_pos, new_pos)
end
end
def build_match_line(old_pos, new_pos)
blob_lines_length = blob_lines.length
old_line_ref = [old_pos, blob_lines_length].join(',')
new_line_ref = [new_pos, blob_lines_length].join(',')
new_match_line_str = "@@ -#{old_line_ref}+#{new_line_ref} @@"
Gitlab::Diff::Line.new(new_match_line_str, 'match', nil, old_pos, new_pos)
end
# Returns the first line position that should be extracted
# from `blob_lines`.
def calculate_from_blob_line!
return unless unfold_required?
from = comment_position - UNFOLD_CONTEXT_SIZE
# There's no line before the match if it's in the top-most
# position.
prev_line_number = line_before_unfold_position&.old_pos || 0
if from <= prev_line_number + 1
@generate_top_match_line = false
from = prev_line_number + 1
end
from
end
# Returns the last line position that should be extracted
# from `blob_lines`.
def calculate_to_blob_line!
return unless unfold_required?
to = comment_position + UNFOLD_CONTEXT_SIZE
return to if bottom?
next_line_number = line_after_unfold_position.old_pos
if to >= next_line_number - 1
@generate_bottom_match_line = false
to = next_line_number - 1
end
to
end
def offset
unfold_line.new_pos - unfold_line.old_pos
end
def line_before_unfold_position
return unless index = unfold_line&.index
@diff_file.diff_lines[index - 1] if index > 0
end
def line_after_unfold_position
return unless index = unfold_line&.index
@diff_file.diff_lines[index + 1] if index >= 0
end
def bottom?
strong_memoize(:bottom) do
@position.old_line > last_line.old_pos
end
end
# Returns the line which needed to be expanded in order to send a comment
# in `@position`.
def unfold_line
strong_memoize(:unfold_line) do
next last_line if bottom?
@diff_file.diff_lines.find do |line|
line.old_pos > comment_position && line.type == 'match'
end
end
end
def comment_position
@position.old_line
end
def last_line
@diff_file.diff_lines.last
end
end
end
end
......@@ -103,6 +103,10 @@ module Gitlab
@diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha)
end
def unfolded_diff?(repository)
diff_file(repository)&.unfolded?
end
def diff_file(repository)
return @diff_file if defined?(@diff_file)
......@@ -136,7 +140,13 @@ module Gitlab
return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project)
comparison.diffs(diff_options).diff_files.first
file = comparison.diffs(diff_options).diff_files.first
# We need to unfold diff lines according to the position in order
# to correctly calculate the line code and trace position changes.
file&.unfold_diff_lines(self)
file
end
def get_formatter_class(type)
......
......@@ -31,19 +31,19 @@ module Gitlab
class ReferenceRewriter
RewriteError = Class.new(StandardError)
def initialize(text, source_project, current_user)
def initialize(text, source_parent, current_user)
@text = text
@source_project = source_project
@source_parent = source_parent
@current_user = current_user
@original_html = markdown(text)
@pattern = Gitlab::ReferenceExtractor.references_pattern
end
def rewrite(target_project)
def rewrite(target_parent)
return @text unless needs_rewrite?
@text.gsub(@pattern) do |reference|
unfold_reference(reference, Regexp.last_match, target_project)
unfold_reference(reference, Regexp.last_match, target_parent)
end
end
......@@ -53,14 +53,14 @@ module Gitlab
private
def unfold_reference(reference, match, target_project)
def unfold_reference(reference, match, target_parent)
before = @text[0...match.begin(0)]
after = @text[match.end(0)..-1]
referable = find_referable(reference)
return reference unless referable
cross_reference = build_cross_reference(referable, target_project)
cross_reference = build_cross_reference(referable, target_parent)
return reference if reference == cross_reference
if cross_reference.nil?
......@@ -72,17 +72,17 @@ module Gitlab
end
def find_referable(reference)
extractor = Gitlab::ReferenceExtractor.new(@source_project,
extractor = Gitlab::ReferenceExtractor.new(@source_parent,
@current_user)
extractor.analyze(reference)
extractor.all.first
end
def build_cross_reference(referable, target_project)
def build_cross_reference(referable, target_parent)
if referable.respond_to?(:project)
referable.to_reference(target_project)
referable.to_reference(target_parent)
else
referable.to_reference(@source_project, target_project: target_project)
referable.to_reference(@source_parent, target_project: target_parent)
end
end
......@@ -91,7 +91,7 @@ module Gitlab
end
def markdown(text)
Banzai.render(text, project: @source_project, no_original_data: true)
Banzai.render(text, project: @source_parent, no_original_data: true)
end
end
end
......
......@@ -16,14 +16,15 @@ module Gitlab
@pattern = FileUploader::MARKDOWN_PATTERN
end
def rewrite(target_project)
def rewrite(target_parent)
return @text unless needs_rewrite?
@text.gsub(@pattern) do |markdown|
file = find_file(@source_project, $~[:secret], $~[:file])
break markdown unless file.try(:exists?)
moved = FileUploader.copy_to(file, target_project)
klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
moved = klass.copy_to(file, target_parent)
moved.markdown_link
end
end
......
......@@ -8,7 +8,10 @@ module Gitlab
def add_gon_variables
gon.api_version = 'v4'
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.default_avatar_url =
Gitlab::Utils.append_path(
Gitlab.config.gitlab.url,
ActionController::Base.helpers.image_path('no_avatar.png'))
gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path
......
......@@ -63,7 +63,7 @@ module Gitlab
end
def repository_url(name)
URI.join(remote, name).to_s
Gitlab::Utils.append_path(remote, name)
end
def remote
......
......@@ -38,7 +38,7 @@ module Gitlab
def go_body(path)
config = Gitlab.config
project_url = URI.join(config.gitlab.url, path)
project_url = Gitlab::Utils.append_path(config.gitlab.url, path)
import_prefix = strip_url(project_url.to_s)
repository_url = if Gitlab::CurrentSettings.enabled_git_access_protocol == 'ssh'
......
......@@ -2,13 +2,14 @@ module Gitlab
module QuickActions
class CommandDefinition
attr_accessor :name, :aliases, :description, :explanation, :params,
:condition_block, :parse_params_block, :action_block
:condition_block, :parse_params_block, :action_block, :warning
def initialize(name, attributes = {})
@name = name
@aliases = attributes[:aliases] || []
@description = attributes[:description] || ''
@warning = attributes[:warning] || ''
@explanation = attributes[:explanation] || ''
@params = attributes[:params] || []
@condition_block = attributes[:condition_block]
......@@ -33,11 +34,13 @@ module Gitlab
def explain(context, arg)
return unless available?(context)
if explanation.respond_to?(:call)
message = if explanation.respond_to?(:call)
execute_block(explanation, context, arg)
else
explanation
end
warning.empty? ? message : "#{message} (#{warning})"
end
def execute(context, arg)
......@@ -61,6 +64,7 @@ module Gitlab
name: name,
aliases: aliases,
description: desc,
warning: warning,
params: prms
}
end
......
......@@ -31,6 +31,10 @@ module Gitlab
@description = block_given? ? block : text
end
def warning(message = '')
@warning = message
end
# Allows to define params for the next quick action.
# These params are shown in the autocomplete menu.
#
......@@ -133,6 +137,7 @@ module Gitlab
name,
aliases: aliases,
description: @description,
warning: @warning,
explanation: @explanation,
params: @params,
condition_block: @condition_block,
......@@ -150,6 +155,7 @@ module Gitlab
@explanation = nil
@params = nil
@condition_block = nil
@warning = nil
@parse_params_block = nil
end
end
......
# frozen_string_literal: true
require 'jwt'
module JSONWebToken
class HMACToken < Token
IAT_LEEWAY = 60
JWT_ALGORITHM = 'HS256'
def initialize(secret)
super()
@secret = secret
end
def self.decode(token, secret, leeway: IAT_LEEWAY, verify_iat: true)
JWT.decode(token, secret, true, leeway: leeway, verify_iat: verify_iat, algorithm: JWT_ALGORITHM)
end
def encoded
JWT.encode(payload, secret, JWT_ALGORITHM)
end
private
attr_reader :secret
end
end
# frozen_string_literal: true
require 'securerandom'
module JSONWebToken
class Token
attr_accessor :issuer, :subject, :audience, :id
attr_accessor :issued_at, :not_before, :expire_time
DEFAULT_NOT_BEFORE_TIME = 5
DEFAULT_EXPIRE_TIME = 60
def initialize
@id = SecureRandom.uuid
@issued_at = Time.now
# we give a few seconds for time shift
@not_before = issued_at - 5.seconds
@not_before = issued_at - DEFAULT_NOT_BEFORE_TIME
# default 60 seconds should be more than enough for this authentication token
@expire_time = issued_at + 1.minute
@expire_time = issued_at + DEFAULT_EXPIRE_TIME
@custom_payload = {}
end
......
......@@ -45,7 +45,6 @@ namespace :gitlab do
start_checking "GitLab Shell"
check_gitlab_shell
check_repos_hooks_directory_is_link
check_gitlab_shell_self_test
finished_checking "GitLab Shell"
......@@ -54,42 +53,6 @@ namespace :gitlab do
# Checks
########################
def check_repos_hooks_directory_is_link
print "hooks directories in repos are links: ... "
gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path
unless Project.count > 0
puts "can't check, you have no projects".color(:magenta)
return
end
puts ""
Project.find_each(batch_size: 100) do |project|
print sanitized_message(project)
project_hook_directory = File.join(project.repository.path_to_repo, "hooks")
if project.empty_repo?
puts "repository is empty".color(:magenta)
elsif File.directory?(project_hook_directory) && File.directory?(gitlab_shell_hooks_path) &&
(File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path))
puts 'ok'.color(:green)
else
puts "wrong or missing hooks".color(:red)
try_fixing_it(
sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')} #{repository_storage_paths_args.join(' ')}"),
'Check the hooks_path in config/gitlab.yml',
'Check your gitlab-shell installation'
)
for_more_information(
see_installation_guide_section "GitLab Shell"
)
fix_and_rerun
end
end
end
def check_gitlab_shell_self_test
gitlab_shell_repo_base = gitlab_shell_path
check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
......
......@@ -136,6 +136,7 @@ HELM_CMD=$(cat << EOF
helm upgrade --install \
--wait \
--timeout 600 \
--set global.appConfig.enableUsagePing=false \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set global.hosts.hostSuffix="$HOST_SUFFIX" \
--set global.hosts.domain="$REVIEW_APPS_DOMAIN" \
......
......@@ -76,7 +76,7 @@ describe SendFileUpload do
it 'sends a file with a custom type' do
headers = double
expected_headers = %r(response-content-disposition=attachment%3Bfilename%3D%22test.js%22&response-content-type=application/javascript)
expected_headers = %r(response-content-disposition=attachment%3Bfilename%3D%22test.js%22&response-content-type=application/ecmascript)
expect(Gitlab::Workhorse).to receive(:send_url).with(expected_headers).and_call_original
expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/)
......
......@@ -141,6 +141,28 @@ describe Projects::BlobController do
expect(lines.first).to have_key('rich_text')
end
context 'comment in any diff line feature flag' do
it 'renders context lines when feature disabled' do
stub_feature_flags(comment_in_any_diff_line: false)
do_get(since: 1, to: 5, offset: 10, from_merge_request: true)
lines = JSON.parse(response.body)
all_context = lines.all? { |line| line['type'] == 'context' }
expect(all_context).to be(true)
end
it 'renders unchanged lines when feature enabled' do
stub_feature_flags(comment_in_any_diff_line: true)
do_get(since: 1, to: 5, offset: 10, from_merge_request: true)
lines = JSON.parse(response.body)
all_unchanged = lines.all? { |line| line['type'].nil? }
expect(all_unchanged).to be(true)
end
end
context 'when rendering match lines' do
it 'adds top match line when "since" is less than 1' do
do_get(since: 5, to: 10, offset: 10, from_merge_request: true)
......@@ -157,7 +179,7 @@ describe Projects::BlobController do
match_line = JSON.parse(response.body).first
expect(match_line['type']).to eq('context')
expect(match_line['type']).to be_nil
end
it 'adds bottom match line when "t"o is less than blob size' do
......@@ -177,7 +199,7 @@ describe Projects::BlobController do
match_line = JSON.parse(response.body).last
expect(match_line['type']).to eq('context')
expect(match_line['type']).to be_nil
end
end
end
......
......@@ -13,7 +13,7 @@ FactoryBot.define do
end
trait :with_token do
service_account_token { Faker::Lorem.characters(10) }
service_account_token { FFaker::Lorem.characters(10) }
end
end
end
require "spec_helper"
describe "User views issue" do
set(:project) { create(:project_empty_repo, :public) }
set(:user) { create(:user) }
set(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
let(:project) { create(:project_empty_repo, :public) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
before do
project.add_developer(user)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment