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: ...@@ -318,6 +318,7 @@ review-docs-cleanup:
cloud-native-image: cloud-native-image:
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: [] before_script: []
dependencies: []
stage: test stage: test
allow_failure: true allow_failure: true
variables: variables:
...@@ -632,6 +633,7 @@ rails5_gemfile_lock_check: ...@@ -632,6 +633,7 @@ rails5_gemfile_lock_check:
ee_compat_check: ee_compat_check:
<<: *rake-exec <<: *rake-exec
dependencies: []
except: except:
- master - master
- tags - tags
...@@ -860,9 +862,7 @@ coverage: ...@@ -860,9 +862,7 @@ coverage:
lint:javascript:report: lint:javascript:report:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
stage: post-test stage: post-test
dependencies: dependencies: []
- compile-assets
- setup-test-env
before_script: [] before_script: []
script: script:
- date - date
...@@ -916,6 +916,7 @@ gitlab_git_test: ...@@ -916,6 +916,7 @@ gitlab_git_test:
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
before_script: [] before_script: []
dependencies: []
cache: {} cache: {}
script: script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
...@@ -926,6 +927,7 @@ no_ee_check: ...@@ -926,6 +927,7 @@ no_ee_check:
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
before_script: [] before_script: []
dependencies: []
cache: {} cache: {}
script: script:
- scripts/no-ee-check - scripts/no-ee-check
......
...@@ -124,7 +124,7 @@ gem 'seed-fu', '~> 2.3.7' ...@@ -124,7 +124,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 2.8' gem 'html-pipeline', '~> 2.8'
gem 'deckar01-task_list', '2.0.0' 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 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'redcarpet', '~> 3.4' gem 'redcarpet', '~> 3.4'
gem 'commonmarker', '~> 0.17' gem 'commonmarker', '~> 0.17'
...@@ -204,6 +204,9 @@ gem 'redis-rails', '~> 5.0.2' ...@@ -204,6 +204,9 @@ gem 'redis-rails', '~> 5.0.2'
gem 'redis', '~> 3.2' gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0' gem 'connection_pool', '~> 2.0'
# Discord integration
gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
# HipChat integration # HipChat integration
gem 'hipchat', '~> 1.5.0' gem 'hipchat', '~> 1.5.0'
...@@ -339,7 +342,7 @@ group :development, :test do ...@@ -339,7 +342,7 @@ group :development, :test do
gem 'minitest', '~> 5.7.0' gem 'minitest', '~> 5.7.0'
# Generate Fake data # Generate Fake data
gem 'ffaker', '~> 2.4' gem 'ffaker', '~> 2.10'
gem 'capybara', '~> 2.15' gem 'capybara', '~> 2.15'
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
...@@ -354,7 +357,7 @@ group :development, :test do ...@@ -354,7 +357,7 @@ group :development, :test do
gem 'rubocop-rspec', '~> 1.22.1' gem 'rubocop-rspec', '~> 1.22.1'
gem 'scss_lint', '~> 0.56.0', require: false 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 'simplecov', '~> 0.14.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false
......
...@@ -162,6 +162,8 @@ GEM ...@@ -162,6 +162,8 @@ GEM
rotp (~> 2.0) rotp (~> 2.0)
diff-lcs (1.3) diff-lcs (1.3)
diffy (3.1.0) diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20180417) domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
...@@ -200,7 +202,7 @@ GEM ...@@ -200,7 +202,7 @@ GEM
multi_json multi_json
fast_blank (1.0.0) fast_blank (1.0.0)
fast_gettext (1.6.0) fast_gettext (1.6.0)
ffaker (2.4.0) ffaker (2.10.0)
ffi (1.9.25) ffi (1.9.25)
flipper (0.13.0) flipper (0.13.0)
flipper-active_record (0.13.0) flipper-active_record (0.13.0)
...@@ -272,7 +274,7 @@ GEM ...@@ -272,7 +274,7 @@ GEM
gitaly-proto (0.123.0) gitaly-proto (0.123.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-markup (1.6.4) gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.3.0) gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (2.4.1) gitlab-styles (2.4.1)
...@@ -335,11 +337,11 @@ GEM ...@@ -335,11 +337,11 @@ GEM
haml (5.0.4) haml (5.0.4)
temple (>= 0.8.0) temple (>= 0.8.0)
tilt tilt
haml_lint (0.26.0) haml_lint (0.28.0)
haml (>= 4.0, < 5.1) haml (>= 4.0, < 5.1)
rainbow rainbow
rake (>= 10, < 13) rake (>= 10, < 13)
rubocop (>= 0.49.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hamlit (2.8.8) hamlit (2.8.8)
temple (>= 0.8.0) temple (>= 0.8.0)
...@@ -449,9 +451,9 @@ GEM ...@@ -449,9 +451,9 @@ GEM
memoizable (0.4.2) memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.0) method_source (0.9.0)
mime-types (3.1) mime-types (3.2.2)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521) mime-types-data (3.2018.0812)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_magick (4.8.0) mini_magick (4.8.0)
mini_mime (1.0.1) mini_mime (1.0.1)
...@@ -596,7 +598,7 @@ GEM ...@@ -596,7 +598,7 @@ GEM
get_process_mem (~> 0.2) get_process_mem (~> 0.2)
puma (>= 2.7, < 4) puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
rack (1.6.10) rack (1.6.11)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (4.4.1)
...@@ -608,7 +610,7 @@ GEM ...@@ -608,7 +610,7 @@ GEM
httpclient (>= 2.4) httpclient (>= 2.4)
multi_json (>= 1.3.6) multi_json (>= 1.3.6)
rack (>= 1.1) rack (>= 1.1)
rack-protection (2.0.3) rack-protection (2.0.4)
rack rack
rack-proxy (0.6.0) rack-proxy (0.6.0)
rack rack
...@@ -676,7 +678,7 @@ GEM ...@@ -676,7 +678,7 @@ GEM
redis-actionpack (>= 5.0, < 6) redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.4.1) redis-store (1.6.0)
redis (>= 2.2, < 5) redis (>= 2.2, < 5)
regexp_parser (0.5.0) regexp_parser (0.5.0)
representable (3.0.4) representable (3.0.4)
...@@ -802,7 +804,7 @@ GEM ...@@ -802,7 +804,7 @@ GEM
rack rack
shoulda-matchers (3.1.2) shoulda-matchers (3.1.2)
activesupport (>= 4.0.0) activesupport (>= 4.0.0)
sidekiq (5.2.1) sidekiq (5.2.3)
connection_pool (~> 2.2, >= 2.2.2) connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0) rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5) redis (>= 3.3.5, < 5)
...@@ -965,6 +967,7 @@ DEPENDENCIES ...@@ -965,6 +967,7 @@ DEPENDENCIES
devise (~> 4.4) devise (~> 4.4)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
discordrb-webhooks-blackst0ne (~> 3.3)
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.5) doorkeeper-openid_connect (~> 1.5)
ed25519 (~> 1.2) ed25519 (~> 1.2)
...@@ -974,7 +977,7 @@ DEPENDENCIES ...@@ -974,7 +977,7 @@ DEPENDENCIES
factory_bot_rails (~> 4.8.2) factory_bot_rails (~> 4.8.2)
faraday (~> 0.12) faraday (~> 0.12)
fast_blank fast_blank
ffaker (~> 2.4) ffaker (~> 2.10)
flipper (~> 0.13.0) flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0) flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0) flipper-active_support_cache_store (~> 0.13.0)
...@@ -995,7 +998,7 @@ DEPENDENCIES ...@@ -995,7 +998,7 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.123.0) gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
...@@ -1010,7 +1013,7 @@ DEPENDENCIES ...@@ -1010,7 +1013,7 @@ DEPENDENCIES
graphiql-rails (~> 1.4.10) graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0) graphql (~> 1.8.0)
grpc (~> 1.15.0) grpc (~> 1.15.0)
haml_lint (~> 0.26.0) haml_lint (~> 0.28.0)
hamlit (~> 2.8.8) hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5) hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes hashie-forbidden_attributes
......
...@@ -165,6 +165,8 @@ GEM ...@@ -165,6 +165,8 @@ GEM
rotp (~> 2.0) rotp (~> 2.0)
diff-lcs (1.3) diff-lcs (1.3)
diffy (3.1.0) diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20180417) domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
...@@ -203,7 +205,7 @@ GEM ...@@ -203,7 +205,7 @@ GEM
multi_json multi_json
fast_blank (1.0.0) fast_blank (1.0.0)
fast_gettext (1.6.0) fast_gettext (1.6.0)
ffaker (2.4.0) ffaker (2.10.0)
ffi (1.9.25) ffi (1.9.25)
flipper (0.13.0) flipper (0.13.0)
flipper-active_record (0.13.0) flipper-active_record (0.13.0)
...@@ -275,7 +277,7 @@ GEM ...@@ -275,7 +277,7 @@ GEM
gitaly-proto (0.123.0) gitaly-proto (0.123.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-markup (1.6.4) gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.3.0) gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (2.4.1) gitlab-styles (2.4.1)
...@@ -338,11 +340,11 @@ GEM ...@@ -338,11 +340,11 @@ GEM
haml (5.0.4) haml (5.0.4)
temple (>= 0.8.0) temple (>= 0.8.0)
tilt tilt
haml_lint (0.26.0) haml_lint (0.28.0)
haml (>= 4.0, < 5.1) haml (>= 4.0, < 5.1)
rainbow rainbow
rake (>= 10, < 13) rake (>= 10, < 13)
rubocop (>= 0.49.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hamlit (2.8.8) hamlit (2.8.8)
temple (>= 0.8.0) temple (>= 0.8.0)
...@@ -544,7 +546,7 @@ GEM ...@@ -544,7 +546,7 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (1.0.0) os (1.0.0)
parallel (1.12.1) parallel (1.12.1)
parser (2.5.1.2) parser (2.5.3.0)
ast (~> 2.4.0) ast (~> 2.4.0)
parslet (1.8.2) parslet (1.8.2)
peek (1.0.1) peek (1.0.1)
...@@ -612,7 +614,7 @@ GEM ...@@ -612,7 +614,7 @@ GEM
httpclient (>= 2.4) httpclient (>= 2.4)
multi_json (>= 1.3.6) multi_json (>= 1.3.6)
rack (>= 1.1) rack (>= 1.1)
rack-protection (2.0.3) rack-protection (2.0.4)
rack rack
rack-proxy (0.6.0) rack-proxy (0.6.0)
rack rack
...@@ -685,7 +687,7 @@ GEM ...@@ -685,7 +687,7 @@ GEM
redis-actionpack (>= 5.0, < 6) redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.4.1) redis-store (1.6.0)
redis (>= 2.2, < 5) redis (>= 2.2, < 5)
regexp_parser (0.5.0) regexp_parser (0.5.0)
representable (3.0.4) representable (3.0.4)
...@@ -810,7 +812,7 @@ GEM ...@@ -810,7 +812,7 @@ GEM
rack rack
shoulda-matchers (3.1.2) shoulda-matchers (3.1.2)
activesupport (>= 4.0.0) activesupport (>= 4.0.0)
sidekiq (5.2.1) sidekiq (5.2.3)
connection_pool (~> 2.2, >= 2.2.2) connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0) rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5) redis (>= 3.3.5, < 5)
...@@ -974,6 +976,7 @@ DEPENDENCIES ...@@ -974,6 +976,7 @@ DEPENDENCIES
devise (~> 4.4) devise (~> 4.4)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
discordrb-webhooks-blackst0ne (~> 3.3)
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.5) doorkeeper-openid_connect (~> 1.5)
ed25519 (~> 1.2) ed25519 (~> 1.2)
...@@ -983,7 +986,7 @@ DEPENDENCIES ...@@ -983,7 +986,7 @@ DEPENDENCIES
factory_bot_rails (~> 4.8.2) factory_bot_rails (~> 4.8.2)
faraday (~> 0.12) faraday (~> 0.12)
fast_blank fast_blank
ffaker (~> 2.4) ffaker (~> 2.10)
flipper (~> 0.13.0) flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0) flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0) flipper-active_support_cache_store (~> 0.13.0)
...@@ -1004,7 +1007,7 @@ DEPENDENCIES ...@@ -1004,7 +1007,7 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.123.0) gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
...@@ -1019,7 +1022,7 @@ DEPENDENCIES ...@@ -1019,7 +1022,7 @@ DEPENDENCIES
graphiql-rails (~> 1.4.10) graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0) graphql (~> 1.8.0)
grpc (~> 1.15.0) grpc (~> 1.15.0)
haml_lint (~> 0.26.0) haml_lint (~> 0.28.0)
hamlit (~> 2.8.8) hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5) hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes hashie-forbidden_attributes
......
...@@ -128,6 +128,7 @@ export default { ...@@ -128,6 +128,7 @@ export default {
eventHub.$once('fetchedNotesData', this.setDiscussions); eventHub.$once('fetchedNotesData', this.setDiscussions);
}, },
methods: { methods: {
...mapActions(['startTaskList']),
...mapActions('diffs', [ ...mapActions('diffs', [
'setBaseConfig', 'setBaseConfig',
'fetchDiffFiles', 'fetchDiffFiles',
...@@ -157,7 +158,13 @@ export default { ...@@ -157,7 +158,13 @@ export default {
if (this.isNotesFetched && !this.assignedDiscussions && !this.isLoading) { if (this.isNotesFetched && !this.assignedDiscussions && !this.isLoading) {
this.assignedDiscussions = true; this.assignedDiscussions = true;
requestIdleCallback(() => this.assignDiscussionsToDiff(), { timeout: 1000 }); requestIdleCallback(
() =>
this.assignDiscussionsToDiff()
.then(this.$nextTick)
.then(this.startTaskList),
{ timeout: 1000 },
);
} }
}, },
adjustView() { adjustView() {
......
...@@ -35,7 +35,7 @@ export default { ...@@ -35,7 +35,7 @@ export default {
if (search === '') return this.renderTreeList ? this.tree : this.allBlobs; 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() { rowDisplayTextKey() {
if (this.renderTreeList && this.search.trim() === '') { if (this.renderTreeList && this.search.trim() === '') {
......
...@@ -190,6 +190,7 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => { ...@@ -190,6 +190,7 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => {
.then(result => dispatch('updateDiscussion', result.discussion, { root: true })) .then(result => dispatch('updateDiscussion', result.discussion, { root: true }))
.then(discussion => dispatch('assignDiscussionsToDiff', [discussion])) .then(discussion => dispatch('assignDiscussionsToDiff', [discussion]))
.then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.fileHash)) .then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.fileHash))
.then(() => dispatch('startTaskList', null, { root: true }))
.catch(() => createFlash(s__('MergeRequests|Saving the comment failed'))); .catch(() => createFlash(s__('MergeRequests|Saving the comment failed')));
}; };
......
...@@ -65,7 +65,13 @@ export default { ...@@ -65,7 +65,13 @@ export default {
const { highlightedDiffLines, parallelDiffLines } = diffFile; const { highlightedDiffLines, parallelDiffLines } = diffFile;
removeMatchLine(diffFile, lineNumbers, bottom); 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({ addContextLines({
inlineLines: highlightedDiffLines, inlineLines: highlightedDiffLines,
parallelLines: parallelDiffLines, parallelLines: parallelDiffLines,
......
...@@ -80,8 +80,8 @@ export const fetchJob = ({ state, dispatch }) => { ...@@ -80,8 +80,8 @@ export const fetchJob = ({ state, dispatch }) => {
export const receiveJobSuccess = ({ commit }, data = {}) => { export const receiveJobSuccess = ({ commit }, data = {}) => {
commit(types.RECEIVE_JOB_SUCCESS, data); commit(types.RECEIVE_JOB_SUCCESS, data);
if (data.favicon) { if (data.status && data.status.favicon) {
setFaviconOverlay(data.favicon); setFaviconOverlay(data.status.favicon);
} else { } else {
resetFavicon(); resetFavicon();
} }
......
...@@ -6,7 +6,6 @@ import Autosize from 'autosize'; ...@@ -6,7 +6,6 @@ import Autosize from 'autosize';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import Flash from '../../flash'; import Flash from '../../flash';
import Autosave from '../../autosave'; import Autosave from '../../autosave';
import TaskList from '../../task_list';
import { import {
capitalizeFirstCharacter, capitalizeFirstCharacter,
convertToCamelCase, convertToCamelCase,
...@@ -146,7 +145,6 @@ export default { ...@@ -146,7 +145,6 @@ export default {
}); });
this.initAutoSave(); this.initAutoSave();
this.initTaskList();
}, },
methods: { methods: {
...mapActions([ ...mapActions([
...@@ -298,13 +296,6 @@ Please check your network connection and try again.`; ...@@ -298,13 +296,6 @@ Please check your network connection and try again.`;
]); ]);
} }
}, },
initTaskList() {
return new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
},
resizeTextarea() { resizeTextarea() {
this.$nextTick(() => { this.$nextTick(() => {
Autosize.update(this.$refs.textarea); Autosize.update(this.$refs.textarea);
......
...@@ -4,7 +4,6 @@ import noteEditedText from './note_edited_text.vue'; ...@@ -4,7 +4,6 @@ import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue'; import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue'; import noteAttachment from './note_attachment.vue';
import noteForm from './note_form.vue'; import noteForm from './note_form.vue';
import TaskList from '../../task_list';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
export default { export default {
...@@ -37,14 +36,12 @@ export default { ...@@ -37,14 +36,12 @@ export default {
}, },
mounted() { mounted() {
this.renderGFM(); this.renderGFM();
this.initTaskList();
if (this.isEditing) { if (this.isEditing) {
this.initAutoSave(this.note); this.initAutoSave(this.note);
} }
}, },
updated() { updated() {
this.initTaskList();
this.renderGFM(); this.renderGFM();
if (this.isEditing) { if (this.isEditing) {
...@@ -59,15 +56,6 @@ export default { ...@@ -59,15 +56,6 @@ export default {
renderGFM() { renderGFM() {
$(this.$refs['note-body']).renderGFM(); $(this.$refs['note-body']).renderGFM();
}, },
initTaskList() {
if (this.canEdit) {
this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
}
},
handleFormUpdate(note, parentElement, callback) { handleFormUpdate(note, parentElement, callback) {
this.$emit('handleFormUpdate', note, parentElement, callback); this.$emit('handleFormUpdate', note, parentElement, callback);
}, },
......
...@@ -46,6 +46,7 @@ export default { ...@@ -46,6 +46,7 @@ export default {
'is-requesting being-posted': this.isRequesting, 'is-requesting being-posted': this.isRequesting,
'disabled-content': this.isDeleting, 'disabled-content': this.isDeleting,
target: this.isTarget, target: this.isTarget,
'is-editable': this.note.current_user.can_edit,
}; };
}, },
canResolve() { canResolve() {
......
...@@ -122,6 +122,7 @@ export default { ...@@ -122,6 +122,7 @@ export default {
setTargetNoteHash: 'setTargetNoteHash', setTargetNoteHash: 'setTargetNoteHash',
toggleDiscussion: 'toggleDiscussion', toggleDiscussion: 'toggleDiscussion',
setNotesFetchedState: 'setNotesFetchedState', setNotesFetchedState: 'setNotesFetchedState',
startTaskList: 'startTaskList',
}), }),
getComponentName(discussion) { getComponentName(discussion) {
if (discussion.isSkeletonNote) { if (discussion.isSkeletonNote) {
...@@ -157,6 +158,7 @@ export default { ...@@ -157,6 +158,7 @@ export default {
this.isFetching = false; this.isFetching = false;
}) })
.then(() => this.$nextTick()) .then(() => this.$nextTick())
.then(() => this.startTaskList())
.then(() => this.checkLocationHash()) .then(() => this.checkLocationHash())
.catch(() => { .catch(() => {
this.setLoadingState(false); this.setLoadingState(false);
......
import Vue from 'vue';
import $ from 'jquery'; import $ from 'jquery';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import TaskList from '../../task_list';
import Flash from '../../flash'; import Flash from '../../flash';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import * as types from './mutation_types'; import * as types from './mutation_types';
...@@ -58,12 +60,13 @@ export const deleteNote = ({ commit, dispatch }, note) => ...@@ -58,12 +60,13 @@ export const deleteNote = ({ commit, dispatch }, note) =>
dispatch('updateMergeRequestWidget'); dispatch('updateMergeRequestWidget');
}); });
export const updateNote = ({ commit }, { endpoint, note }) => export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
service service
.updateNote(endpoint, note) .updateNote(endpoint, note)
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
commit(types.UPDATE_NOTE, res); commit(types.UPDATE_NOTE, res);
dispatch('startTaskList');
}); });
export const replyToDiscussion = ({ commit }, { endpoint, data }) => export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
...@@ -85,6 +88,7 @@ export const createNewNote = ({ commit, dispatch }, { endpoint, data }) => ...@@ -85,6 +88,7 @@ export const createNewNote = ({ commit, dispatch }, { endpoint, data }) =>
commit(types.ADD_NEW_NOTE, res); commit(types.ADD_NEW_NOTE, res);
dispatch('updateMergeRequestWidget'); dispatch('updateMergeRequestWidget');
dispatch('startTaskList');
} }
return res; return res;
}); });
...@@ -260,6 +264,8 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => { ...@@ -260,6 +264,8 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
commit(types.ADD_NEW_NOTE, note); commit(types.ADD_NEW_NOTE, note);
} }
}); });
dispatch('startTaskList');
} }
commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at); commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
...@@ -368,5 +374,16 @@ export const setCommentsDisabled = ({ commit }, data) => { ...@@ -368,5 +374,16 @@ export const setCommentsDisabled = ({ commit }, data) => {
commit(types.DISABLE_COMMENTS, 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 // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -40,10 +40,8 @@ export default { ...@@ -40,10 +40,8 @@ export default {
failed: __('Failed to deploy to'), failed: __('Failed to deploy to'),
}, },
data() { data() {
const features = window.gon.features || {};
return { return {
isStopping: false, isStopping: false,
enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges,
}; };
}, },
computed: { computed: {
...@@ -74,10 +72,7 @@ export default { ...@@ -74,10 +72,7 @@ export default {
: ''; : '';
}, },
shouldRenderDropdown() { shouldRenderDropdown() {
return ( return this.deployment.changes && this.deployment.changes.length > 0;
this.enableCiEnvironmentsStatusChanges &&
(this.deployment.changes && this.deployment.changes.length > 0)
);
}, },
}, },
methods: { methods: {
......
$system-note-icon-size: 32px; $system-note-icon-size: 32px;
$system-note-svg-size: 16px; $system-note-svg-size: 16px;
$note-form-margin-left: 70px; $note-form-margin-left: 72px;
@mixin vertical-line($left) { @mixin vertical-line($left) {
&::before { &::before {
...@@ -54,7 +54,7 @@ $note-form-margin-left: 70px; ...@@ -54,7 +54,7 @@ $note-form-margin-left: 70px;
} }
.main-notes-list { .main-notes-list {
@include vertical-line(39px); @include vertical-line(36px);
} }
.notes { .notes {
...@@ -268,7 +268,7 @@ $note-form-margin-left: 70px; ...@@ -268,7 +268,7 @@ $note-form-margin-left: 70px;
} }
.system-note { .system-note {
padding: 6px $gl-padding-24; padding: 6px 20px;
margin: $gl-padding-24 0; margin: $gl-padding-24 0;
background-color: transparent; background-color: transparent;
......
...@@ -122,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -122,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController
@lines.map! do |line| @lines.map! do |line|
# These are marked as context lines but are loaded from blobs. # These are marked as context lines but are loaded from blobs.
# We also have context lines loaded from diffs in other places. # 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.rich_text = line
diff_line diff_line
end end
...@@ -132,6 +132,11 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -132,6 +132,11 @@ class Projects::BlobController < Projects::ApplicationController
render json: DiffLineSerializer.new.represent(@lines) render json: DiffLineSerializer.new.represent(@lines)
end 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 def add_match_line
return unless @form.unfold? return unless @form.unfold?
......
...@@ -22,6 +22,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -22,6 +22,12 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def render_diffs def render_diffs
@environment = @merge_request.environments_for(current_user).last @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 @diffs.write_cache
...@@ -108,4 +114,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -108,4 +114,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs) @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) @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes), @merge_request)
end end
def renderable_notes
define_diff_comment_vars unless @notes
@notes
end
end end
...@@ -14,9 +14,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -14,9 +14,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase] 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 def index
@merge_requests = @issuables @merge_requests = @issuables
......
...@@ -83,7 +83,7 @@ module ImportHelper ...@@ -83,7 +83,7 @@ module ImportHelper
private private
def github_project_url(full_path) 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 end
def github_root_url def github_root_url
...@@ -95,6 +95,6 @@ module ImportHelper ...@@ -95,6 +95,6 @@ module ImportHelper
end end
def gitea_project_url(full_path) 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
end end
...@@ -22,6 +22,8 @@ module Clusters ...@@ -22,6 +22,8 @@ module Clusters
key: Settings.attr_encrypted_db_key_base_truncated, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) }
def token_name def token_name
"#{namespace}-token" "#{namespace}-token"
end end
......
...@@ -83,7 +83,7 @@ module Clusters ...@@ -83,7 +83,7 @@ module Clusters
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true) .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end 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) variables.concat(kubernetes_namespace.predefined_variables)
else else
# From 11.5, every Clusters::Project should have at least one # From 11.5, every Clusters::Project should have at least one
......
...@@ -13,17 +13,14 @@ module Deployable ...@@ -13,17 +13,14 @@ module Deployable
name: expanded_environment_name name: expanded_environment_name
) )
environment.deployments.create!( create_deployment!(
project_id: environment.project_id, project_id: environment.project_id,
environment: environment, environment: environment,
ref: ref, ref: ref,
tag: tag, tag: tag,
sha: sha, sha: sha,
user: user, user: user,
deployable: self, on_stop: on_stop)
on_stop: on_stop).tap do |_|
self.reload # Reload relationships
end
end end
end end
end end
...@@ -66,6 +66,10 @@ class DiffNote < Note ...@@ -66,6 +66,10 @@ class DiffNote < Note
self.original_position.diff_refs == diff_refs self.original_position.diff_refs == diff_refs
end end
def discussion_first_note?
self == discussion.first_note
end
private private
def enqueue_diff_file_creation_job def enqueue_diff_file_creation_job
...@@ -78,26 +82,33 @@ class DiffNote < Note ...@@ -78,26 +82,33 @@ class DiffNote < Note
end end
def should_create_diff_file? 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 end
def fetch_diff_file def fetch_diff_file
if note_diff_file file =
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash) if note_diff_file
Gitlab::Diff::File.new(diff, diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
repository: project.repository, Gitlab::Diff::File.new(diff,
diff_refs: original_position.diff_refs) repository: project.repository,
elsif created_at_diff?(noteable.diff_refs) diff_refs: original_position.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're elsif created_at_diff?(noteable.diff_refs)
# presenting a "current version" of the MR discussion diff. # We're able to use the already persisted diffs (Postgres) if we're
# So no need to make an extra Gitaly diff request for it. # presenting a "current version" of the MR discussion diff.
# As an extra benefit, the returned `diff_file` already # So no need to make an extra Gitaly diff request for it.
# has `highlighted_diff_lines` data set from Redis on # As an extra benefit, the returned `diff_file` already
# `Diff::FileCollection::MergeRequestDiff`. # has `highlighted_diff_lines` data set from Redis on
noteable.diffs(original_position.diff_options).diff_files.first # `Diff::FileCollection::MergeRequestDiff`.
else noteable.diffs(original_position.diff_options).diff_files.first
original_position.diff_file(self.project.repository) else
end 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 end
def supported? def supported?
......
...@@ -142,7 +142,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -142,7 +142,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def commits_by_shas(shas) def commits_by_shas(shas)
return [] unless shas.present? return MergeRequestDiffCommit.none unless shas.present?
merge_request_diff_commits.where(sha: shas) merge_request_diff_commits.where(sha: shas)
end end
......
...@@ -135,6 +135,7 @@ class Project < ActiveRecord::Base ...@@ -135,6 +135,7 @@ class Project < ActiveRecord::Base
# Project services # Project services
has_one :campfire_service has_one :campfire_service
has_one :discord_service
has_one :drone_ci_service has_one :drone_ci_service
has_one :emails_on_push_service has_one :emails_on_push_service
has_one :pipelines_email_service has_one :pipelines_email_service
......
...@@ -86,14 +86,16 @@ class BambooService < CiService ...@@ -86,14 +86,16 @@ class BambooService < CiService
end end
def read_build_page(response) def read_build_page(response)
if response.code != 200 || response.dig('results', 'results', 'size') == '0' key =
# If actual build link can't be determined, send user to build summary page. if response.code != 200 || response.dig('results', 'results', 'size') == '0'
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s # If actual build link can't be determined, send user to build summary page.
else build_key
# If actual build link is available, go to build result page. else
result_key = response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key') # If actual build link is available, go to build result page.
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key')
end end
build_url("browse/#{key}")
end end
def read_commit_status(response) def read_commit_status(response)
...@@ -117,7 +119,7 @@ class BambooService < CiService ...@@ -117,7 +119,7 @@ class BambooService < CiService
end end
def build_url(path) def build_url(path)
URI.join("#{bamboo_url}/", path).to_s Gitlab::Utils.append_path(bamboo_url, path)
end end
def get_path(path, query_params = {}) 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 ...@@ -39,11 +39,9 @@ class DroneCiService < CiService
end end
def commit_status_path(sha, ref) def commit_status_path(sha, ref)
url = [drone_url, Gitlab::Utils.append_path(
"gitlab/#{project.full_path}/commits/#{sha}", drone_url,
"?branch=#{URI.encode(ref.to_s)}&access_token=#{token}"] "gitlab/#{project.full_path}/commits/#{sha}?branch=#{URI.encode(ref.to_s)}&access_token=#{token}")
URI.join(*url).to_s
end end
def commit_status(sha, ref) def commit_status(sha, ref)
...@@ -74,11 +72,9 @@ class DroneCiService < CiService ...@@ -74,11 +72,9 @@ class DroneCiService < CiService
end end
def build_page(sha, ref) def build_page(sha, ref)
url = [drone_url, Gitlab::Utils.append_path(
"gitlab/#{project.full_path}/redirect/commits/#{sha}", drone_url,
"?branch=#{URI.encode(ref.to_s)}"] "gitlab/#{project.full_path}/redirect/commits/#{sha}?branch=#{URI.encode(ref.to_s)}")
URI.join(*url).to_s
end end
def title def title
......
...@@ -54,7 +54,7 @@ class JiraService < IssueTrackerService ...@@ -54,7 +54,7 @@ class JiraService < IssueTrackerService
{ {
username: self.username, username: self.username,
password: self.password, 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('/'), context_path: url.path.chomp('/'),
auth_type: :basic, auth_type: :basic,
read_timeout: 120, read_timeout: 120,
......
...@@ -34,10 +34,9 @@ class MockCiService < CiService ...@@ -34,10 +34,9 @@ class MockCiService < CiService
# http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
# #
def build_page(sha, ref) def build_page(sha, ref)
url = [mock_service_url, Gitlab::Utils.append_path(
"#{project.namespace.path}/#{project.path}/status/#{sha}"] mock_service_url,
"#{project.namespace.path}/#{project.path}/status/#{sha}")
URI.join(*url).to_s
end end
# Return string with build status or :error symbol # Return string with build status or :error symbol
...@@ -61,10 +60,9 @@ class MockCiService < CiService ...@@ -61,10 +60,9 @@ class MockCiService < CiService
end end
def commit_status_path(sha) def commit_status_path(sha)
url = [mock_service_url, Gitlab::Utils.append_path(
"#{project.namespace.path}/#{project.path}/status/#{sha}.json"] mock_service_url,
"#{project.namespace.path}/#{project.path}/status/#{sha}.json")
URI.join(*url).to_s
end end
def read_commit_status(response) def read_commit_status(response)
......
...@@ -132,7 +132,7 @@ class TeamcityService < CiService ...@@ -132,7 +132,7 @@ class TeamcityService < CiService
end end
def build_url(path) def build_url(path)
URI.join("#{teamcity_url}/", path).to_s Gitlab::Utils.append_path(teamcity_url, path)
end end
def get_path(path) def get_path(path)
......
...@@ -253,6 +253,7 @@ class Service < ActiveRecord::Base ...@@ -253,6 +253,7 @@ class Service < ActiveRecord::Base
bugzilla bugzilla
campfire campfire
custom_issue_tracker custom_issue_tracker
discord
drone_ci drone_ci
emails_on_push emails_on_push
external_wiki external_wiki
......
...@@ -37,7 +37,7 @@ class EnvironmentStatusEntity < Grape::Entity ...@@ -37,7 +37,7 @@ class EnvironmentStatusEntity < Grape::Entity
es.deployment.try(:formatted_deployment_time) es.deployment.try(:formatted_deployment_time)
end end
expose :changes, if: ->(*) { Feature.enabled?(:ci_environments_status_changes, project) } expose :changes
private 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 # frozen_string_literal: true
module Issues module Issues
class MoveService < Issues::BaseService class MoveService < Issuable::Clone::BaseService
MoveError = Class.new(StandardError) MoveError = Class.new(StandardError)
def execute(issue, new_project) def execute(issue, target_project)
@old_issue = issue @target_project = target_project
@old_project = @project
@new_project = new_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!' raise MoveError, 'Cannot move issue due to insufficient permissions!'
end end
if @project == new_project if @project == @target_project
raise MoveError, 'Cannot move issue to project it originates from!' raise MoveError, 'Cannot move issue to project it originates from!'
end end
# Using transaction because of a high resources footprint super
# on rewriting notes (unfolding references)
#
ActiveRecord::Base.transaction do
@new_issue = create_new_issue
update_new_issue
update_old_issue
end
notify_participants notify_participants
@new_issue new_entity
end end
private private
def update_new_issue def update_old_entity
rewrite_notes super
copy_resource_label_events
rewrite_issue_award_emoji
add_note_moved_from
end
def update_old_issue
add_note_moved_to
close_issue
mark_as_moved mark_as_moved
end end
def create_new_issue def create_new_entity
new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids, new_params = {
milestone_id: cloneable_milestone_id, id: nil,
project: @new_project, author: @old_issue.author, iid: nil,
description: rewrite_content(@old_issue.description), project: @target_project,
assignee_ids: @old_issue.assignee_ids } author: original_entity.author,
assignee_ids: original_entity.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
}
LabelsFinder.new(current_user, params).execute.pluck(:id) new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
CreateService.new(@target_project, @current_user, new_params).execute
end 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 def mark_as_moved
milestones.first&.id original_entity.update(moved_to: new_entity)
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])
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
end end
def close_issue def notify_participants
close_service = CloseService.new(@old_project, @current_user) notification_service.async.issue_moved(original_entity, new_entity, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false)
end end
def add_note_moved_from def add_note_from
SystemNoteService.noteable_moved(@new_issue, @new_project, SystemNoteService.noteable_moved(new_entity, @target_project,
@old_issue, @current_user, original_entity, current_user,
direction: :from) direction: :from)
end end
def add_note_moved_to def add_note_to
SystemNoteService.noteable_moved(@old_issue, @old_project, SystemNoteService.noteable_moved(original_entity, old_project,
@new_issue, @current_user, new_entity, current_user,
direction: :to) direction: :to)
end 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
end end
...@@ -29,10 +29,6 @@ module MergeRequests ...@@ -29,10 +29,6 @@ module MergeRequests
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def clear_cache(new_diff) 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 # 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 # model, as that will interfere with other actions happening when
# reloading the diff. # 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 # frozen_string_literal: true
module Notes module Notes
class CreateService < ::BaseService class CreateService < ::Notes::BaseService
def execute def execute
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
...@@ -35,6 +35,7 @@ module Notes ...@@ -35,6 +35,7 @@ module Notes
if !only_commands && note.save if !only_commands && note.save
todo_service.new_note(note, current_user) todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
end end
if command_params.present? if command_params.present?
......
# frozen_string_literal: true # frozen_string_literal: true
module Notes module Notes
class DestroyService < BaseService class DestroyService < ::Notes::BaseService
def execute(note) def execute(note)
TodoService.new.destroy_target(note) do |note| TodoService.new.destroy_target(note) do |note|
note.destroy note.destroy
end end
clear_noteable_diffs_cache(note)
end end
end end
end end
...@@ -149,9 +149,9 @@ class FileUploader < GitlabUploader ...@@ -149,9 +149,9 @@ class FileUploader < GitlabUploader
# return a new uploader with a file copy on another project # return a new uploader with a file copy on another project
def self.copy_to(uploader, to_project) def self.copy_to(uploader, to_project)
moved = uploader.dup.tap do |u| moved = self.new(to_project)
u.model = to_project moved.object_store = uploader.object_store
end moved.filename = uploader.filename
moved.copy_file(uploader.file) moved.copy_file(uploader.file)
moved moved
......
...@@ -34,10 +34,13 @@ ...@@ -34,10 +34,13 @@
.clearfix .clearfix
%p %p
%i.fa.fa-exclamation-circle %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 %p
%i.fa.fa-exclamation-circle %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. 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 @@ ...@@ -165,7 +165,7 @@
.input-group .input-group
.input-group-prepend .input-group-prepend
.input-group-text .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' = f.text_field :path, class: 'form-control'
%ul %ul
%li Be careful. Renaming a project's repository can have unintended side effects. %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: ...@@ -24,9 +24,32 @@ The following files require a review from the Documentation team:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")} * #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
To make sure these changes are reviewed, mention `@gl-docsteam` in a separate When your content is ready for review, mention a technical writer in a separate
comment, and explain what needs to be reviewed by the team. Please don't mention comment and explain what needs to be reviewed.
the team until your changes are ready for review.
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 MARKDOWN
unless gitlab.mr_labels.include?('Documentation') unless gitlab.mr_labels.include?('Documentation')
......
...@@ -4,9 +4,7 @@ description: "Set and configure Git protocol v2" ...@@ -4,9 +4,7 @@ description: "Set and configure Git protocol v2"
# Configuring 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 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, enabled by default in GitLab for HTTP requests. In order to enable SSH,
......
...@@ -10,7 +10,7 @@ Asana - Teamwork without email ...@@ -10,7 +10,7 @@ Asana - Teamwork without email
Set Asana service for a project. 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 PUT /projects/:id/services/asana
...@@ -92,7 +92,7 @@ Parameters: ...@@ -92,7 +92,7 @@ Parameters:
| Parameter | Type | Required | Description | | 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 | | `build_key` | string | true | Bamboo build plan key like KEY |
| `username` | string | true | A user with API access, if applicable | | `username` | string | true | A user with API access, if applicable |
| `password` | string | true | Password of the user | | `password` | string | true | Password of the user |
...@@ -117,7 +117,7 @@ GET /projects/:id/services/bamboo ...@@ -117,7 +117,7 @@ GET /projects/:id/services/bamboo
Bugzilla Issue Tracker Bugzilla Issue Tracker
### Create/Edit Buildkite service ### Create/Edit Bugzilla service
Set Bugzilla service for a project. Set Bugzilla service for a project.
...@@ -168,7 +168,7 @@ Parameters: ...@@ -168,7 +168,7 @@ Parameters:
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `token` | string | true | Buildkite project GitLab token | | `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 | | `enable_ssl_verification` | boolean | false | Enable SSL verification |
### Delete Buildkite service ### Delete Buildkite service
...@@ -278,7 +278,7 @@ Parameters: ...@@ -278,7 +278,7 @@ Parameters:
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `token` | string | true | Drone CI project specific token | | `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 | | `enable_ssl_verification` | boolean | false | Enable SSL verification |
### Delete Drone CI service ### Delete Drone CI service
...@@ -421,7 +421,7 @@ Parameters: ...@@ -421,7 +421,7 @@ Parameters:
| Parameter | Type | Required | Description | | 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_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
| `notify_only_default_branch` | boolean | false | Send notifications only for the default branch | | `notify_only_default_branch` | boolean | false | Send notifications only for the default branch |
| `push_events` | boolean | false | Enable notifications for push events | | `push_events` | boolean | false | Enable notifications for push events |
...@@ -470,7 +470,7 @@ Parameters: ...@@ -470,7 +470,7 @@ Parameters:
| `notify` | boolean | false | Enable notifications | | `notify` | boolean | false | Enable notifications |
| `room` | string | false |Room name or ID | | `room` | string | false |Room name or ID |
| `api_version` | string | false | Leave blank for default (v2) | | `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 ### Delete HipChat service
...@@ -496,7 +496,7 @@ Send IRC messages, on update, to a list of recipients through an Irker gateway. ...@@ -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. 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 PUT /projects/:id/services/irker
...@@ -546,7 +546,7 @@ Set JIRA service for a project. ...@@ -546,7 +546,7 @@ Set JIRA service for a project.
> **Notes:** > **Notes:**
> - Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and > - Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
> `project_url` are replaced by `project_key`, `url`. If you are using an > `project_url` are replaced by `project_key`, `url`. If you are using an
> older version, [follow this documentation][old-jira-api]. > older version, [follow this documentation][old-jira-api].
``` ```
...@@ -557,7 +557,7 @@ Parameters: ...@@ -557,7 +557,7 @@ Parameters:
| Parameter | Type | Required | Description | | 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`. | | `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. | | `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. | | `password` | string | yes | The password of the user created to be used with GitLab/JIRA. |
...@@ -589,7 +589,7 @@ PUT /projects/:id/services/kubernetes ...@@ -589,7 +589,7 @@ PUT /projects/:id/services/kubernetes
Parameters: Parameters:
- `namespace` (**required**) - The Kubernetes namespace to use - `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 - `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) - `ca_pem` (optional) - A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)
...@@ -658,7 +658,6 @@ Parameters: ...@@ -658,7 +658,6 @@ Parameters:
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `token` | string | yes | The Slack token | | `token` | string | yes | The Slack token |
### Delete Slack slash command service ### Delete Slack slash command service
Delete Slack slash command service for a project. Delete Slack slash command service for a project.
...@@ -823,7 +822,7 @@ Parameters: ...@@ -823,7 +822,7 @@ Parameters:
| Parameter | Type | Required | Description | | 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 ### Delete Prometheus service
...@@ -934,7 +933,7 @@ Parameters: ...@@ -934,7 +933,7 @@ Parameters:
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `webhook` | string | true | https://hooks.slack.com/services/... | | `webhook` | string | true | `https://hooks.slack.com/services/...` |
| `username` | string | false | username | | `username` | string | false | username |
| `channel` | string | false | Default channel to use if others are not configured | | `channel` | string | false | Default channel to use if others are not configured |
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines | | `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
...@@ -988,7 +987,7 @@ Parameters: ...@@ -988,7 +987,7 @@ Parameters:
| Parameter | Type | Required | Description | | 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 ### Delete Microsoft Teams service
...@@ -1024,7 +1023,7 @@ Parameters: ...@@ -1024,7 +1023,7 @@ Parameters:
| Parameter | Type | Required | Description | | 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 | | `username` | string | false | username |
| `channel` | string | false | Default channel to use if others are not configured | | `channel` | string | false | Default channel to use if others are not configured |
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines | | `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
...@@ -1080,7 +1079,7 @@ Parameters: ...@@ -1080,7 +1079,7 @@ Parameters:
| Parameter | Type | Required | Description | | 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 | | `build_type` | string | true | Build configuration ID |
| `username` | string | true | A user with permissions to trigger a manual build | | `username` | string | true | A user with permissions to trigger a manual build |
| `password` | string | true | The password of the user | | `password` | string | true | The password of the user |
...@@ -1104,7 +1103,6 @@ GET /projects/:id/services/teamcity ...@@ -1104,7 +1103,6 @@ GET /projects/:id/services/teamcity
[jira-doc]: ../user/project/integrations/jira.md [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 [old-jira-api]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/api/services.md#jira
## MockCI ## 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. 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: ...@@ -1123,7 +1121,7 @@ Parameters:
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `mock_service_url` | string | true | http://localhost:4004 | | `mock_service_url` | string | true | `http://localhost:4004` |
### Delete MockCI service ### 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 End-to-end testing is a strategy used to check whether your application works
as expected across entire software stack and architecture, including as expected across the entire software stack and architecture, including
integration of all microservices and components that are supposed to work integration of all micro-services and components that are supposed to work
together. together.
## How do we test GitLab? ## How do we test GitLab?
We use [Omnibus GitLab][omnibus-gitlab] to build GitLab packages and then we 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 test these packages using the [GitLab QA orchestrator][gitlab-qa] tool, which is
black-box, click-driven testing framework. a black-box testing framework for the API and the UI.
### Testing nightly builds ### Testing nightly builds
We run scheduled pipeline each night to test nightly builds created by Omnibus. 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 ### Testing code in merge requests
It is possible to run end-to-end tests (eventually being run within a 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 [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 the `package-and-qa` manual action in the `test` stage, that should be present
widget. 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 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. 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. ...@@ -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 Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines. 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. requests. This starts a chain of pipelines in multiple projects.
1. The script being executed triggers a pipeline in GitLab Omnibus and waits 1. The script being executed triggers a pipeline in [Omnibus GitLab][omnibus-gitlab]
for the resulting status. We call this a _status attribution_. 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 1. GitLab packages are being built in the [Omnibus GitLab][omnibus-gitlab]
pushed to Container Registry. pipeline. Packages are then pushed to its Container Registry.
1. When packages are ready, and available in the registry, a final step in the 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 [Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new
QA project. It also waits for a resulting status. [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 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` against a test environment that has been just orchestrated by the `gitlab-qa`
tool. tool.
1. The result of the GitLab QA pipeline is being propagated upstream, through 1. The result of the [GitLab QA pipeline][gitlab-qa-pipelines] is being
Omnibus, back to CE / EE merge request. propagated upstream, through Omnibus, back to the CE / EE merge request.
#### How do I write tests? #### How do I write tests?
In order to write new tests, you first need to learn more about GitLab QA 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 architecture. See the [documentation about it][gitlab-qa-architecture].
GitLab QA project.
Once you decided where to put test environment orchestration scenarios and Once you decided where to put [test environment orchestration scenarios] and
instance specs, take a look at the [relevant documentation][instance-qa-readme] [instance-level scenarios], take a look at the [GitLab QA README][instance-qa-readme],
and examples in [the `qa/` directory][instance-qa-examples]. the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing
instance-level scenarios][instance-level scenarios].
## Where can I ask for help? ## Where can I ask for help?
You can ask question in the `#quality` channel on Slack (GitLab internal) or 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 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 [omnibus-gitlab]: https://gitlab.com/gitlab-org/omnibus-gitlab
[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa [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 [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-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-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 [instance-qa-examples]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa
# Smoke Tests # Smoke Tests
It is imperative in any testing suite that we have Smoke Tests. In short, smoke tests are will run quick sanity It is imperative in any testing suite that we have Smoke Tests. In short, smoke
end-to-end functional tests from GitLab QA and are designed to run against the specified environment to ensure that tests will run quick sanity end-to-end functional tests from GitLab QA and are
basic functionality is working. designed to run against the specified environment to ensure that basic
functionality is working.
Currently, our suite consists of this basic functionality coverage: Currently, our suite consists of this basic functionality coverage:
...@@ -11,6 +12,8 @@ Currently, our suite consists of this basic functionality coverage: ...@@ -11,6 +12,8 @@ Currently, our suite consists of this basic functionality coverage:
- Issue Creation - Issue Creation
- Merge Request Creation - Merge Request Creation
Smoke tests have the `:smoke` RSpec metadata.
--- ---
[Return to Testing documentation](index.md) [Return to Testing documentation](index.md)
...@@ -34,7 +34,11 @@ records should use stubs/doubles as much as possible. ...@@ -34,7 +34,11 @@ records should use stubs/doubles as much as possible.
Formal definition: https://en.wikipedia.org/wiki/Integration_testing 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 | | 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 ...@@ -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 JavaScript method), or integration tests (e.g. test a component that is composed
of multiple components). 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 - https://en.wikipedia.org/wiki/System_testing
of view (aka black-box testing). These tests should test a happy path for a - https://en.wikipedia.org/wiki/White-box_testing
given page or set of pages, and a test case should be added for any regression
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 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 regression is found, regression tests should be added at the lowest-level
possible). possible).
| Tests path | Testing engine | Notes | | 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! ### Consider **not** writing a system test!
...@@ -89,7 +113,7 @@ we have enough Unit & Integration tests), we shouldn't need to duplicate their ...@@ -89,7 +113,7 @@ we have enough Unit & Integration tests), we shouldn't need to duplicate their
thorough testing at the System test level. 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 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: 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: ...@@ -107,29 +131,33 @@ The reasons why we should follow these best practices are as follows:
[Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist [Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist
[RackTest]: https://github.com/teamcapybara/capybara#racktest [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], GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse],
[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces [Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces
are configured and packaged by [GitLab Omnibus]. are configured and packaged by [GitLab Omnibus].
[GitLab QA] is a tool that allows to test that all these pieces integrate well The QA framework and instance-level scenarios are [part of GitLab Rails] so that
together by building a Docker image for a given version of GitLab Rails and they're always in-sync with the codebase (especially the views).
running feature tests (i.e. using Capybara) against it.
The actual test scenarios and steps are [part of GitLab Rails] so that they're Note that:
always in-sync with the codebase.
### Smoke tests - 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
Smoke tests are quick tests that may be run at any time (especially after the pre-deployment migrations). - 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 > See [end-to-end tests](end_to_end_tests.md) for more information.
learn more.
[multiple pieces]: ../architecture.md#components [multiple pieces]: ../architecture.md#components
[GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell [GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell
...@@ -138,8 +166,29 @@ learn more. ...@@ -138,8 +166,29 @@ learn more.
[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages [GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages
[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-runner [GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-runner
[GitLab Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab [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 [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 ## EE-specific tests
......
# GitLab Runner Helm Chart # GitLab Runner Helm Chart
> **Note:** > **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 The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster. Kubernetes cluster.
...@@ -132,7 +132,7 @@ runners: ...@@ -132,7 +132,7 @@ runners:
If your cluster has RBAC enabled, you can choose to either have the chart create its own service account or provide one. If your cluster has RBAC enabled, you can choose to either have the chart create its own service account or provide one.
To have the chart create the service account for you, set `rbac.create` to true. To have the chart create the service account for you, set `rbac.create` to true.
### Controlling maximum Runner concurrency ### Controlling maximum Runner concurrency
......
...@@ -36,7 +36,7 @@ Please consider using a virtual machine to run GitLab. ...@@ -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. 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. 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. needs several Gems that have native extensions.
## Hardware requirements ## Hardware requirements
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
To enable the Auth0 OmniAuth provider, you must create an Auth0 account, and an To enable the Auth0 OmniAuth provider, you must create an Auth0 account, and an
application. 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. create an account, you can do so at the same link.
1. Select "New App/API". 1. Select "New App/API".
......
...@@ -54,6 +54,7 @@ you to use. ...@@ -54,6 +54,7 @@ you to use.
``` ```
Account: Email, Read Account: Email, Read
Projects: Read
Repositories: Read Repositories: Read
Pull Requests: Read Pull Requests: Read
Issues: Read Issues: Read
......
...@@ -35,7 +35,7 @@ In Google's side: ...@@ -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 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. 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 1. To enable projects to access [Google Kubernetes Engine](../user/project/clusters/index.md), you must also
enable these APIs: enable these APIs:
- Google Kubernetes Engine API - 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) ## GitLab Flavored Markdown (GFM)
...@@ -8,21 +13,21 @@ GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specifi ...@@ -8,21 +13,21 @@ GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specifi
You can use GFM in the following areas: You can use GFM in the following areas:
- comments - Comments
- issues - Issues
- merge requests - Merge requests
- milestones - Milestones
- snippets (the snippet must be named with a `.md` extension) - Snippets (the snippet must be named with a `.md` extension)
- wiki pages - Wiki pages
- markdown documents inside the repository - Markdown documents inside repositories
You can also use other rich text files in GitLab. You might have to install a 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. dependency to do so. Please see the [`github-markup` gem readme](https://github.com/gitlabhq/markup#markups) for more information.
> **Notes:** > **Notes:**
> >
> For the best result, we encourage you to check this document out as rendered > For the best result, we encourage you to check this document out as [rendered
> by GitLab itself: [markdown.md] > by GitLab itself](markdown.md).
> >
> As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown > 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 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 ...@@ -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 repositories are also processed with CommonMark. Older content in issues/comments
are still processed using the [Redcarpet Ruby library][redcarpet]. 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._ > _Where there are significant differences, we will try to call them out in this document._
### Transitioning to CommonMark ### Transitioning to CommonMark
......
# Migrating from ClearCase # 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 tools developed by IBM which also include a centralized version control system
similar to Git. similar to Git.
......
...@@ -29,7 +29,7 @@ Below is a valid example of a manifest file: ...@@ -29,7 +29,7 @@ Below is a valid example of a manifest file:
```xml ```xml
<manifest> <manifest>
<remote review="https://android-review.googlesource.com/" /> <remote review="https://android.googlesource.com/" />
<project path="build/make" name="platform/build" /> <project path="build/make" name="platform/build" />
<project path="build/blueprint" name="platform/build/blueprint" /> <project path="build/blueprint" name="platform/build/blueprint" />
...@@ -38,10 +38,10 @@ Below is a valid example of a manifest file: ...@@ -38,10 +38,10 @@ Below is a valid example of a manifest file:
As a result, the following projects will be created: As a result, the following projects will be created:
| GitLab | Import URL | | GitLab | Import URL |
|---|---| |:------------------------------------------------|:------------------------------------------------------------|
| https://gitlab.com/YOUR_GROUP/build/make | https://android-review.googlesource.com/platform/build | | `https://gitlab.com/YOUR_GROUP/build/make` | <https://android.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/blueprint` | <https://android.googlesource.com/platform/build/blueprint> |
## Importing the repositories ## 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 @@ ...@@ -4,9 +4,9 @@
To enable Mattermost integration you must create an incoming webhook integration: To enable Mattermost integration you must create an incoming webhook integration:
1. Sign in to your Mattermost instance 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. 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. 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. 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 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 ...@@ -30,6 +30,7 @@ Click on the service links to see further configuration instructions and details
| [Bugzilla](bugzilla.md) | Bugzilla issue tracker | | [Bugzilla](bugzilla.md) | Bugzilla issue tracker |
| Campfire | Simple web-based real-time group chat | | Campfire | Simple web-based real-time group chat |
| Custom Issue Tracker | Custom issue tracker | | 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 | | 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 | | [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 | | External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
......
...@@ -298,6 +298,14 @@ module API ...@@ -298,6 +298,14 @@ module API
desc: 'Title' desc: 'Title'
} }
], ],
'discord' => [
{
required: true,
name: :webhook,
type: String,
desc: 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…'
}
],
'drone-ci' => [ 'drone-ci' => [
{ {
required: true, required: true,
...@@ -677,6 +685,7 @@ module API ...@@ -677,6 +685,7 @@ module API
BuildkiteService, BuildkiteService,
CampfireService, CampfireService,
CustomIssueTrackerService, CustomIssueTrackerService,
DiscordService,
DroneCiService, DroneCiService,
EmailsOnPushService, EmailsOnPushService,
ExternalWikiService, ExternalWikiService,
......
...@@ -29,6 +29,7 @@ module Banzai ...@@ -29,6 +29,7 @@ module Banzai
end end
def absolute_link_attr(uri) 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 URI.join(Gitlab.config.gitlab.url, uri).to_s
end end
end end
......
...@@ -88,35 +88,19 @@ module BitbucketServer ...@@ -88,35 +88,19 @@ module BitbucketServer
def build_url(path) def build_url(path)
return path if path.starts_with?(root_url) return path if path.starts_with?(root_url)
url_join_paths(root_url, path) Gitlab::Utils.append_path(root_url, path)
end end
def root_url def root_url
url_join_paths(base_uri, "/rest/api/#{api_version}") Gitlab::Utils.append_path(base_uri, "rest/api/#{api_version}")
end end
def delete_url(resource, path) def delete_url(resource, path)
if resource == :branches 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 else
build_url(path) build_url(path)
end end
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
end end
...@@ -116,7 +116,9 @@ code_quality: ...@@ -116,7 +116,9 @@ code_quality:
license_management: license_management:
stage: test 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 allow_failure: true
script: script:
- license_management - license_management
......
...@@ -28,6 +28,7 @@ module Gitlab ...@@ -28,6 +28,7 @@ module Gitlab
@repository = repository @repository = repository
@diff_refs = diff_refs @diff_refs = diff_refs
@fallback_diff_refs = fallback_diff_refs @fallback_diff_refs = fallback_diff_refs
@unfolded = false
# Ensure items are collected in the the batch # Ensure items are collected in the the batch
new_blob_lazy new_blob_lazy
...@@ -137,6 +138,24 @@ module Gitlab ...@@ -137,6 +138,24 @@ module Gitlab
Gitlab::Diff::Parser.new.parse(raw_diff.each_line, diff_file: self).to_a Gitlab::Diff::Parser.new.parse(raw_diff.each_line, diff_file: self).to_a
end 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 def highlighted_diff_lines
@highlighted_diff_lines ||= @highlighted_diff_lines ||=
Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
......
...@@ -5,9 +5,9 @@ module Gitlab ...@@ -5,9 +5,9 @@ module Gitlab
class Line class Line
SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze 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_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) def initialize(text, type, index, old_pos, new_pos, parent_file: nil, line_code: nil, rich_text: nil)
@text, @type, @index = text, type, index @text, @type, @index = text, type, index
...@@ -21,7 +21,14 @@ module Gitlab ...@@ -21,7 +21,14 @@ module Gitlab
end end
def self.init_from_hash(hash) 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 end
def to_hash 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 ...@@ -103,6 +103,10 @@ module Gitlab
@diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha) @diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha)
end end
def unfolded_diff?(repository)
diff_file(repository)&.unfolded?
end
def diff_file(repository) def diff_file(repository)
return @diff_file if defined?(@diff_file) return @diff_file if defined?(@diff_file)
...@@ -136,7 +140,13 @@ module Gitlab ...@@ -136,7 +140,13 @@ module Gitlab
return unless diff_refs.complete? return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project) 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 end
def get_formatter_class(type) def get_formatter_class(type)
......
...@@ -31,19 +31,19 @@ module Gitlab ...@@ -31,19 +31,19 @@ module Gitlab
class ReferenceRewriter class ReferenceRewriter
RewriteError = Class.new(StandardError) RewriteError = Class.new(StandardError)
def initialize(text, source_project, current_user) def initialize(text, source_parent, current_user)
@text = text @text = text
@source_project = source_project @source_parent = source_parent
@current_user = current_user @current_user = current_user
@original_html = markdown(text) @original_html = markdown(text)
@pattern = Gitlab::ReferenceExtractor.references_pattern @pattern = Gitlab::ReferenceExtractor.references_pattern
end end
def rewrite(target_project) def rewrite(target_parent)
return @text unless needs_rewrite? return @text unless needs_rewrite?
@text.gsub(@pattern) do |reference| @text.gsub(@pattern) do |reference|
unfold_reference(reference, Regexp.last_match, target_project) unfold_reference(reference, Regexp.last_match, target_parent)
end end
end end
...@@ -53,14 +53,14 @@ module Gitlab ...@@ -53,14 +53,14 @@ module Gitlab
private private
def unfold_reference(reference, match, target_project) def unfold_reference(reference, match, target_parent)
before = @text[0...match.begin(0)] before = @text[0...match.begin(0)]
after = @text[match.end(0)..-1] after = @text[match.end(0)..-1]
referable = find_referable(reference) referable = find_referable(reference)
return reference unless referable 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 return reference if reference == cross_reference
if cross_reference.nil? if cross_reference.nil?
...@@ -72,17 +72,17 @@ module Gitlab ...@@ -72,17 +72,17 @@ module Gitlab
end end
def find_referable(reference) def find_referable(reference)
extractor = Gitlab::ReferenceExtractor.new(@source_project, extractor = Gitlab::ReferenceExtractor.new(@source_parent,
@current_user) @current_user)
extractor.analyze(reference) extractor.analyze(reference)
extractor.all.first extractor.all.first
end end
def build_cross_reference(referable, target_project) def build_cross_reference(referable, target_parent)
if referable.respond_to?(:project) if referable.respond_to?(:project)
referable.to_reference(target_project) referable.to_reference(target_parent)
else else
referable.to_reference(@source_project, target_project: target_project) referable.to_reference(@source_parent, target_project: target_parent)
end end
end end
...@@ -91,7 +91,7 @@ module Gitlab ...@@ -91,7 +91,7 @@ module Gitlab
end end
def markdown(text) 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 end
end end
......
...@@ -16,14 +16,15 @@ module Gitlab ...@@ -16,14 +16,15 @@ module Gitlab
@pattern = FileUploader::MARKDOWN_PATTERN @pattern = FileUploader::MARKDOWN_PATTERN
end end
def rewrite(target_project) def rewrite(target_parent)
return @text unless needs_rewrite? return @text unless needs_rewrite?
@text.gsub(@pattern) do |markdown| @text.gsub(@pattern) do |markdown|
file = find_file(@source_project, $~[:secret], $~[:file]) file = find_file(@source_project, $~[:secret], $~[:file])
break markdown unless file.try(:exists?) 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 moved.markdown_link
end end
end end
......
...@@ -8,7 +8,10 @@ module Gitlab ...@@ -8,7 +8,10 @@ module Gitlab
def add_gon_variables def add_gon_variables
gon.api_version = 'v4' 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.max_file_size = Gitlab::CurrentSettings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path gon.webpack_public_path = webpack_public_path
......
...@@ -63,7 +63,7 @@ module Gitlab ...@@ -63,7 +63,7 @@ module Gitlab
end end
def repository_url(name) def repository_url(name)
URI.join(remote, name).to_s Gitlab::Utils.append_path(remote, name)
end end
def remote def remote
......
...@@ -38,7 +38,7 @@ module Gitlab ...@@ -38,7 +38,7 @@ module Gitlab
def go_body(path) def go_body(path)
config = Gitlab.config 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) import_prefix = strip_url(project_url.to_s)
repository_url = if Gitlab::CurrentSettings.enabled_git_access_protocol == 'ssh' repository_url = if Gitlab::CurrentSettings.enabled_git_access_protocol == 'ssh'
......
...@@ -2,13 +2,14 @@ module Gitlab ...@@ -2,13 +2,14 @@ module Gitlab
module QuickActions module QuickActions
class CommandDefinition class CommandDefinition
attr_accessor :name, :aliases, :description, :explanation, :params, 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 = {}) def initialize(name, attributes = {})
@name = name @name = name
@aliases = attributes[:aliases] || [] @aliases = attributes[:aliases] || []
@description = attributes[:description] || '' @description = attributes[:description] || ''
@warning = attributes[:warning] || ''
@explanation = attributes[:explanation] || '' @explanation = attributes[:explanation] || ''
@params = attributes[:params] || [] @params = attributes[:params] || []
@condition_block = attributes[:condition_block] @condition_block = attributes[:condition_block]
...@@ -33,11 +34,13 @@ module Gitlab ...@@ -33,11 +34,13 @@ module Gitlab
def explain(context, arg) def explain(context, arg)
return unless available?(context) return unless available?(context)
if explanation.respond_to?(:call) message = if explanation.respond_to?(:call)
execute_block(explanation, context, arg) execute_block(explanation, context, arg)
else else
explanation explanation
end end
warning.empty? ? message : "#{message} (#{warning})"
end end
def execute(context, arg) def execute(context, arg)
...@@ -61,6 +64,7 @@ module Gitlab ...@@ -61,6 +64,7 @@ module Gitlab
name: name, name: name,
aliases: aliases, aliases: aliases,
description: desc, description: desc,
warning: warning,
params: prms params: prms
} }
end end
......
...@@ -31,6 +31,10 @@ module Gitlab ...@@ -31,6 +31,10 @@ module Gitlab
@description = block_given? ? block : text @description = block_given? ? block : text
end end
def warning(message = '')
@warning = message
end
# Allows to define params for the next quick action. # Allows to define params for the next quick action.
# These params are shown in the autocomplete menu. # These params are shown in the autocomplete menu.
# #
...@@ -133,6 +137,7 @@ module Gitlab ...@@ -133,6 +137,7 @@ module Gitlab
name, name,
aliases: aliases, aliases: aliases,
description: @description, description: @description,
warning: @warning,
explanation: @explanation, explanation: @explanation,
params: @params, params: @params,
condition_block: @condition_block, condition_block: @condition_block,
...@@ -150,6 +155,7 @@ module Gitlab ...@@ -150,6 +155,7 @@ module Gitlab
@explanation = nil @explanation = nil
@params = nil @params = nil
@condition_block = nil @condition_block = nil
@warning = nil
@parse_params_block = nil @parse_params_block = nil
end end
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 # frozen_string_literal: true
require 'securerandom'
module JSONWebToken module JSONWebToken
class Token class Token
attr_accessor :issuer, :subject, :audience, :id attr_accessor :issuer, :subject, :audience, :id
attr_accessor :issued_at, :not_before, :expire_time attr_accessor :issued_at, :not_before, :expire_time
DEFAULT_NOT_BEFORE_TIME = 5
DEFAULT_EXPIRE_TIME = 60
def initialize def initialize
@id = SecureRandom.uuid @id = SecureRandom.uuid
@issued_at = Time.now @issued_at = Time.now
# we give a few seconds for time shift # 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 # 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 = {} @custom_payload = {}
end end
......
...@@ -45,7 +45,6 @@ namespace :gitlab do ...@@ -45,7 +45,6 @@ namespace :gitlab do
start_checking "GitLab Shell" start_checking "GitLab Shell"
check_gitlab_shell check_gitlab_shell
check_repos_hooks_directory_is_link
check_gitlab_shell_self_test check_gitlab_shell_self_test
finished_checking "GitLab Shell" finished_checking "GitLab Shell"
...@@ -54,42 +53,6 @@ namespace :gitlab do ...@@ -54,42 +53,6 @@ namespace :gitlab do
# Checks # 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 def check_gitlab_shell_self_test
gitlab_shell_repo_base = gitlab_shell_path gitlab_shell_repo_base = gitlab_shell_path
check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base) check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
......
...@@ -136,6 +136,7 @@ HELM_CMD=$(cat << EOF ...@@ -136,6 +136,7 @@ HELM_CMD=$(cat << EOF
helm upgrade --install \ helm upgrade --install \
--wait \ --wait \
--timeout 600 \ --timeout 600 \
--set global.appConfig.enableUsagePing=false \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set global.hosts.hostSuffix="$HOST_SUFFIX" \ --set global.hosts.hostSuffix="$HOST_SUFFIX" \
--set global.hosts.domain="$REVIEW_APPS_DOMAIN" \ --set global.hosts.domain="$REVIEW_APPS_DOMAIN" \
......
...@@ -76,7 +76,7 @@ describe SendFileUpload do ...@@ -76,7 +76,7 @@ describe SendFileUpload do
it 'sends a file with a custom type' do it 'sends a file with a custom type' do
headers = double 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(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:/) expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/)
......
...@@ -141,6 +141,28 @@ describe Projects::BlobController do ...@@ -141,6 +141,28 @@ describe Projects::BlobController do
expect(lines.first).to have_key('rich_text') expect(lines.first).to have_key('rich_text')
end 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 context 'when rendering match lines' do
it 'adds top match line when "since" is less than 1' 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) do_get(since: 5, to: 10, offset: 10, from_merge_request: true)
...@@ -157,7 +179,7 @@ describe Projects::BlobController do ...@@ -157,7 +179,7 @@ describe Projects::BlobController do
match_line = JSON.parse(response.body).first match_line = JSON.parse(response.body).first
expect(match_line['type']).to eq('context') expect(match_line['type']).to be_nil
end end
it 'adds bottom match line when "t"o is less than blob size' do it 'adds bottom match line when "t"o is less than blob size' do
...@@ -177,7 +199,7 @@ describe Projects::BlobController do ...@@ -177,7 +199,7 @@ describe Projects::BlobController do
match_line = JSON.parse(response.body).last match_line = JSON.parse(response.body).last
expect(match_line['type']).to eq('context') expect(match_line['type']).to be_nil
end end
end end
end end
......
...@@ -13,7 +13,7 @@ FactoryBot.define do ...@@ -13,7 +13,7 @@ FactoryBot.define do
end end
trait :with_token do trait :with_token do
service_account_token { Faker::Lorem.characters(10) } service_account_token { FFaker::Lorem.characters(10) }
end end
end end
end end
require "spec_helper" require "spec_helper"
describe "User views issue" do describe "User views issue" do
set(:project) { create(:project_empty_repo, :public) } let(:project) { create(:project_empty_repo, :public) }
set(:user) { create(:user) } let(:user) { create(:user) }
set(:issue) { create(:issue, project: project, description: "# Description header", author: user) } let(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
before do before do
project.add_developer(user) 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