diff options
187 files changed, 3598 insertions, 871 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1ea9ce1f497..c5cd006a289 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -509,6 +509,7 @@ rspec-mysql: parallel: 50 .rspec-quarantine: &rspec-quarantine + retry: 0 script: - export CACHE_CLASSES=true - scripts/gitaly-test-spawn diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 00000000000..69de9a5dd13 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,30 @@ +{ + "extends": "stylelint-config-recommended", + "plugins": [ + "stylelint-scss" + ], + "rules": { + "no-descending-specificity": null, + "font-family-no-missing-generic-family-keyword": null, + "at-rule-no-unknown": [ true, { + ignoreAtRules: ["include", "each", "mixin", "extend", "if", "function", "for", "else", "return"] + }], + "selector-type-no-unknown": [true, { + "ignoreTypes": ["gl-emoji"] + }], + "unit-no-unknown" : [true, { + "ignoreFunctions": ["-webkit-image-set"] + }], + "scss/at-extend-no-missing-placeholder": null, + "scss/at-function-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$", + "scss/at-import-no-partial-leading-underscore": true, + "scss/at-import-partial-extension-blacklist": ["scss"], + "scss/at-mixin-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$", + "scss/at-rule-no-unknown": true, + "scss/dollar-variable-colon-space-after": "always", + "scss/dollar-variable-colon-space-before": "never", + "scss/dollar-variable-pattern": "^[_]?[a-z]+([a-z0-9-]+[a-z0-9]+)?$", + "scss/percent-placeholder-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$", + "scss/selector-no-redundant-nesting-selector": true, + } +} diff --git a/Dangerfile b/Dangerfile index 6a2c5cf2773..715a2bcbbae 100644 --- a/Dangerfile +++ b/Dangerfile @@ -11,3 +11,4 @@ danger.import_dangerfile(path: 'danger/commit_messages') danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies') danger.import_dangerfile(path: 'danger/prettier') danger.import_dangerfile(path: 'danger/eslint') +danger.import_dangerfile(path: 'danger/roulette') diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 815d5ca06d5..66e2ae6c25c 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.19.0 +1.19.1 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 2bf50aaf17a..56b6be4ebb2 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -8.3.0 +8.3.1 diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 7bbafe66199..5a27388863c 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -144,6 +144,7 @@ export default { if (left || right) { return { + ...line, left: line.left ? mapDiscussions(line.left) : null, right: line.right ? mapDiscussions(line.right, () => !left) : null, }; diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index effb6202327..062024b8cdd 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -161,6 +161,7 @@ export function addContextLines(options) { const normalizedParallelLines = contextLines.map(line => ({ left: line, right: line, + line_code: line.line_code, })); if (options.bottom) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index fba31f16d65..5090b0bdc3c 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -163,7 +163,7 @@ export default class FilteredSearchVisualTokens { const tokenValueElement = tokenValueContainer.querySelector('.value'); tokenValueElement.innerText = tokenValue; - if (tokenValue === 'none' || tokenValue === 'any') { + if (['none', 'any'].includes(tokenValue.toLowerCase())) { return; } diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue index 5119dbf32eb..11d5d9639b6 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue @@ -44,7 +44,7 @@ export default { <div class="d-flex ide-commit-editor-header align-items-center"> <file-icon :file-name="activeFile.name" :size="16" class="mr-2" /> <strong class="mr-2"> {{ activeFile.path }} </strong> - <changed-file-icon :file="activeFile" class="ml-0" /> + <changed-file-icon :file="activeFile" :is-centered="false" /> <div class="ml-auto"> <button v-if="!isStaged" diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 04ecd4ba4e7..c9c4e9e86f8 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -51,8 +51,11 @@ export default { return __('Create file'); }, - isCreatingNew() { - return this.entryModal.type !== modalTypes.rename; + isCreatingNewFile() { + return this.entryModal.type === 'blob'; + }, + placeholder() { + return this.isCreatingNewFile ? 'dir/file_name' : 'dir/'; }, }, methods: { @@ -107,9 +110,12 @@ export default { v-model="entryName" type="text" class="form-control qa-full-file-path" - placeholder="/dir/file_name" + :placeholder="placeholder" /> - <ul v-if="isCreatingNew" class="prepend-top-default list-inline qa-template-list"> + <ul + v-if="isCreatingNewFile" + class="file-templates prepend-top-default list-inline qa-template-list" + > <li v-for="(template, index) in templateTypes" :key="index" class="list-inline-item"> <button type="button" diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 0ceff10a02a..29fe460017e 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -130,7 +130,7 @@ export const isInViewport = (el, offset = {}) => { rect.top >= (top || 0) && rect.left >= (left || 0) && rect.bottom <= window.innerHeight && - rect.right <= window.innerWidth + parseInt(rect.right, 10) <= window.innerWidth ); }; diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index ff303d0f55a..fb1d98355b3 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -95,6 +95,7 @@ export default { <div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body"> <suggestions v-if="hasSuggestion && !isEditing" + class="note-text md" :suggestions="note.suggestions" :note-html="note.note_html" :line-type="lineType" diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index b7e9f7c2028..ded084e9b10 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -93,6 +93,7 @@ export default { }, computed: { ...mapGetters([ + 'convertedDisscussionIds', 'getNoteableData', 'nextUnresolvedDiscussionId', 'unresolvedDiscussionsCount', @@ -301,6 +302,10 @@ export default { note: { note: noteText }, }; + if (this.convertedDisscussionIds.includes(this.discussion.id)) { + postData.return_discussion = true; + } + if (this.discussion.for_commit) { postData.note_project_id = this.discussion.project_id; } diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 6d72b72e628..9eb69dd91ae 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -60,6 +60,7 @@ export default { ...mapGetters([ 'isNotesFetched', 'discussions', + 'convertedDisscussionIds', 'getNotesDataByProp', 'isLoading', 'commentsDisabled', @@ -193,7 +194,9 @@ export default { /> <placeholder-note v-else :key="discussion.id" :note="discussion.notes[0]" /> </template> - <template v-else-if="discussion.individual_note"> + <template + v-else-if="discussion.individual_note && !convertedDisscussionIds.includes(discussion.id)" + > <system-note v-if="discussion.notes[0].system" :key="discussion.id" diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index ff65f14d529..1ae8b9a0686 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -83,12 +83,44 @@ export const updateNote = ({ commit, dispatch }, { endpoint, note }) => dispatch('startTaskList'); }); -export const replyToDiscussion = ({ commit }, { endpoint, data }) => +export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => { + const { notesById } = getters; + + notes.forEach(note => { + if (notesById[note.id]) { + commit(types.UPDATE_NOTE, note); + } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) { + const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id); + + if (discussion) { + commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); + } else if (note.type === constants.DIFF_NOTE) { + dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); + } else { + commit(types.ADD_NEW_NOTE, note); + } + } else { + commit(types.ADD_NEW_NOTE, note); + } + }); +}; + +export const replyToDiscussion = ({ commit, state, getters, dispatch }, { endpoint, data }) => service .replyToDiscussion(endpoint, data) .then(res => res.json()) .then(res => { - commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res); + if (res.discussion) { + commit(types.UPDATE_DISCUSSION, res.discussion); + + updateOrCreateNotes({ commit, state, getters, dispatch }, res.discussion.notes); + + dispatch('updateMergeRequestWidget'); + dispatch('startTaskList'); + dispatch('updateResolvableDiscussonsCounts'); + } else { + commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res); + } return res; }); @@ -262,25 +294,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => { if (resp.notes && resp.notes.length) { - const { notesById } = getters; - - resp.notes.forEach(note => { - if (notesById[note.id]) { - commit(types.UPDATE_NOTE, note); - } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) { - const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id); - - if (discussion) { - commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); - } else if (note.type === constants.DIFF_NOTE) { - dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); - } else { - commit(types.ADD_NEW_NOTE, note); - } - } else { - commit(types.ADD_NEW_NOTE, note); - } - }); + updateOrCreateNotes({ commit, state, getters, dispatch }, resp.notes); dispatch('startTaskList'); } diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 0ffc0cb2593..5026c13dab5 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -4,6 +4,8 @@ import { collapseSystemNotes } from './collapse_utils'; export const discussions = state => collapseSystemNotes(state.discussions); +export const convertedDisscussionIds = state => state.convertedDisscussionIds; + export const targetNoteHash = state => state.targetNoteHash; export const getNotesData = state => state.notesData; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 887e6d22b06..6168aeae35d 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -5,6 +5,7 @@ import mutations from '../mutations'; export default () => ({ state: { discussions: [], + convertedDisscussionIds: [], targetNoteHash: null, lastFetchedAt: null, diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index d167f8ef421..23ef843bb75 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -266,7 +266,7 @@ export default { }, [types.CONVERT_TO_DISCUSSION](state, discussionId) { - const discussion = utils.findNoteObjectById(state.discussions, discussionId); - Object.assign(discussion, { individual_note: false }); + const convertedDisscussionIds = [...state.convertedDisscussionIds, discussionId]; + Object.assign(state, { convertedDisscussionIds }); }, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue index a1d3a09cca4..33963d5e1e6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue @@ -73,14 +73,14 @@ export default { <gl-button :aria-label="ariaLabel" variant="blank" - class="commit-edit-toggle mr-2" + class="commit-edit-toggle square s24 mr-2" @click.stop="toggle()" > <icon :name="collapseIcon" :size="16" /> </gl-button> <span v-if="expanded">{{ __('Collapse') }}</span> <span v-else> - <span v-html="message"></span> + <span class="vertical-align-middle" v-html="message"></span> <gl-button variant="link" class="modify-message-button"> {{ modifyLinkMessage }} </gl-button> diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue index bb7710f708e..e9ab6f5ba7a 100644 --- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue @@ -37,6 +37,11 @@ export default { required: false, default: 12, }, + isCentered: { + type: Boolean, + required: false, + default: true, + }, }, computed: { changedIcon() { @@ -78,7 +83,12 @@ export default { </script> <template> - <span v-gl-tooltip.right :title="tooltipTitle" class="file-changed-icon ml-auto"> + <span + v-gl-tooltip.right + :title="tooltipTitle" + :class="{ 'ml-auto': isCentered }" + class="file-changed-icon" + > <icon v-if="showIcon" :name="changedIcon" :size="size" :css-classes="changedIconClass" /> </span> </template> diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue index f54033efc54..0cbcdbf2eb4 100644 --- a/app/assets/javascripts/vue_shared/components/file_row.vue +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -136,6 +136,7 @@ export default { <div v-else :class="fileClass" + :title="file.name" class="file-row" role="button" @click="clickFile" diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue index c33665c24f6..dcda701f049 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue @@ -130,6 +130,6 @@ export default { <template> <div> <div class="flash-container js-suggestions-flash"></div> - <div v-show="isRendered" ref="container" class="note-text md" v-html="noteHtml"></div> + <div v-show="isRendered" ref="container" v-html="noteHtml"></div> </div> </template> diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 4fb787887a1..70d50c74ca9 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -63,15 +63,15 @@ // // Pass in any number of transitions @mixin transition($transitions...) { - $unfoldedTransitions: (); + $unfolded-transitions: (); @each $transition in $transitions { - $unfoldedTransitions: append($unfoldedTransitions, unfoldTransition($transition), comma); + $unfolded-transitions: append($unfolded-transitions, unfold-transition($transition), comma); } - transition: $unfoldedTransitions; + transition: $unfolded-transitions; } -@mixin disableAllAnimation { +@mixin disable-all-animation { /*CSS transitions*/ -o-transition-property: none !important; -moz-transition-property: none !important; @@ -92,27 +92,27 @@ animation: none !important; } -@function unfoldTransition ($transition) { +@function unfold-transition ($transition) { // Default values $property: all; $duration: $general-hover-transition-duration; $easing: $general-hover-transition-curve; // Browser default is ease, which is what we want $delay: null; // Browser default is 0, which is what we want - $defaultProperties: ($property, $duration, $easing, $delay); + $default-properties: ($property, $duration, $easing, $delay); // Grab transition properties if they exist - $unfoldedTransition: (); - @for $i from 1 through length($defaultProperties) { + $unfolded-transition: (); + @for $i from 1 through length($default-properties) { $p: null; @if $i <= length($transition) { $p: nth($transition, $i); } @else { - $p: nth($defaultProperties, $i); + $p: nth($default-properties, $i); } - $unfoldedTransition: append($unfoldedTransition, $p); + $unfolded-transition: append($unfolded-transition, $p); } - @return $unfoldedTransition; + @return $unfolded-transition; } .btn { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 36dd1cee4de..23dcc1817b1 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -565,15 +565,14 @@ } .navbar-empty { + justify-content: center; height: $header-height; background: $white-light; border-bottom: 1px solid $white-normal; - .mx-auto { - .tanuki-logo, - img { - height: 36px; - } + .tanuki-logo, + .brand-header-logo { + max-height: 100%; } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index f708a26bb32..961de8402ef 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -228,7 +228,7 @@ .cur { .avatar { - @include disableAllAnimation; + @include disable-all-animation; border: 1px solid $white-light; } } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 9837b1a6bd0..b9d0c0d4d96 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -36,10 +36,6 @@ width: fit-content; } - tbody { - background-color: $white-light; - } - tr { th { border-bottom: solid 2px $gl-gray-100; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index a08639936c0..bf85acdc0d6 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -49,13 +49,6 @@ word-wrap: normal; } - // Multi-line code blocks should scroll horizontally - pre { - code { - white-space: pre; - } - } - kbd { display: inline-block; padding: 3px 5px; @@ -166,6 +159,10 @@ overflow-x: auto; border-radius: 2px; + // Multi-line code blocks should scroll horizontally + code { + white-space: pre; + } &.plain-readme { background: none; @@ -303,11 +300,10 @@ body { } .page-title-empty { - margin-top: 0; + margin: 12px 0; line-height: 1.3; font-size: 1.25em; font-weight: $gl-font-weight-bold; - margin: 12px 0; } h1, diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 96dab609a13..530b1154550 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -251,7 +251,7 @@ $gl-padding-top: 10px; $gl-sidebar-padding: 22px; $gl-bar-padding: 3px; $input-horizontal-padding: 12px; -$browserScrollbarSize: 10px; +$browser-scrollbar-size: 10px; /* * Misc diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 604f806dc58..ca9a2a673f5 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -125,7 +125,7 @@ $dark-il: #de935f; .diff-line-num.new, .line_content.new { - @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border); + @include diff-background($dark-new-bg, $dark-new-idiff, $dark-border); &::before, a { @@ -135,7 +135,7 @@ $dark-il: #de935f; .diff-line-num.old, .line_content.old { - @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border); + @include diff-background($dark-old-bg, $dark-old-idiff, $dark-border); &::before, a { diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 8e2720511da..bc3761d1e47 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -125,7 +125,7 @@ $monokai-gi: #a6e22e; .diff-line-num.new, .line_content.new { - @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); + @include diff-background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); &::before, a { @@ -135,7 +135,7 @@ $monokai-gi: #a6e22e; .diff-line-num.old, .line_content.old { - @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); + @include diff-background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); &::before, a { diff --git a/app/assets/stylesheets/highlight/none.scss b/app/assets/stylesheets/highlight/none.scss index 7ced4e82e66..4bedb6a8e5b 100644 --- a/app/assets/stylesheets/highlight/none.scss +++ b/app/assets/stylesheets/highlight/none.scss @@ -4,7 +4,7 @@ -@mixin matchLine { +@mixin match-line { color: $black-transparent; background-color: $white-normal; } @@ -45,7 +45,7 @@ &.match .line_content, .new-nonewline.line_content, .old-nonewline.line_content { - @include matchLine; + @include match-line; } .diff-line-num { @@ -121,7 +121,7 @@ } &.match { - @include matchLine; + @include match-line; } &.hll:not(.empty-cell) { diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index cd1f0f6650f..de7b9424340 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -129,7 +129,7 @@ $solarized-dark-il: #2aa198; .diff-line-num.new, .line_content.new { - @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); + @include diff-background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); &::before, a { @@ -139,7 +139,7 @@ $solarized-dark-il: #2aa198; .diff-line-num.old, .line_content.old { - @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); + @include diff-background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); &::before, a { diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 09c3ea36414..84a92d0320a 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -90,7 +90,7 @@ $solarized-light-vg: #268bd2; $solarized-light-vi: #268bd2; $solarized-light-il: #2aa198; -@mixin matchLine { +@mixin match-line { color: $black-transparent; background: $solarized-light-matchline-bg; } @@ -125,7 +125,7 @@ $solarized-light-il: #2aa198; &.match .line_content, &.old-nonewline .line_content, &.new-nonewline .line_content { - @include matchLine; + @include match-line; } td.diff-line-num.hll:not(.empty-cell), @@ -136,7 +136,7 @@ $solarized-light-il: #2aa198; .diff-line-num.new, .line_content.new { - @include diff_background($solarized-light-new-bg, + @include diff-background($solarized-light-new-bg, $solarized-light-new-idiff, $solarized-light-border); &::before, @@ -147,7 +147,7 @@ $solarized-light-il: #2aa198; .diff-line-num.old, .line_content.old { - @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); + @include diff-background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); &::before, a { @@ -168,7 +168,7 @@ $solarized-light-il: #2aa198; } .line_content.match { - @include matchLine; + @include match-line; } &:not(.diff-expanded) + .diff-expanded, diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss index 90a5250c247..c636abbdfad 100644 --- a/app/assets/stylesheets/highlight/white_base.scss +++ b/app/assets/stylesheets/highlight/white_base.scss @@ -70,7 +70,7 @@ $white-gc-color: #999; $white-gc-bg: #eaf2f5; -@mixin matchLine { +@mixin match-line { color: $black-transparent; background-color: $gray-light; } @@ -105,7 +105,7 @@ pre.code, &.match .line_content, .new-nonewline.line_content, .old-nonewline.line_content { - @include matchLine; + @include match-line; } .diff-line-num { @@ -185,7 +185,7 @@ pre.code, } &.match { - @include matchLine; + @include match-line; } &.hll:not(.empty-cell) { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 37ed5ae674a..cb5f1a84005 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -34,7 +34,6 @@ .detail-page-header-actions { align-self: center; - flex-shrink: 0; flex: 0 0 auto; @include media-breakpoint-down(xs) { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index e3b98b26a11..a708149b36c 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -602,7 +602,7 @@ } } -@mixin diff_background($background, $idiff, $border) { +@mixin diff-background($background, $idiff, $border) { background: $background; &.line_content span.idiff { diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 5a988b184b6..655b297295a 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -182,9 +182,8 @@ .template-selector-dropdowns-wrap { display: inline-block; - margin-left: 8px; - vertical-align: top; margin: 5px 0 0 8px; + vertical-align: top; @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 135730d71e9..790d438c7e2 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -738,6 +738,8 @@ z-index: 103; background: $gray-light; color: $gl-text-color; + margin-top: -1px; + border-top: 1px solid $border-color; .mr-version-menus-container { display: flex; @@ -789,7 +791,6 @@ position: sticky; top: $header-height + $mr-tabs-height; width: 100%; - border-top: 1px solid $border-color; &.is-fileTreeOpen { margin-left: -16px; @@ -810,10 +811,7 @@ top: $header-height; z-index: 200; background-color: $white-light; - - @include media-breakpoint-down(md) { - border-bottom: 1px solid $border-color; - } + border-bottom: 1px solid $border-color; @include media-breakpoint-up(sm) { position: sticky; @@ -1019,3 +1017,8 @@ z-index: 99999; background: $black-transparent; } + +.source-branch-removal-status { + padding-left: 50px; + padding-bottom: $gl-padding; +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 66866aedfba..277030ad3af 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -704,8 +704,8 @@ .scrolling-tabs-container { .scrolling-tabs { margin-top: $gl-padding-8; - margin-bottom: $gl-padding-8 - $browserScrollbarSize; - padding-bottom: $browserScrollbarSize; + margin-bottom: $gl-padding-8 - $browser-scrollbar-size; + padding-bottom: $browser-scrollbar-size; flex-wrap: wrap; border-bottom: 0; } @@ -713,7 +713,7 @@ .fade-left, .fade-right { top: 0; - height: calc(100% - #{$browserScrollbarSize}); + height: calc(100% - #{$browser-scrollbar-size}); .fa { top: 50%; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 26cd5dc801f..af0b0c64814 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -137,6 +137,8 @@ class ApplicationController < ActionController::Base if response.status == 422 && response.body.present? && response.content_type == 'application/json'.freeze payload[:response] = response.body end + + payload[:queue_duration] = request.env[::Gitlab::Middleware::RailsQueueDuration::GITLAB_RAILS_QUEUE_DURATION_KEY] end ## diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb index 9ca54c5519b..28e4cece548 100644 --- a/app/controllers/concerns/send_file_upload.rb +++ b/app/controllers/concerns/send_file_upload.rb @@ -3,7 +3,7 @@ module SendFileUpload def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, proxy: false, disposition: 'attachment') if attachment - response_disposition = ::Gitlab::ContentDisposition.format(disposition: 'attachment', filename: attachment) + response_disposition = ::Gitlab::ContentDisposition.format(disposition: disposition, filename: attachment) # Response-Content-Type will not override an existing Content-Type in # Google Cloud Storage, so the metadata needs to be cleared on GCS for diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index e5a1fc9d6ff..a9d6addd4a4 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -13,9 +13,10 @@ class HelpController < ApplicationController # Remove YAML frontmatter so that it doesn't look weird @help_index = File.read(Rails.root.join('doc', 'README.md')).sub(YAML_FRONT_MATTER_REGEXP, '') - # Prefix Markdown links with `help/` unless they are external links - # See http://rubular.com/r/X3baHTbPO2 - @help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do + # Prefix Markdown links with `help/` unless they are external links. + # '//' not necessarily part of URL, e.g., mailto:mail@example.com + # See https://rubular.com/r/DFHZl5w8d3bpzV + @help_index.gsub!(%r{(?<delim>\]\()(?!\w+:)(?!/)(?<link>[^\)\(]+\))}) do "#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}" end end diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 473c90c882c..7fbbbb04154 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -28,7 +28,7 @@ module AppearancesHelper def brand_header_logo if current_appearance&.header_logo? - image_tag current_appearance.header_logo_path + image_tag current_appearance.header_logo_path, class: 'brand-header-logo' else render 'shared/logo.svg' end diff --git a/app/models/board.rb b/app/models/board.rb index a137863456c..758a71d6903 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -21,6 +21,10 @@ class Board < ActiveRecord::Base group_id.present? end + def project_board? + project_id.present? + end + def backlog_list lists.merge(List.backlog).take end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index acef5d2e643..372f6d678f6 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -687,9 +687,18 @@ module Ci end end + # Returns the modified paths. + # + # The returned value is + # * Array: List of modified paths that should be evaluated + # * nil: Modified path can not be evaluated def modified_paths strong_memoize(:modified_paths) do - push_details.modified_paths + if merge_request? + merge_request.modified_paths + elsif branch_updated? + push_details.modified_paths + end end end diff --git a/app/models/discussion.rb b/app/models/discussion.rb index f2678e0597d..32529ebf71d 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -17,8 +17,6 @@ class Discussion :for_commit?, :for_merge_request?, - :save, - to: :first_note def project_id diff --git a/app/models/individual_note_discussion.rb b/app/models/individual_note_discussion.rb index aab0ff93468..b4a661ae5b4 100644 --- a/app/models/individual_note_discussion.rb +++ b/app/models/individual_note_discussion.rb @@ -17,8 +17,12 @@ class IndividualNoteDiscussion < Discussion noteable.supports_replying_to_individual_notes? && Feature.enabled?(:reply_to_individual_notes) end - def convert_to_discussion! - first_note.becomes!(Discussion.note_class).to_discussion + def convert_to_discussion!(save: false) + first_note.becomes!(Discussion.note_class).to_discussion.tap do + # Save needs to be called on first_note instead of the transformed note + # because of https://gitlab.com/gitlab-org/gitlab-ce/issues/57324 + first_note.save if save + end end def reply_attributes diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb index 6f639e5a7b2..6c507c47752 100644 --- a/app/models/releases/link.rb +++ b/app/models/releases/link.rb @@ -6,7 +6,7 @@ module Releases belongs_to :release - validates :url, presence: true, url: true, uniqueness: { scope: :release } + validates :url, presence: true, url: { protocols: %w(http https ftp) }, uniqueness: { scope: :release } validates :name, presence: true, uniqueness: { scope: :release } scope :sorted, -> { order(created_at: :desc) } diff --git a/app/models/user.rb b/app/models/user.rb index 24101eda0b1..0d52006db28 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -275,6 +275,7 @@ class User < ApplicationRecord scope :confirmed, -> { where.not(confirmed_at: nil) } scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) } scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) } + scope :with_emails, -> { preload(:emails) } # Limits the users to those that have TODOs, optionally in the given state. # diff --git a/app/policies/board_policy.rb b/app/policies/board_policy.rb index 46db008421f..4bf1e7bd3e1 100644 --- a/app/policies/board_policy.rb +++ b/app/policies/board_policy.rb @@ -4,10 +4,12 @@ class BoardPolicy < BasePolicy delegate { @subject.parent } condition(:is_group_board) { @subject.group_board? } + condition(:is_project_board) { @subject.project_board? } - rule { is_group_board ? can?(:read_group) : can?(:read_project) }.enable :read_parent + rule { is_project_board & can?(:read_project) }.enable :read_parent rule { is_group_board & can?(:read_group) }.policy do + enable :read_parent enable :read_milestone enable :read_issue end diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb index b6c30da4d3a..dff0d9696f8 100644 --- a/app/services/applications/create_service.rb +++ b/app/services/applications/create_service.rb @@ -2,16 +2,16 @@ module Applications class CreateService - # rubocop: disable CodeReuse/ActiveRecord + attr_reader :current_user, :params + def initialize(current_user, params) @current_user = current_user - @params = params.except(:ip_address) + @params = params.except(:ip_address) # rubocop: disable CodeReuse/ActiveRecord end - # rubocop: enable CodeReuse/ActiveRecord # EE would override and use `request` arg def execute(request) - Doorkeeper::Application.create(@params) + Doorkeeper::Application.create(params) end end end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 699b3e8555e..354e53a367c 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -36,7 +36,7 @@ module Ci project: project, current_user: current_user, push_options: params[:push_options], - **extra_options(**options)) + **extra_options(options)) sequence = Gitlab::Ci::Pipeline::Chain::Sequence .new(pipeline, command, SEQUENCE) @@ -108,7 +108,12 @@ module Ci end # rubocop: enable CodeReuse/ActiveRecord - def extra_options + def extra_options(options = {}) + # In Ruby 2.4, even when options is empty, f(**options) doesn't work when f + # doesn't have any parameters. We reproduce the Ruby 2.5 behavior by + # checking explicitely that no arguments are given. + raise ArgumentError if options.any? + {} # overriden in EE end end diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb index 28879d2d67f..2cb73555d85 100644 --- a/app/services/concerns/exclusive_lease_guard.rb +++ b/app/services/concerns/exclusive_lease_guard.rb @@ -42,7 +42,7 @@ module ExclusiveLeaseGuard def lease_timeout raise NotImplementedError, - "#{self.class.name} does not implement #{__method__}" + "#{self.class.name} does not implement #{__method__}" end def lease_release? diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 65208b07e27..110e589e30d 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class CreateBranchService < BaseService - def execute(branch_name, ref) - create_master_branch if project.empty_repo? + def execute(branch_name, ref, create_master_if_empty: true) + create_master_branch if create_master_if_empty && project.empty_repo? result = ValidateNewBranchService.new(project, current_user) .execute(branch_name) diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb index 988215ffc78..99324638300 100644 --- a/app/services/emails/base_service.rb +++ b/app/services/emails/base_service.rb @@ -2,10 +2,11 @@ module Emails class BaseService - attr_reader :current_user + attr_reader :current_user, :params, :user def initialize(current_user, params = {}) - @current_user, @params = current_user, params.dup + @current_user = current_user + @params = params.dup @user = params.delete(:user) end end diff --git a/app/services/emails/create_service.rb b/app/services/emails/create_service.rb index 56925a724fe..dc06a5caa40 100644 --- a/app/services/emails/create_service.rb +++ b/app/services/emails/create_service.rb @@ -3,12 +3,11 @@ module Emails class CreateService < ::Emails::BaseService def execute(extra_params = {}) - skip_confirmation = @params.delete(:skip_confirmation) + skip_confirmation = params.delete(:skip_confirmation) - email = @user.emails.create(@params.merge(extra_params)) - - email&.confirm if skip_confirmation && current_user.admin? - email + user.emails.create(params.merge(extra_params)).tap do |email| + email&.confirm if skip_confirmation && current_user.admin? + end end end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 9ecee7c6156..092fd64574d 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -140,7 +140,7 @@ class GitPushService < BaseService .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) EventCreateService.new.push(project, current_user, build_push_data) - Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push) + Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options) project.execute_hooks(build_push_data.dup, :push_hooks) project.execute_services(build_push_data.dup, :push_hooks) @@ -231,4 +231,10 @@ class GitPushService < BaseService def last_pushed_commits @last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT) end + + private + + def pipeline_options + {} # to be overriden in EE + end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 03fcf614c64..6fef5b3ed1d 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -10,7 +10,7 @@ class GitTagPushService < BaseService @push_data = build_push_data EventCreateService.new.push(project, current_user, push_data) - Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push) + Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, pipeline_options) SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks) project.execute_hooks(push_data.dup, :tag_push_hooks) @@ -59,4 +59,8 @@ class GitTagPushService < BaseService [], '') end + + def pipeline_options + {} # to be overriden in EE + end end diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb index 52b45f1b2ce..3fb2c2b3007 100644 --- a/app/services/issues/build_service.rb +++ b/app/services/issues/build_service.rb @@ -57,9 +57,11 @@ module Issues end def issue_params - @issue_params ||= issue_params_with_info_from_discussions.merge(whitelisted_issue_params) + @issue_params ||= build_issue_params end + private + def whitelisted_issue_params if can?(current_user, :admin_issue, project) params.slice(:title, :description, :milestone_id) @@ -67,5 +69,9 @@ module Issues params.slice(:title, :description) end end + + def build_issue_params + issue_params_with_info_from_discussions.merge(whitelisted_issue_params) + end end end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index b975c3a8cb6..5a6e7338b42 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -35,7 +35,7 @@ module Notes if !only_commands && note.save if note.part_of_discussion? && note.discussion.can_convert_to_discussion? - note.discussion.convert_to_discussion!.save(touch: false) + note.discussion.convert_to_discussion!(save: true) end todo_service.new_note(note, current_user) diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb index 7ee9732040d..985a03060bd 100644 --- a/app/services/notes/quick_actions_service.rb +++ b/app/services/notes/quick_actions_service.rb @@ -7,9 +7,14 @@ module Notes 'MergeRequest' => MergeRequests::UpdateService, 'Commit' => Commits::TagService }.freeze + private_constant :UPDATE_SERVICES + + def self.update_services + UPDATE_SERVICES + end def self.noteable_update_service(note) - UPDATE_SERVICES[note.noteable_type] + update_services[note.noteable_type] end def self.supported?(note) diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 68cdc69023a..56f11b31110 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -249,6 +249,7 @@ module NotificationRecipientService attr_reader :action attr_reader :previous_assignee attr_reader :skip_current_user + def initialize(target, current_user, action:, custom_action: nil, previous_assignee: nil, skip_current_user: true) @target = target @current_user = current_user @@ -258,9 +259,13 @@ module NotificationRecipientService @skip_current_user = skip_current_user end + def add_watchers + add_project_watchers + end + def build! add_participants(current_user) - add_project_watchers + add_watchers add_custom_notifications # Re-assign is considered as a mention of the new assignee diff --git a/app/services/protected_branches/api_service.rb b/app/services/protected_branches/api_service.rb index 9b85e13107b..1b13dace5f2 100644 --- a/app/services/protected_branches/api_service.rb +++ b/app/services/protected_branches/api_service.rb @@ -3,16 +3,15 @@ module ProtectedBranches class ApiService < BaseService def create - @push_params = AccessLevelParams.new(:push, params) - @merge_params = AccessLevelParams.new(:merge, params) + ::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute + end - protected_branch_params = { + def protected_branch_params + { name: params[:name], - push_access_levels_attributes: @push_params.access_levels, - merge_access_levels_attributes: @merge_params.access_levels + push_access_levels_attributes: AccessLevelParams.new(:push, params).access_levels, + merge_access_levels_attributes: AccessLevelParams.new(:merge, params).access_levels } - - ::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute end end end diff --git a/app/services/protected_branches/legacy_api_update_service.rb b/app/services/protected_branches/legacy_api_update_service.rb index da8bf2ce02a..7cb8d41818f 100644 --- a/app/services/protected_branches/legacy_api_update_service.rb +++ b/app/services/protected_branches/legacy_api_update_service.rb @@ -6,30 +6,31 @@ # lives in this service. module ProtectedBranches class LegacyApiUpdateService < BaseService + attr_reader :protected_branch, :developers_can_push, :developers_can_merge + def execute(protected_branch) + @protected_branch = protected_branch @developers_can_push = params.delete(:developers_can_push) @developers_can_merge = params.delete(:developers_can_merge) - @protected_branch = protected_branch - protected_branch.transaction do delete_redundant_access_levels - case @developers_can_push + case developers_can_push when true params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }] when false params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::MAINTAINER }] end - case @developers_can_merge + case developers_can_merge when true params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }] when false params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::MAINTAINER }] end - service = ProtectedBranches::UpdateService.new(@project, @current_user, @params) + service = ProtectedBranches::UpdateService.new(project, current_user, params) service.execute(protected_branch) end end @@ -37,12 +38,12 @@ module ProtectedBranches private def delete_redundant_access_levels - unless @developers_can_merge.nil? - @protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll + unless developers_can_merge.nil? + protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll end - unless @developers_can_push.nil? - @protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll + unless developers_can_push.nil? + protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll end end end diff --git a/app/services/task_list_toggle_service.rb b/app/services/task_list_toggle_service.rb index af24bc0524c..f6602a35033 100644 --- a/app/services/task_list_toggle_service.rb +++ b/app/services/task_list_toggle_service.rb @@ -67,6 +67,6 @@ class TaskListToggleService # When using CommonMark, we should be able to use the embedded `sourcepos` attribute to # target the exact line in the DOM. def get_html_checkbox(html) - html.css(".task-list-item[data-sourcepos^='#{line_number}:'] > input.task-list-item-checkbox").first + html.css(".task-list-item[data-sourcepos^='#{line_number}:'] input.task-list-item-checkbox").first end end diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb index db03ba8756f..e50840a9158 100644 --- a/app/services/users/activity_service.rb +++ b/app/services/users/activity_service.rb @@ -26,12 +26,15 @@ module Users def record_activity return if Gitlab::Database.read_only? + today = Date.today + + return if @user.last_activity_on == today + lease = Gitlab::ExclusiveLease.new("acitvity_service:#{@user.id}", timeout: LEASE_TIMEOUT) return unless lease.try_obtain - @user.update_attribute(:last_activity_on, Date.today) - Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@user.id} (username: #{@user.username})") + @user.update_attribute(:last_activity_on, today) end end end diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml index ddc1cdb24b5..26fd34347ec 100644 --- a/app/views/layouts/_mailer.html.haml +++ b/app/views/layouts/_mailer.html.haml @@ -49,7 +49,7 @@ %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } %tbody %tr.line - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" } %tr.header %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } = header_logo diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml index 2dfc787b7a8..348ce18b122 100644 --- a/app/views/layouts/header/_empty.html.haml +++ b/app/views/layouts/header/_empty.html.haml @@ -1,4 +1,2 @@ %header.navbar.fixed-top.navbar-empty - .container - .mx-auto - = brand_header_logo + = brand_header_logo diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb index 7c66ac046ea..9ec8bcca4f3 100644 --- a/app/workers/reactive_caching_worker.rb +++ b/app/workers/reactive_caching_worker.rb @@ -6,7 +6,7 @@ class ReactiveCachingWorker # rubocop: disable CodeReuse/ActiveRecord def perform(class_name, id, *args) klass = begin - Kernel.const_get(class_name) + class_name.constantize rescue NameError nil end diff --git a/bin/secpick b/bin/secpick index 8f956d300a7..d01304285b6 100755 --- a/bin/secpick +++ b/bin/secpick @@ -45,9 +45,7 @@ module Secpick def git_commands ["git fetch #{@options[:remote]} #{stable_branch}", - "git checkout #{stable_branch}", - "git pull #{@options[:remote]} #{stable_branch}", - "git checkout -B #{source_branch}", + "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch}", "git cherry-pick #{@options[:sha]}", "git push #{@options[:remote]} #{source_branch}", "git checkout #{original_branch}"] @@ -55,10 +53,10 @@ module Secpick def gitlab_params { + issuable_template: 'Security Release', merge_request: { source_branch: source_branch, - target_branch: stable_branch, - description: '/label ~security' + target_branch: stable_branch } } end diff --git a/changelogs/unreleased/24642-activity_service_optimization.yml b/changelogs/unreleased/24642-activity_service_optimization.yml new file mode 100644 index 00000000000..bdfa769959e --- /dev/null +++ b/changelogs/unreleased/24642-activity_service_optimization.yml @@ -0,0 +1,5 @@ +--- +title: Optimize Redis usage in User::ActivityService +merge_request: 25005 +author: +type: performance diff --git a/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml new file mode 100644 index 00000000000..1af49fb6a2c --- /dev/null +++ b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Require only one parameter when updating a wiki' +merge_request: 25191 +author: Robert Schilling +type: fixed diff --git a/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml b/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml new file mode 100644 index 00000000000..1c739130fcc --- /dev/null +++ b/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Expose if the current user can merge a MR' +merge_request: 25207 +author: Robert Schilling +type: added diff --git a/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml b/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml new file mode 100644 index 00000000000..8393cb9d282 --- /dev/null +++ b/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml @@ -0,0 +1,5 @@ +--- +title: Add API endpoint to get a commit's GPG signature +merge_request: 25032 +author: +type: added diff --git a/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml new file mode 100644 index 00000000000..3c8b58f3001 --- /dev/null +++ b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Expose text_color for project and group labels' +merge_request: 25172 +author: Robert Schilling +type: added diff --git a/changelogs/unreleased/56237-api-truncated-commit-title.yml b/changelogs/unreleased/56237-api-truncated-commit-title.yml new file mode 100644 index 00000000000..1a48d0fda1b --- /dev/null +++ b/changelogs/unreleased/56237-api-truncated-commit-title.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Expose full commit title' +merge_request: 25189 +author: Robert Schilling +type: fixed diff --git a/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml b/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml new file mode 100644 index 00000000000..ae2d9e18e0b --- /dev/null +++ b/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Indicate if label is a project label' +merge_request: 25219 +author: Robert Schilling +type: added diff --git a/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml new file mode 100644 index 00000000000..2e0ae9c3732 --- /dev/null +++ b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Fix docs and parameters for hangouts-chat service' +merge_request: 25180 +author: Robert Schilling +type: fixed diff --git a/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml b/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml new file mode 100644 index 00000000000..3146d07db3d --- /dev/null +++ b/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml @@ -0,0 +1,5 @@ +--- +title: Return bottom border on MR Tabs +merge_request: !25198 +author: +type: fixed diff --git a/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml new file mode 100644 index 00000000000..6be6a2115b9 --- /dev/null +++ b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml @@ -0,0 +1,5 @@ +--- +title: Add support for FTP assets for releases +merge_request: 25071 +author: Robert Schilling +type: added diff --git a/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml b/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml new file mode 100644 index 00000000000..9d9158ca4af --- /dev/null +++ b/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml @@ -0,0 +1,5 @@ +--- +title: Do not show file templates when creating a new directory in WebIDE +merge_request: !25119 +author: +type: fixed diff --git a/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml new file mode 100644 index 00000000000..f7d6a6c4863 --- /dev/null +++ b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml @@ -0,0 +1,5 @@ +--- +title: Fix import_jid error on project import +merge_request: 25239 +author: +type: fixed diff --git a/changelogs/unreleased/57589-update-workhorse.yml b/changelogs/unreleased/57589-update-workhorse.yml new file mode 100644 index 00000000000..525913bba4c --- /dev/null +++ b/changelogs/unreleased/57589-update-workhorse.yml @@ -0,0 +1,5 @@ +--- +title: Update Workhorse to v8.3.1 +merge_request: +author: +type: other diff --git a/changelogs/unreleased/add-title-attribute-to-file-row.yml b/changelogs/unreleased/add-title-attribute-to-file-row.yml new file mode 100644 index 00000000000..c68d3d544e7 --- /dev/null +++ b/changelogs/unreleased/add-title-attribute-to-file-row.yml @@ -0,0 +1,5 @@ +--- +title: add title attribute to display file name +merge_request: 25154 +author: Satoshi Nakamatsu @satoshicano +type: added diff --git a/changelogs/unreleased/filter-note-parameters.yml b/changelogs/unreleased/filter-note-parameters.yml new file mode 100644 index 00000000000..fca2a394820 --- /dev/null +++ b/changelogs/unreleased/filter-note-parameters.yml @@ -0,0 +1,5 @@ +--- +title: Include note in the Rails filter_parameters configuration +merge_request: 25238 +author: +type: other diff --git a/changelogs/unreleased/fix_-56347.yml b/changelogs/unreleased/fix_-56347.yml new file mode 100644 index 00000000000..1d03ed8864c --- /dev/null +++ b/changelogs/unreleased/fix_-56347.yml @@ -0,0 +1,5 @@ +--- +title: Fix overlapping empty-header logo +merge_request: 24868 +author: Jonas L. +type: fixed diff --git a/changelogs/unreleased/ravlen-fix-spaces-unicode.yml b/changelogs/unreleased/ravlen-fix-spaces-unicode.yml new file mode 100644 index 00000000000..fbcbdc53cfe --- /dev/null +++ b/changelogs/unreleased/ravlen-fix-spaces-unicode.yml @@ -0,0 +1,5 @@ +--- +title: Correct non-standard unicode spaces to regular unicode +merge_request: 24795 +author: Marcel Amirault +type: other diff --git a/changelogs/unreleased/sh-fix-board-user-assigns.yml b/changelogs/unreleased/sh-fix-board-user-assigns.yml new file mode 100644 index 00000000000..89c228107f0 --- /dev/null +++ b/changelogs/unreleased/sh-fix-board-user-assigns.yml @@ -0,0 +1,5 @@ +--- +title: Fix 403 errors when adding an assignee list in project boards +merge_request: 25263 +author: +type: fixed diff --git a/changelogs/unreleased/sh-log-rails-queue-duration.yml b/changelogs/unreleased/sh-log-rails-queue-duration.yml new file mode 100644 index 00000000000..89390aef108 --- /dev/null +++ b/changelogs/unreleased/sh-log-rails-queue-duration.yml @@ -0,0 +1,5 @@ +--- +title: Log queue duration in production_json.log +merge_request: 25075 +author: +type: other diff --git a/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml new file mode 100644 index 00000000000..fbab898b799 --- /dev/null +++ b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: 'Support `only: changes:` on MR pipelines' +merge_request: 24490 +author: Hiroyuki Sato +type: added diff --git a/changelogs/unreleased/syntax-highlighting-again.yml b/changelogs/unreleased/syntax-highlighting-again.yml new file mode 100644 index 00000000000..14223fc0927 --- /dev/null +++ b/changelogs/unreleased/syntax-highlighting-again.yml @@ -0,0 +1,5 @@ +--- +title: Fix suggested changes syntax highlighting +merge_request: 25116 +author: +type: fixed diff --git a/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml b/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml new file mode 100644 index 00000000000..7a6bda1580d --- /dev/null +++ b/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml @@ -0,0 +1,5 @@ +--- +title: Fixed alignment of changed icon in Web IDE +merge_request: +author: +type: fixed diff --git a/config/application.rb b/config/application.rb index 92a3d031c63..49e7f5836e4 100644 --- a/config/application.rb +++ b/config/application.rb @@ -97,7 +97,7 @@ module Gitlab # # NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not # introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182 - config.filter_parameters += [/token$/, /password/, /secret/, /key$/] + config.filter_parameters += [/token$/, /password/, /secret/, /key$/, /^note$/, /^text$/] config.filter_parameters += %i( certificate encrypted_key diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb index c897bc30e76..164954d1293 100644 --- a/config/initializers/lograge.rb +++ b/config/initializers/lograge.rb @@ -23,7 +23,8 @@ unless Sidekiq.server? remote_ip: event.payload[:remote_ip], user_id: event.payload[:user_id], username: event.payload[:username], - ua: event.payload[:ua] + ua: event.payload[:ua], + queue_duration: event.payload[:queue_duration] } gitaly_calls = Gitlab::GitalyClient.get_request_count diff --git a/config/webpack.config.js b/config/webpack.config.js index fdf179b007a..cf9e77d2424 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -150,6 +150,7 @@ module.exports = { loader: 'worker-loader', options: { name: '[name].[hash:8].worker.js', + inline: IS_DEV_SERVER, }, }, 'babel-loader', diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile index 530c6638653..63b2f6f5c5c 100644 --- a/danger/changelog/Dangerfile +++ b/danger/changelog/Dangerfile @@ -16,16 +16,12 @@ consider adding any of the %<labels>s labels. #{SEE_DOC} MSG -def ee? - ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md') -end - def ee_changelog?(changelog_path) changelog_path =~ /unreleased-ee/ end def ce_port_changelog?(changelog_path) - ee? && !ee_changelog?(changelog_path) + helper.ee? && !ee_changelog?(changelog_path) end def check_changelog(path) diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile index 188331cc87c..dd0e3f6deb6 100644 --- a/danger/documentation/Dangerfile +++ b/danger/documentation/Dangerfile @@ -1,17 +1,6 @@ # frozen_string_literal: true -# All the files/directories that should be reviewed by the Docs team. -DOCS_FILES = [ - 'doc/' -].freeze - -def docs_paths_requiring_review(files) - files.select do |file| - DOCS_FILES.any? { |pattern| file.start_with?(pattern) } - end -end - -docs_paths_to_review = docs_paths_requiring_review(helper.all_changed_files) +docs_paths_to_review = helper.changes_by_category[:documentation] unless docs_paths_to_review.empty? message 'This merge request adds or changes files that require a ' \ diff --git a/danger/plugins/helper.rb b/danger/plugins/helper.rb index f4eb9119266..581c0720083 100644 --- a/danger/plugins/helper.rb +++ b/danger/plugins/helper.rb @@ -1,34 +1,15 @@ # frozen_string_literal: true +require 'net/http' +require 'yaml' + +require_relative '../../lib/gitlab/danger/helper' + module Danger - # Common helper functions for our danger scripts - # If we find ourselves repeating code in our danger files, we might as well put them in here. + # Common helper functions for our danger scripts. See Gitlab::Danger::Helper + # for more details class Helper < Plugin - # Returns a list of all files that have been added, modified or renamed. - # `git.modified_files` might contain paths that already have been renamed, - # so we need to remove them from the list. - # - # Considering these changes: - # - # - A new_file.rb - # - D deleted_file.rb - # - M modified_file.rb - # - R renamed_file_before.rb -> renamed_file_after.rb - # - # it will return - # ``` - # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ] - # ``` - # - # @return [Array<String>] - def all_changed_files - Set.new - .merge(git.added_files.to_a) - .merge(git.modified_files.to_a) - .merge(git.renamed_files.map { |x| x[:after] }) - .subtract(git.renamed_files.map { |x| x[:before] }) - .to_a - .sort - end + # Put the helper code somewhere it can be tested + include Gitlab::Danger::Helper end end diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile new file mode 100644 index 00000000000..5c3d7a4ca49 --- /dev/null +++ b/danger/roulette/Dangerfile @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +MESSAGE = <<MARKDOWN +## Reviewer roulette + +Changes that require review have been detected! A merge request is normally +reviewed by both a reviewer and a maintainer in its primary category (e.g. +~frontend or ~backend), and by a maintainer in all other categories. +MARKDOWN + +CATEGORY_TABLE_HEADER = <<MARKDOWN + +To spread load more evenly across eligible reviewers, Danger has randomly picked +a candidate for each review slot. Feel free to override this selection if you +think someone else would be better-suited, or the chosen person is unavailable. + +Once you've decided who will review this merge request, mention them as you +normally would! Danger does not (yet?) automatically notify them for you. + +| Category | Reviewer | Maintainer | +| -------- | -------- | ---------- | +MARKDOWN + +UNKNOWN_FILES_MESSAGE = <<MARKDOWN + +These files couldn't be categorised, so Danger was unable to suggest a reviewer. +Please consider creating a merge request to +[add support](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/danger/helper.rb) +for them. +MARKDOWN + +def spin(team, project, category) + reviewers = team.select { |member| member.reviewer?(project, category) } + maintainers = team.select { |member| member.maintainer?(project, category) } + + # TODO: filter out people who are currently not in the office + # TODO: take CODEOWNERS into account? + + reviewer = reviewers[rand(reviewers.size)] + maintainer = maintainers[rand(maintainers.size)] + + "| #{helper.label_for_category(category)} | #{reviewer&.markdown_name} | #{maintainer&.markdown_name} |" +end + +def build_list(items) + list = items.map { |filename| "* `#{filename}`" }.join("\n") + + if items.size > 10 + "\n<details>\n\n#{list}\n\n</details>" + else + list + end +end + +changes = helper.changes_by_category +categories = changes.keys - [:unknown] + +unless changes.empty? + team = + begin + helper.project_team + rescue => err + warn("Reviewer roulette failed to load team data: #{err.message}") + [] + end + + # Exclude the MR author from the team for selection purposes + team.delete_if { |teammate| teammate.username == gitlab.mr_author } + + project = helper.project_name + unknown = changes.fetch(:unknown, []) + + rows = categories.map { |category| spin(team, project, category) } + + markdown(MESSAGE) + markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty? + markdown(UNKNOWN_FILES_MESSAGE + build_list(unknown)) unless unknown.empty? +end diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 698f4caab3a..36dee75bd44 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -23,12 +23,13 @@ requests from the API are logged to a separate file in `api_json.log`. Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example: ```json -{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76} +{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"queue_duration": 112.47} ``` In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data: -1. `duration`: the total time taken to retrieve the request +1. `duration`: total time in milliseconds taken to retrieve the request +1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse 1. `view`: total time taken inside the Rails views 1. `db`: total time to retrieve data from the database 1. `gitaly_calls`: total number of calls made to Gitaly @@ -91,6 +92,8 @@ This entry above shows an access to an internal endpoint to check whether an associated SSH key can download the project in question via a `git fetch` or `git clone`. In this example, we see: +1. `duration`: total time in milliseconds taken to retrieve the request +1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse 1. `method`: The HTTP method used to make the request 1. `path`: The relative path of the query 1. `params`: Key-value pairs passed in a query string or HTTP body. Sensitive parameters (e.g. passwords, tokens, etc.) are filtered out. diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 10ae8c7dedf..5c809f25fbd 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -11,7 +11,7 @@ description: 'Learn how to administer GitLab Pages.' > - This guide is for Omnibus GitLab installations. If you have installed > GitLab from source, follow the [Pages source installation document](source.md). > - To learn how to use GitLab Pages, read the [user documentation][pages-userguide]. -> - Does NOT support subgroups. See [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) for more information and status. +> - Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8. This document describes how to set up the _latest_ GitLab Pages feature. Make sure to read the [changelog](#changelog) if you are upgrading to a new GitLab diff --git a/doc/api/commits.md b/doc/api/commits.md index 14742f034e0..7bfea0498e4 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -656,6 +656,46 @@ Example response: ] ``` +## Get GPG signature of a commit + +Get the [GPG signature from a commit](../user/project/repository/gpg_signed_commits/index.md), +if it is signed. For unsigned commits, it results in a 404 response. + +``` +GET /projects/:id/repository/commits/:sha/signature +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +| `sha` | string | yes | The commit hash or name of a repository branch or tag | + +```bash +curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/repository/commits/da738facbc19eb2fc2cef57c49be0e6038570352/signature" +``` + +Example response if commit is signed: + +```json +{ + "gpg_key_id": 1, + "gpg_key_primary_keyid": "8254AAB3FBD54AC9", + "gpg_key_user_name": "John Doe", + "gpg_key_user_email": "johndoe@example.com", + "verification_status": "verified", + "gpg_key_subkey_id": null +} +``` + +Example response if commit is unsigned: +```json +{ + "message": "404 GPG Signature Not Found" +} +``` + [ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit" [ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047 [ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026 diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md index d4715dec192..3d4b099b49e 100644 --- a/doc/api/group_labels.md +++ b/doc/api/group_labels.md @@ -28,6 +28,7 @@ Example response: "id": 7, "name": "bug", "color": "#FF0000", + "text_color" : "#FFFFFF", "description": null, "open_issues_count": 0, "closed_issues_count": 0, @@ -38,6 +39,7 @@ Example response: "id": 4, "name": "feature", "color": "#228B22", + "text_color" : "#FFFFFF", "description": null, "open_issues_count": 0, "closed_issues_count": 0, @@ -73,6 +75,7 @@ Example response: "id": 9, "name": "Feature Proposal", "color": "#FFA500", + "text_color" : "#FFFFFF", "description": "Describes new ideas", "open_issues_count": 0, "closed_issues_count": 0, @@ -108,6 +111,7 @@ Example response: "id": 9, "name": "Feature Idea", "color": "#FFA500", + "text_color" : "#FFFFFF", "description": "Describes new ideas", "open_issues_count": 0, "closed_issues_count": 0, @@ -158,6 +162,7 @@ Example response: "id": 9, "name": "Feature Idea", "color": "#FFA500", + "text_color" : "#FFFFFF", "description": "Describes new ideas", "open_issues_count": 0, "closed_issues_count": 0, @@ -192,6 +197,7 @@ Example response: "id": 9, "name": "Feature Idea", "color": "#FFA500", + "text_color" : "#FFFFFF", "description": "Describes new ideas", "open_issues_count": 0, "closed_issues_count": 0, diff --git a/doc/api/labels.md b/doc/api/labels.md index aec1a2c7592..eec55bff294 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -24,56 +24,66 @@ Example response: "id" : 1, "name" : "bug", "color" : "#d9534f", + "text_color" : "#FFFFFF", "description": "Bug reported by user", "open_issues_count": 1, "closed_issues_count": 0, "open_merge_requests_count": 1, "subscribed": false, - "priority": 10 + "priority": 10, + "is_project_label": true }, { "id" : 4, "color" : "#d9534f", + "text_color" : "#FFFFFF", "name" : "confirmed", "description": "Confirmed issue", "open_issues_count": 2, "closed_issues_count": 5, "open_merge_requests_count": 0, "subscribed": false, - "priority": null + "priority": null, + "is_project_label": true }, { "id" : 7, "name" : "critical", "color" : "#d9534f", + "text_color" : "#FFFFFF", "description": "Critical issue. Need fix ASAP", "open_issues_count": 1, "closed_issues_count": 3, "open_merge_requests_count": 1, "subscribed": false, - "priority": null + "priority": null, + "is_project_label": true }, { "id" : 8, "name" : "documentation", "color" : "#f0ad4e", + "text_color" : "#FFFFFF", "description": "Issue about documentation", "open_issues_count": 1, "closed_issues_count": 0, "open_merge_requests_count": 2, "subscribed": false, - "priority": null + "priority": null, + "is_project_label": false }, { "id" : 9, "color" : "#5cb85c", + "text_color" : "#FFFFFF", "name" : "enhancement", "description": "Enhancement proposal", "open_issues_count": 1, "closed_issues_count": 0, "open_merge_requests_count": 1, "subscribed": true, - "priority": null + "priority": null, + "is_project_label": true } ] ``` @@ -105,12 +115,14 @@ Example response: "id" : 10, "name" : "feature", "color" : "#5843AD", + "text_color" : "#FFFFFF", "description":null, "open_issues_count": 0, "closed_issues_count": 0, "open_merge_requests_count": 0, "subscribed": false, - "priority": null + "priority": null, + "is_project_label": true } ``` @@ -161,12 +173,14 @@ Example response: "id" : 8, "name" : "docs", "color" : "#8E44AD", + "text_color" : "#FFFFFF", "description": "Documentation", "open_issues_count": 1, "closed_issues_count": 0, "open_merge_requests_count": 2, "subscribed": false, - "priority": null + "priority": null, + "is_project_label": true } ``` @@ -196,12 +210,14 @@ Example response: "id" : 1, "name" : "bug", "color" : "#d9534f", + "text_color" : "#FFFFFF", "description": "Bug reported by user", "open_issues_count": 1, "closed_issues_count": 0, "open_merge_requests_count": 1, "subscribed": true, - "priority": null + "priority": null, + "is_project_label": true } ``` diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index d58cd45538d..ffeceabc680 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -435,6 +435,9 @@ Parameters: "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, + "user" : { + "can_merge" : false + } "assignee": { "id": 1, "name": "Administrator", diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md index 943109a3ea9..61f4ec130fa 100644 --- a/doc/api/releases/index.md +++ b/doc/api/releases/index.md @@ -14,7 +14,7 @@ GET /projects/:id/releases | Attribute | Type | Required | Description | | ------------- | -------------- | -------- | --------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | Example request: @@ -160,7 +160,7 @@ GET /projects/:id/releases/:tag_name | Attribute | Type | Required | Description | | ------------- | -------------- | -------- | --------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | | `tag_name` | string | yes | The tag where the release will be created from. | Example request: @@ -239,10 +239,10 @@ POST /projects/:id/releases | Attribute | Type | Required | Description | | ------------- | -------------- | -------- | --------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | | `name` | string | yes | The release name. | | `tag_name` | string | yes | The tag where the release will be created from. | -| `description` | string | yes | The description of the release. You can use [markdown](../user/markdown.md). | +| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). | | `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. | | `assets:links`| array of hash | no | An array of assets links. | | `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. | @@ -331,10 +331,10 @@ PUT /projects/:id/releases/:tag_name | Attribute | Type | Required | Description | | ------------- | -------------- | -------- | --------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | | `tag_name` | string | yes | The tag where the release will be created from. | | `name` | string | no | The release name. | -| `description` | string | no | The description of the release. You can use [markdown](../user/markdown.md). | +| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). | Example request: @@ -412,7 +412,7 @@ DELETE /projects/:id/releases/:tag_name | Attribute | Type | Required | Description | | ------------- | -------------- | -------- | --------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | | `tag_name` | string | yes | The tag where the release will be created from. | Example request: diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md index ae99f3bd8b6..fd7b9d6e6e2 100644 --- a/doc/api/releases/links.md +++ b/doc/api/releases/links.md @@ -3,6 +3,7 @@ > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7. Using this API you can manipulate GitLab's [Release](../../user/project/releases/index.md) links. For manipulating other Release assets, see [Release API](index.md). +GitLab supports links links to `http`, `https`, and `ftp` assets. ## Get links diff --git a/doc/api/services.md b/doc/api/services.md index 2a8ce39e570..5d5aa3e5b3e 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -412,7 +412,7 @@ Google GSuite team collaboration tool. Set Hangouts Chat service for a project. ``` -PUT /projects/:id/services/hangouts_chat +PUT /projects/:id/services/hangouts-chat ``` >**Note:** Specific event parameters (e.g. `push_events` flag) were [introduced in v10.4][11435] @@ -438,7 +438,7 @@ Parameters: Delete Hangouts Chat service for a project. ``` -DELETE /projects/:id/services/hangouts_chat +DELETE /projects/:id/services/hangouts-chat ``` ### Get Hangouts Chat service settings @@ -446,7 +446,7 @@ DELETE /projects/:id/services/hangouts_chat Get Hangouts Chat service settings for a project. ``` -GET /projects/:id/services/hangouts_chat +GET /projects/:id/services/hangouts-chat ``` ## Irker (IRC gateway) diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md index 6499413baf0..474a481836a 100644 --- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md +++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md @@ -112,7 +112,7 @@ on GitLab CI/CD. To set the environment variables, navigate to your project's ![Variable Settings in GitLab](img/cloud_foundry_variables.png) Once set up, GitLab CI/CD will deploy your app to CF at every push to your -repository's deafult branch. To see the build logs or watch your builds running +repository's default branch. To see the build logs or watch your builds running live, navigate to **CI/CD > Pipelines**. CAUTION: **Caution:** diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 984878b6c9b..8c8a31e9323 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -423,10 +423,28 @@ connected with merge requests yet, and because GitLab is creating pipelines before an user can create a merge request we don't know a target branch at this point. -Without a target branch, it is not possible to know what the common ancestor is, -thus we always create a job in that case. This feature works best for stable -branches like `master` because in that case GitLab uses the previous commit -that is present in a branch to compare against the latest SHA that was pushed. +#### Using `changes` with `merge_requests` + +With [pipelines for merge requests](../merge_request_pipelines/index.md), +make it possible to define if a job should be created base on files modified +in a merge request. + +For example: + +``` +docker build service one: + script: docker build -t my-service-one-image:$CI_COMMIT_REF_SLUG . + only: + refs: + - merge_requests + changes: + - Dockerfile + - service-one/**/* +``` + +In the scenario above, if you create or update a merge request that changes +either files in `service-one` folder or `Dockerfile`, GitLab creates and triggers +the `docker build service one` job. ## `tags` diff --git a/doc/development/README.md b/doc/development/README.md index d5829e31343..13646cbfe48 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -49,6 +49,7 @@ description: 'Learn how to contribute to GitLab.' - [Working with the GitHub importer](github_importer.md) - [Import/Export development documentation](import_export.md) - [Working with Merge Request diffs](diffs.md) +- [Kubernetes integration guidelines](kubernetes.md) - [Permissions](permissions.md) - [Prometheus metrics](prometheus_metrics.md) - [Guidelines for reusing abstractions](reusing_abstractions.md) diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 25ea2211b64..4e5fb47e331 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -23,6 +23,11 @@ one of the [Merge request coaches][team]. If you need assistance with security scans or comments, feel free to include the Security Team (`@gitlab-com/gl-security`) in the review. +The `danger-review` CI job will randomly pick a reviewer and a maintainer for +each area of the codebase that your merge request seems to touch. It only makes +recommendations - feel free to override it if you think someone else is a better +fit! + Depending on the areas your merge request touches, it must be **approved** by one or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer): diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index cda66447c2c..7a3a8f25c2d 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -112,7 +112,7 @@ table_display_block: true ## Emphasis - Use double asterisks (`**`) to mark a word or text in bold (`**bold**`). -- Use undescore (`_`) for text in italics (`_italic_`). +- Use underscore (`_`) for text in italics (`_italic_`). - Use greater than (`>`) for blockquotes. ## Punctuation diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 3a3cb77f592..86b8972a69e 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -1,7 +1,7 @@ # Frontend Development Guidelines > **Notice:** -We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md) +> We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md) This document describes various guidelines to ensure consistency and quality across GitLab's frontend team. @@ -32,32 +32,41 @@ For our currently-supported browsers, see our [requirements][requirements]. --- ## [Development Process](development_process.md) + How we plan and execute the work on the frontend. ## [Architecture](architecture.md) + How we go about making fundamental design decisions in GitLab's frontend team or make changes to our frontend development guidelines. ## [Testing](../testing_guide/frontend_testing.md) + How we write frontend tests, run the GitLab test suite, and debug test related issues. ## [Design Patterns](design_patterns.md) + Common JavaScript design patterns in GitLab's codebase. ## [Vue.js Best Practices](vue.md) + Vue specific design patterns and practices. ## [Vuex](vuex.md) + Vuex specific design patterns and practices. ## [Axios](axios.md) + Axios specific practices and gotchas. ## [GraphQL](graphql.md) + How to use GraphQL ## [Icons and Illustrations](icons.md) + How we use SVG for our Icons and Illustrations. ## [Components](components.md) @@ -70,7 +79,7 @@ How we use UI components. ### [JavaScript Style Guide](style_guide_js.md) -We use eslint to enforce our JavaScript style guides. Our guide is based on +We use eslint to enforce our JavaScript style guides. Our guide is based on the excellent [Airbnb][airbnb-js-style-guide] style guide with a few small changes. @@ -81,23 +90,26 @@ Our SCSS conventions which are enforced through [scss-lint][scss-lint]. --- ## [Performance](performance.md) + Best practices for monitoring and maximizing frontend performance. --- ## [Security](security.md) + Frontend security practices. --- ## [Accessibility](accessibility.md) + Our accessibility standards and resources. ## [Internationalization (i18n) and Translations](../i18n/externalization.md) + Frontend internationalization support is described in [this document](../i18n/). The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available. - [rails]: http://rubyonrails.org/ [haml]: http://haml.info/ [hamlit]: https://github.com/k0kubun/hamlit @@ -116,6 +128,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the --- ## [DropLab](droplab/droplab.md) + Our internal `DropLab` dropdown library. - [DropLab](droplab/droplab.md) diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md new file mode 100644 index 00000000000..4b2d48903ac --- /dev/null +++ b/doc/development/kubernetes.md @@ -0,0 +1,126 @@ +# Kubernetes integration - development guidelines + +This document provides various guidelines when developing for GitLab's +[Kubernetes integration](../user/project/clusters/index.md). + +## Development + +### Architecture + +Some Kubernetes operations, such as creating restricted project +namespaces are performed on the GitLab Rails application. These +operations are performed using a [client library](#client-library). +These operations will carry an element of risk as the operations will be +run as the same user running the GitLab Rails application, see the +[security](#security) section below. + +Some Kubernetes operations, such as installing cluster applications are +performed on one-off pods on the Kubernetes cluster itself. These +installation pods are currently named `install-<application_name>` and +are created within the `gitlab-managed-apps` namespace. + +In terms of code organization, we generally add objects that represent +Kubernetes resources in +[`lib/gitlab/kubernetes`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/kubernetes). + +### Client library + +We use the [`kubeclient`](https://rubygems.org/gems/kubeclient) gem to +perform Kubernetes API calls. As the `kubeclient` gem does not support +different API Groups (e.g. `apis/rbac.authorization.k8s.io`) from a +single client, we have created a wrapper class, +[`Gitlab::Kubernetes::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb) +that will enable you to achieve this. + +Selected Kubernetes API groups are currently supported. Do add support +for new API groups or methods to +[`Gitlab::Kubernetes::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb) +if you need to use them. New API groups or API group versions can be +added to `SUPPORTED_API_GROUPS` - internally, this will create an +internal client for that group. New methods can be added as a delegation +to the relevant internal client. + +### Performance considerations + +All calls to the Kubernetes API must be in a background process. Do not +perform Kubernetes API calls within a web request as this will block +unicorn and can easily lead to a Denial Of Service (DoS) attack in GitLab as +the Kubernetes cluster response times are outside of our control. + +The easiest way to ensure your calls happen a background process is to +delegate any such work to happen in a [sidekiq +worker](sidekiq_style_guide.md). + +There are instances where you would like to make calls to Kubernetes and +return the response and as such a background worker does not seem to be +a good fit. For such cases you should make use of [reactive +caching](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/models/concerns/reactive_caching.rb). +For example: + +```ruby + def calculate_reactive_cache! + { pods: cluster.platform_kubernetes.kubeclient.get_pods } + end + + def pods + with_reactive_cache do |data| + data[:pods] + end + end +``` + +### Testing + +We have some Webmock stubs in +[`KubernetesHelpers`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/support/helpers/kubernetes_helpers.rb) +which can help with mocking out calls to Kubernetes API in your tests. + +## Security + +### SSRF + +As URLs for Kubernetes clusters are user controlled it is easily +susceptible to Server Side Request Forgery (SSRF) attacks. You should +understand the mitigation strategies if you are adding more API calls to +a cluster. + +Mitigation strategies include: + +1. Not allowing redirects to attacker controller resources: + [`Kubeclient::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb#) + can be configured to disallow any redirects by passing in + `http_max_redirects: 0` as an option. +1. Not exposing error messages: by doing so, we + prevent attackers from triggering errors to expose results from + attacker controlled requests. For example, we do not expose (or store) + raw error messages: + + ```ruby + rescue Kubernetes::HttpError => e + # bad + # app.make_errored!("Kubernetes error: #{e.message}") + + # good + app.make_errored!("Kubernetes error: #{e.error_code}") + ``` + +## Debugging + +Logs related to the Kubernetes integration can be found in +[kubernetes.log](../administration/logs.md#kuberneteslog). On a local +GDK install, this will be present in `log/kubernetes.log`. + +Some services such as +[`Clusters::Applications::InstallService`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/services/clusters/applications/install_service.rb#L18) +rescues `StandardError` which can make it harder to debug issues in an +development environment. The current workaround is to temporarily +comment out the `rescue` in your local development source. + +You can also follow the installation pod logs to debug issues related to +installation. Once the installation/upgrade is underway, wait for the +pod to be created. Then run the following to obtain the pods logs as +they are written: + +```bash +kubectl logs <pod_name> --follow -n gitlab-managed-apps +``` diff --git a/doc/development/new_fe_guide/event_tracking.md b/doc/development/new_fe_guide/event_tracking.md new file mode 100644 index 00000000000..1958f1ce528 --- /dev/null +++ b/doc/development/new_fe_guide/event_tracking.md @@ -0,0 +1,74 @@ +# Event Tracking + +We use [Snowplow](https://github.com/snowplow/snowplow) for tracking custom events. + +## Generic tracking function + +In addition to Snowplow's built-in method for tracking page views, we use a generic tracking function which enables us to selectively apply listeners to events. + +The generic tracking function can be imported in EE-specific JS files as follows: + +```javascript +import { trackEvent } from `ee/stats`; +``` + +This gives the user access to the `trackEvent` method, which takes the following parameters: + +| parameter | type | description | required | +| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `category` | string | Describes the page that you're capturing click events on. Unless infeasible, please use the Rails page attribute `document.body.dataset.page` by default. | true | +| `eventName` | string | Describes the action the user is taking. The first word should always describe the action. For example, clicks should be `click` and activations should be `activate`. Use underscores to describe what was acted on. For example, activating a form field would be `activate_form_input`. Clicking on a dropdown is `click_dropdown`. | true | +| `additionalData` | object | Additional data such as `label`, `property`, and `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). | false | + +Read more about instrumentation and the taxonomy in the [Product Handbook](https://about.gitlab.com/handbook/product/feature-instrumentation). + +### Tracking in `.js` and `.vue` files + +The most simple use case is to add tracking programmatically to an event of interest in Javascript. + +The following example demonstrates how to track a click on a button in Javascript by calling the `trackEvent` method explicitly: + +```javascript +import { trackEvent } from `ee/stats`; + +trackEvent('dashboard:projects:index', 'click_button', { + label: 'create_from_template', + property: 'template_preview', + value: 'rails', +}); +``` + +### Tracking in HAML templates + +Sometimes we want to track clicks for multiple elements on a page. Creating event handlers for all elements could soon turn into a tedious task. + +There's a more convenient solution to this problem. When working with HAML templates, we can add `data-track-*` attributes to elements of interest. This way, all elements that have both `data-track-label` and `data-track-event` attributes assigned get marked for event tracking. All we have to do is call the `bindTrackableContainer` method on a container which allows for better scoping. + +Below is an example of `data-track-*` attributes assigned to a button in HAML: + +```ruby +%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } } +``` + +By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them. + +```javascript +import Stats from 'ee/stats'; + +document.addEventListener('DOMContentLoaded', () => { + Stats.bindTrackableContainer('.my-container', 'category'); +}); +``` + +The second parameter in `bindTrackableContainer` is optional. If omitted, the value of `document.body.dataset.page` will be used as category instead. + +Below is a list of supported `data-track-*` attributes: + +| attribute | description | required | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `data-track-label` | The `label` in `trackEvent` | true | +| `data-track-event` | The `eventName` in `trackEvent` | true | +| `data-track-property` | The `property` in `trackEvent`. If omitted, an empty string will be used as a default value. | false | +| `data-track-value` | The `value` in `trackEvent`. If omitted, this will be `target.value` or empty string. For checkboxes, the default value being tracked will be the element's checked attribute if `data-track-value` is omitted. | false | + +Since Snowplow is an Enterprise Edition feature, it's necessary to create a CE backport when adding `data-track-*` attributes to HAML templates in most cases. diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md index bfcca9cec7b..5fd5af252ef 100644 --- a/doc/development/new_fe_guide/index.md +++ b/doc/development/new_fe_guide/index.md @@ -27,6 +27,10 @@ Learn about all the internal JavaScript modules that make up our frontend. Style guides to keep our code consistent. +## [Event Tracking with Snowplow](event_tracking.md) + +How we use Snowplow to track custom events. + ## [Tips](tips.md) Tips from our frontend team to develop more efficiently and effectively. diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index 3af97717775..85b47f88cc4 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -143,17 +143,21 @@ thousands of unused Docker images.** **How big are the Kubernetes clusters (`review-apps-ce` and `review-apps-ee`)?** > The clusters are currently set up with a single pool of preemptible nodes, - with a minimum of 1 node and a maximum of 100 nodes. + with a minimum of 1 node and a maximum of 50 nodes. **What are the machine running on the cluster?** - > We're currently using `n1-standard-4` (4 vCPUs, 15 GB memory) machines. + > We're currently using `n1-standard-16` (16 vCPUs, 60 GB memory) machines. **How do we secure this from abuse? Apps are open to the world so we need to find a way to limit it to only us.** > This isn't enabled for forks. +## Other resources + +* [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing) + [charts-1068]: https://gitlab.com/charts/gitlab/issues/1068 [gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587 [gitlab:assets:compile]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511610 diff --git a/doc/integration/github.md b/doc/integration/github.md index eca9aa16499..9bb3579dd84 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -21,10 +21,10 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe - Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. - Homepage URL: the URL to your GitLab installation. e.g., `https://gitlab.company.com` - Application description: Fill this in if you wish. - - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth`. Please make sure the port is included if your GitLab instance is not configured on default port. + - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth/github/callback`. Please make sure the port is included if your GitLab instance is not configured on default port. ![Register OAuth App](img/github_register_app.png) - NOTE: Be sure to append `/users/auth` to the end of the callback URL + NOTE: Be sure to append `/users/auth/github/callback` to the end of the callback URL to prevent a [OAuth2 convert redirect](http://tetraph.com/covert_redirect/) vulnerability. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 4976284aea4..74a966f3a17 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -61,6 +61,7 @@ The following table depicts the various user permission levels in a project. | Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ | | Lock issue discussions | | ✓ | ✓ | ✓ | ✓ | | Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | +| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ | | Lock merge request discussions | | | ✓ | ✓ | ✓ | | Create new environments | | | ✓ | ✓ | ✓ | | Stop environments | | | ✓ | ✓ | ✓ | @@ -101,6 +102,7 @@ The following table depicts the various user permission levels in a project. | Manage clusters | | | | ✓ | ✓ | | Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ | | Edit comments (posted by any user) | | | | ✓ | ✓ | +| Manage Error Tracking | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index 83bc79925e1..0a7c5832a2d 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -18,7 +18,7 @@ the second factor of authentication. Once enabled, in addition to supplying your password to login, you'll be prompted to activate your U2F device (usually by pressing a button on it), and it will perform secure authentication on your behalf. -The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend +The U2F workflow is only [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera and Firefox at this point, so we _strongly_ recommend that you set up both methods of two-factor authentication, so you can still access your account from other browsers. diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md index fe4b36062f7..90bb92d2062 100644 --- a/doc/user/project/operations/error_tracking.md +++ b/doc/user/project/operations/error_tracking.md @@ -14,10 +14,14 @@ You may sign up to the cloud hosted <https://sentry.io> or deploy your own [on-p ### Enabling Sentry +NOTE: **Note:** +You will need at least Maintainer [permissions](../../permissions.md) to enable the Sentry integration. + GitLab provides an easy way to connect Sentry to your project: 1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance. 1. [Find or generate](https://docs.sentry.io/api/auth/) a Sentry auth token for your Sentry project. +Make sure to give the token at least the following scopes: `event:read` and `project:read`. 1. Navigate to your project’s **Settings > Operations** and provide the Sentry API URL and auth token. 1. Ensure that the 'Active' checkbox is set. 1. Click **Save changes** for the changes to take effect. @@ -25,6 +29,9 @@ GitLab provides an easy way to connect Sentry to your project: ## Error Tracking List +NOTE: **Note:** +You will need at least Reporter [permissions](../../permissions.md) to view the Error Tracking list. + The Error Tracking list may be found at **Operations > Error Tracking** in your project's sidebar. ![Error Tracking list](img/error_tracking_list.png) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 11f6165fcb4..2de3fb7e080 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -66,7 +66,7 @@ publish any website written directly in plain HTML, CSS, and JavaScript.</p> If you're using GitLab.com, your website will be publicly available to the internet. If you're using self-managed instances (Core, Starter, Premium, or Ultimate), your websites will be published on your own server, according to the -[Pages admin settings](../../../administration/pages/index.md) chosen by your sysdamin, +[Pages admin settings](../../../administration/pages/index.md) chosen by your sysadmin, who can opt for making them public or internal to your server. ### How it works diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md index c7e20f01a75..db19370b97c 100644 --- a/doc/user/project/repository/gpg_signed_commits/index.md +++ b/doc/user/project/repository/gpg_signed_commits/index.md @@ -266,3 +266,7 @@ To remove a GPG key from your account: You can configure your project to reject commits that aren't GPG-signed via [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html). + +## GPG signing API + +Learn how to [get the GPG signature from a commit via API](../../../../api/commits.md#get-gpg-signature-of-a-commit). diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index 9c095da5a4b..3e85e97d7a5 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -50,7 +50,7 @@ review the list of changed files. Click on each file to review the changes and click the tick icon to stage the file. Once you have staged some changes, you can add a commit message and commit the -staged changes. Unstaged changes will not be commited. +staged changes. Unstaged changes will not be committed. ![Commit changes](img/commit_changes.png) diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index a7313082fac..1b9fb504b15 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -2,314 +2,324 @@ # Introduction to GitLab Flow -Version management with git makes branching and merging much easier than older versioning systems such as SVN. -This allows a wide variety of branching strategies and workflows. -Almost all of these are an improvement over the methods used before git. -But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems. -Therefore we propose the GitLab flow as clearly defined set of best practices. -It combines [feature driven development](https://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking. +Git allows a wide variety of branching strategies and workflows. +Because of this, many organizations end up with workflows that are too complicated, not clearly defined, or not integrated with issue tracking systems. +Therefore, we propose GitLab flow as a clearly defined set of best practices. +It combines [feature-driven development](https://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](https://martinfowler.com/bliki/FeatureBranch.html) with issue tracking. -Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow. -This article describes the GitLab flow that integrates the git workflow with an issue tracking system. -It offers a simple, transparent and effective way to work with git. +Organizations coming to Git from other version control systems frequently find it hard to develop a productive workflow. +This article describes GitLab flow, which integrates the Git workflow with an issue tracking system. +It offers a simple, transparent, and effective way to work with Git. ![Four stages (working copy, index, local repo, remote repo) and three steps between them](four_stages.png) -When converting to git you have to get used to the fact that there are three steps before a commit is shared with colleagues. -Most version control systems have only one step, committing from the working copy to a shared server. -In git you add files from the working copy to the staging area. After that you commit them to the local repo. +When converting to Git, you have to get used to the fact that it takes three steps to share a commit with colleagues. +Most version control systems have only one step: committing from the working copy to a shared server. +In Git, you add files from the working copy to the staging area. After that, you commit them to your local repo. The third step is pushing to a shared remote repository. -After getting used to these three steps the branching model becomes the challenge. +After getting used to these three steps, the next challenge is the branching model. -![Multiple long running branches and merging in all directions](messy_flow.png) +![Multiple long-running branches and merging in all directions](messy_flow.png) -Since many organizations new to git have no conventions how to work with it, it can quickly become a mess. -The biggest problem they run into is that many long running branches that each contain part of the changes are around. -People have a hard time figuring out which branch they should develop on or deploy to production. -Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html). -We think there is still room for improvement and will detail a set of practices we call GitLab flow. +Since many organizations new to Git have no conventions for how to work with it, their repositories can quickly become messy. +The biggest problem is that many long-running branches emerge that all contain part of the changes. +People have a hard time figuring out which branch has the latest code, or which branch to deploy to production. +Frequently, the reaction to this problem is to adopt a standardized pattern such as [Git flow](https://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html). +We think there is still room for improvement. In this document, we describe a set of practices we call GitLab flow. ## Git flow and its problems ![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png) -Git flow was one of the first proposals to use git branches and it has gotten a lot of attention. -It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes. -The development happens on the develop branch, moves to a release branch and is finally merged into the master branch. -Git flow is a well defined standard but its complexity introduces two problems. -The first problem is that developers must use the develop branch and not master, master is reserved for code that is released to production. -It is a convention to call your default branch master and to mostly branch from and merge to this. -Since most tools automatically make the master branch the default one and display that one by default it is annoying to have to switch to another one. -The second problem of git flow is the complexity introduced by the hotfix and release branches. +Git flow was one of the first proposals to use Git branches, and it has received a lot of attention. +It suggests a `master` branch and a separate `develop` branch, as well as supporting branches for features, releases, and hotfixes. +The development happens on the `develop` branch, moves to a release branch, and is finally merged into the `master` branch. + +Git flow is a well-defined standard, but its complexity introduces two problems. +The first problem is that developers must use the `develop` branch and not `master`. `master` is reserved for code that is released to production. +It is a convention to call your default branch `master` and to mostly branch from and merge to this. +Since most tools automatically use the `master` branch as the default, it is annoying to have to switch to another branch. + +The second problem of Git flow is the complexity introduced by the hotfix and release branches. These branches can be a good idea for some organizations but are overkill for the vast majority of them. -Nowadays most organizations practice continuous delivery which means that your default branch can be deployed. -This means that hotfix and release branches can be prevented including all the ceremony they introduce. +Nowadays, most organizations practice continuous delivery, which means that your default branch can be deployed. +Continuous delivery removes the need for hotfix and release branches, including all the ceremony they introduce. An example of this ceremony is the merging back of release branches. Though specialized tools do exist to solve this, they require documentation and add complexity. -Frequently developers make a mistake and for example changes are only merged into master and not into the develop branch. -The root cause of these errors is that git flow is too complex for most of the use cases. -And doing releases doesn't automatically mean also doing hotfixes. +Frequently, developers make mistakes such as merging changes only into `master` and not into the `develop` branch. +The reason for these errors is that Git flow is too complicated for most use cases. +For example, many projects do releases but don't need to do hotfixes. ## GitHub flow as a simpler alternative ![Master branch with feature branches merged in](github_flow.png) -In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html). -This flow has only feature branches and a master branch. -This is very simple and clean, many organizations have adopted it with great success. -Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches. -Merging everything into the master branch and deploying often means you minimize the amount of code in 'inventory' which is in line with the lean and continuous delivery best practices. -But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues. -With GitLab flow we offer additional guidance for these questions. +In reaction to Git flow, GitHub created a simpler alternative. +[GitHub flow](https://guides.github.com/introduction/flow/index.html) has only feature branches and a `master` branch. +This flow is clean and straightforward, and many organizations have adopted it with great success. +Atlassian recommends [a similar strategy](https://www.atlassian.com/blog/archives/simple-git-workflow-simple), although they rebase feature branches. +Merging everything into the `master` branch and frequently deploying means you minimize the amount of unreleased code, which is in line with lean and continuous delivery best practices. +However, this flow still leaves a lot of questions unanswered regarding deployments, environments, releases, and integrations with issues. +With GitLab flow, we offer additional guidance for these questions. ## Production branch with GitLab flow -![Master branch and production branch with arrow that indicate deployments](production_branch.png) +![Master branch and production branch with an arrow that indicates a deployment](production_branch.png) -GitHub flow does assume you are able to deploy to production every time you merge a feature branch. -This is possible for e.g. SaaS applications, but there are many cases where this is not possible. -One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation. -Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times. -In these cases you can make a production branch that reflects the deployed code. -You can deploy a new version by merging in master to the production branch. -If you need to know what code is in production you can just checkout the production branch to see. +GitHub flow assumes you can deploy to production every time you merge a feature branch. +While this is possible in some cases, such as SaaS applications, there are many cases where this is not possible. +One case is where you don't control the timing of a release, for example, an iOS application that is released when it passes App Store validation. +Another case is when you have deployment windows — for example, workdays from 10 AM to 4 PM when the operations team is at full capacity — but you also merge code at other times. +In these cases, you can make a production branch that reflects the deployed code. +You can deploy a new version by merging `master` into the production branch. +If you need to know what code is in production, you can just checkout the production branch to see. The approximate time of deployment is easily visible as the merge commit in the version control system. This time is pretty accurate if you automatically deploy your production branch. -If you need a more exact time you can have your deployment script create a tag on each deployment. -This flow prevents the overhead of releasing, tagging and merging that is common to git flow. +If you need a more exact time, you can have your deployment script create a tag on each deployment. +This flow prevents the overhead of releasing, tagging, and merging that happens with Git flow. ## Environment branches with GitLab flow ![Multiple branches with the code cascading from one to another](environment_branches.png) -It might be a good idea to have an environment that is automatically updated to the master branch. -Only in this case, the name of this environment might differ from the branch name. -Suppose you have a staging environment, a pre-production environment and a production environment. -In this case the master branch is deployed on staging. When someone wants to deploy to pre-production they create a merge request from the master branch to the pre-production branch. -And going live with code happens by merging the pre-production branch into the production branch. -This workflow where commits only flow downstream ensures that everything has been tested on all environments. -If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch. -If master is good to go (it should be if you are practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches. -If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches. +It might be a good idea to have an environment that is automatically updated to the `master` branch. +Only, in this case, the name of this environment might differ from the branch name. +Suppose you have a staging environment, a pre-production environment, and a production environment. +In this case, deploy the `master` branch to staging. +To deploy to pre-production, create a merge request from the `master` branch to the pre-production branch. +Go live by merging the pre-production branch into the production branch. +This workflow, where commits only flow downstream, ensures that everything is tested in all environments. +If you need to cherry-pick a commit with a hotfix, it is common to develop it on a feature branch and merge it into `master` with a merge request. +In this case, do not delete the feature branch yet. +If `master` passes automatic testing, you then merge the feature branch into the other branches. +If this is not possible because more manual testing is required, you can send merge requests from the feature branch to the downstream branches. ## Release branches with GitLab flow ![Master and multiple release branches that vary in length with cherry-picks from master](release_branches.png) -Only in case you need to release software to the outside world you need to work with release branches. -In this case, each branch contains a minor version (2-3-stable, 2-4-stable, etc.). -The stable branch uses master as a starting point and is created as late as possible. -By branching as late as possible you minimize the time you have to apply bug fixes to multiple branches. -After a release branch is announced, only serious bug fixes are included in the release branch. -If possible these bug fixes are first merged into master and then cherry-picked into the release branch. -This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases. -This is called an 'upstream first' policy that is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo). -Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag. +You only need to work with release branches if you need to release software to the outside world. +In this case, each branch contains a minor version, for example, 2-3-stable, 2-4-stable, etc. +Create stable branches using `master` as a starting point, and branch as late as possible. +By doing this, you minimize the length of time during which you have to apply bug fixes to multiple branches. +After announcing a release branch, only add serious bug fixes to the branch. +If possible, first merge these bug fixes into `master`, and then cherry-pick them into the release branch. +If you start by merging into the release branch, you might forget to cherry-pick them into `master`, and then you'd encounter the same bug in subsequent releases. +Merging into `master` and then cherry-picking into release is called an "upstream first" policy, which is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/en/blog/a-community-for-using-openstack-with-red-hat-rdo). +Every time you include a bug fix in a release branch, increase the patch version (to comply with [Semantic Versioning](https://semver.org/)) by setting a new tag. Some projects also have a stable branch that points to the same commit as the latest released branch. -In this flow it is not common to have a production branch (or git flow master branch). +In this flow, it is not common to have a production branch (or Git flow `master` branch). ## Merge/pull requests with GitLab flow -![Merge request with line comments](mr_inline_comments.png) +![Merge request with inline comments](mr_inline_comments.png) -Merge or pull requests are created in a git management application and ask an assigned person to merge two branches. -Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch. -Tools such as GitLab and others choose the name merge request since that is the final action that is requested of the assignee. -In this article we'll refer to them as merge requests. +Merge or pull requests are created in a Git management application. They ask an assigned person to merge two branches. +Tools such as GitHub and Bitbucket choose the name "pull request" since the first manual action is to pull the feature branch. +Tools such as GitLab and others choose the name "merge request" since the final action is to merge the feature branch. +In this article, we'll refer to them as merge requests. -If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team. -This can be done by creating a merge request without assigning it to anyone, instead you mention people in the description or a comment (/cc @mark @susan). -This means it is not ready to be merged but feedback is welcome. +If you work on a feature branch for more than a few hours, it is good to share the intermediate result with the rest of the team. +To do this, create a merge request without assigning it to anyone. +Instead, mention people in the description or a comment, for example, "/cc @mark @susan." +This indicates that the merge request is not ready to be merged yet, but feedback is welcome. Your team members can comment on the merge request in general or on specific lines with line comments. -The merge requests serves as a code review tool and no separate tools such as Gerrit and reviewboard should be needed. -If the review reveals shortcomings anyone can commit and push a fix. -Commonly the person to do this is the creator of the merge/pull request. -The diff in the merge/pull requests automatically updates when new commits are pushed on the branch. +The merge request serves as a code review tool, and no separate code review tools should be needed. +If the review reveals shortcomings, anyone can commit and push a fix. +Usually, the person to do this is the creator of the merge request. +The diff in the merge request automatically updates when new commits are pushed to the branch. + +When you are ready for your feature branch to be merged, assign the merge request to the person who knows most about the codebase you are changing. +Also, mention any other people from whom you would like feedback. +After the assigned person feels comfortable with the result, they can merge the branch. +If the assigned person does not feel comfortable, they can request more changes or close the merge request without merging. + +In GitLab, it is common to protect the long-lived branches, e.g., the `master` branch, so that [most developers can't modify them](../permissions/permissions.md). +So, if you want to merge into a protected branch, assign your merge request to someone with maintainer permissions. -When you feel comfortable with it to be merged you assign it to the person that knows most about the codebase you are changing and mention any other people you would like feedback from. -There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged. -If the assigned person does not feel comfortable they can close the merge request without merging. +After you merge a feature branch, you should remove it from the source control software. +In GitLab, you can do this when merging. +Removing finished branches ensures that the list of branches shows only work in progress. +It also ensures that if someone reopens the issue, they can use the same branch name without causing problems. -In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://docs.gitlab.com/ce/permissions/permissions.html). -So if you want to merge it into a protected branch you assign it to someone with maintainer authorizations. +NOTE: **Note:** +When you reopen an issue you need to create a new merge request. + +![Remove checkbox for branch in merge requests](remove_checkbox.png) ## Issue tracking with GitLab flow -![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png) +![Merge request with the branch name "15-require-a-password-to-change-it" and assignee field shown](merge_request.png) GitLab flow is a way to make the relation between the code and the issue tracker more transparent. -Any significant change to the code should start with an issue where the goal is described. -Having a reason for every code change is important to inform everyone on the team and to help people keep the scope of a feature branch small. -In GitLab each change to the codebase starts with an issue in the issue tracking system. -If there is no issue yet it should be created first provided there is significant work involved (more than 1 hour). -For many organizations this will be natural since the issue will have to be estimated for the sprint. -Issue titles should describe the desired state of the system, e.g. "As an administrator I want to remove users without receiving an error" instead of "Admin can't remove users.". - -When you are ready to code you start a branch for the issue from the master branch. -The name of this branch should start with the issue number, for example '15-require-a-password-to-change-it'. - -When you are done or want to discuss the code you open a merge request. -This is an online place to discuss the change and review the code. -Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch. -If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request. -These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet. -_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready. - -When the author thinks the code is ready the merge request is assigned to reviewer. -The reviewer presses the merge button when they think the code is ready for inclusion in the master branch. -In this case the code is merged and a merge commit is generated that makes this event easily visible later on. -Merge requests always create a merge commit even when the commit could be added without one. -This merge strategy is called 'no fast-forward' in git. -After the merge the feature branch is deleted since it is no longer needed, in GitLab this deletion is an option when merging. +Any significant change to the code should start with an issue that describes the goal. +Having a reason for every code change helps to inform the rest of the team and to keep the scope of a feature branch small. +In GitLab, each change to the codebase starts with an issue in the issue tracking system. +If there is no issue yet, create the issue, as long as the change will take a significant amount of work, i.e., more than 1 hour. +In many organizations, raising an issue is part of the development process because they are used in sprint planning. +The issue title should describe the desired state of the system. +For example, the issue title "As an administrator, I want to remove users without receiving an error" is better than "Admin can't remove users." + +When you are ready to code, create a branch for the issue from the `master` branch. +This branch is the place for any work related to this change. + +NOTE: **Note:** +The name of a branch might be dictated by organizational standards. +For example, in GitLab, any branches in GitLab EE that are equivalent to branches in GitLab CE [must end in `-ee`](https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html#cherry-picking-from-ce-to-ee). + +When you are done or want to discuss the code, open a merge request. +A merge request is an online place to discuss the change and review the code. + +If you open the merge request but do not assign it to anyone, it is a "Work In Progress" merge request. +These are used to discuss the proposed implementation but are not ready for inclusion in the `master` branch yet. +Start the title of the merge request with "[WIP]" or "WIP:" to prevent it from being merged before it's ready. + +When you think the code is ready, assign the merge request to a reviewer. +The reviewer can merge the changes when they think the code is ready for inclusion in the `master` branch. +When they press the merge button, GitLab merges the code and creates a merge commit that makes this event easily visible later on. +Merge requests always create a merge commit, even when the branch could be merged without one. +This merge strategy is called "no fast-forward" in Git. +After the merge, delete the feature branch since it is no longer needed. +In GitLab, this deletion is an option when merging. Suppose that a branch is merged but a problem occurs and the issue is reopened. -In this case it is no problem to reuse the same branch name since it was deleted when the branch was merged. -At any time there is at most one branch for every issue. +In this case, it is no problem to reuse the same branch name since the first branch was deleted when it was merged. +At any time, there is at most one branch for every issue. It is possible that one feature branch solves more than one issue. ## Linking and closing issues from merge requests ![Merge request showing the linked issues that will be closed](close_issue_mr.png) -Linking to issues can happen by mentioning them in commit messages (fixes #14, closes #67, etc.) or in the merge request description. -GitLab then creates links to the mentioned issues and creates comments in the corresponding issues linking back to the merge request. +Link to issues by mentioning them in commit messages or the description of a merge request, for example, "Fixes #16" or "Duck typing is preferred. See #12." +GitLab then creates links to the mentioned issues and creates comments in the issues linking back to the merge request. -These issues are closed once code is merged into the default branch. +To automatically close linked issues, mention them with the words "fixes" or "closes," for example, "fixes #14" or "closes #67." GitLab closes these issues when the code is merged into the default branch. -If you only want to make the reference without closing the issue you can also just mention it: "Duck typing is preferred. #12". - -If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue. +If you have an issue that spans across multiple repositories, create an issue for each repository and link all issues to a parent issue. ## Squashing commits with rebase ![Vim screen showing the rebase view](rebase.png) -With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them. -In GitLab EE and .com you can also [rebase before merge](http://docs.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface. -This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical. -However you should never rebase commits you have pushed to a remote server. -Somebody can have referred to the commits or cherry-picked them. -When you rebase you change the identifier (SHA-1) of the commit and this is confusing. -If you do that the same change will be known under multiple identifiers and this can cause much confusion. -If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit. -Another reasons not to rebase is that you lose authorship information, maybe someone created a merge request, another person pushed a commit on there to improve it and a third one merged it. -In this case rebasing all the commits into one prevent the other authors from being properly attributed and sharing part of the [git blame](https://git-scm.com/docs/git-blame). - -People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on. -This will lead to many commits per change which makes the history harder to understand. -But the advantages of having stable identifiers outweigh this drawback. -And to understand a change in context one can always look at the merge commit that groups all the commits together when the code is merged into the master branch. - -After you merge multiple commits from a feature branch into the master branch this is harder to undo. -If you had squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed. -Fortunately [reverting a merge made some time ago](https://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git. -This however, requires having specific merge commits for the commits your want to revert. -If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise. - -Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option. -Git management software will always create a merge commit when you accept a merge request. - -## Do not order commits with rebase +With Git, you can use an interactive rebase (`rebase -i`) to squash multiple commits into one or reorder them. +This functionality is useful if you want to replace a couple of small commits with a single commit, or if you want to make the order more logical. + +However, you should never rebase commits you have pushed to a remote server. +Rebasing creates new commits for all your changes, which can cause confusion because the same change would have multiple identifiers. +It also causes merge errors for anyone working on the same branch because their history would not match with yours. +Also, if someone has already reviewed your code, rebasing makes it hard to tell what changed since the last review. + +You should also never rebase commits authored by other people. +Not only does this rewrite history, but it also loses authorship information. +Rebasing prevents the other authors from being attributed and sharing part of the [`git blame`](https://git-scm.com/docs/git-blame). + +If a merge involves many commits, it may seem more difficult to undo. +You might think to solve this by squashing all the changes into one commit before merging, but as discussed earlier, it is a bad idea to rebase commits that you have already pushed. +Fortunately, there is an easy way to undo a merge with all its commits. +The way to do this is by reverting the merge commit. +Preserving this ability to revert a merge is a good reason to always use the "no fast-forward" (`--no-ff`) strategy when you merge manually. + +NOTE: **Note:** +If you revert a merge commit and then change your mind, revert the revert commit to redo the merge. +Git does not allow you to merge the code again otherwise. + +## Reducing merge commits in feature branches ![List of sequential merge commits](merge_commits.png) -With git you can also rebase your feature branch commits to order them after the commits on the master branch. -This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history. -However, just like with squashing you should never rebase commits you have pushed to a remote server. -This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend. -When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](https://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/). -You can reuse recorded resolutions (rerere) sometimes, but without rebasing you only have to solve the conflicts one time and you’re set. -There has to be a better way to avoid many merge commits. - -The way to prevent creating many merge commits is to not frequently merge master into the feature branch. -We'll discuss the three reasons to merge in master: leveraging code, merge conflicts, and long running branches. -If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit. -If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this. -You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order. -For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG.md merge=union` so that there are fewer merge conflicts in it. -The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project. -Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). -At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. -That's continuous building, and a Good Thing, but there's no integration, so it's not CI.". -The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work. -If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html). -As for the long running branches that take more than one day there are two strategies. -In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time. -In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release. -This strategy is [advocated by Linus Torvalds](https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html) because the state of the code at these points is better known. - -In conclusion, we can say that you should try to prevent merge commits, but not eliminate them. -Your codebase should be clean but your history should represent what actually happened. -Developing software happen in small messy steps and it is OK to have your history reflect this. -You can use tools to view the network graphs of commits and understand the messy history that created your code. -If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers. +Having lots of merge commits can make your repository history messy. +Therefore, you should try to avoid merge commits in feature branches. +Often, people avoid merge commits by just using rebase to reorder their commits after the commits on the `master` branch. +Using rebase prevents a merge commit when merging `master` into your feature branch, and it creates a neat linear history. +However, as discussed in [the section about rebasing](#squashing-commits-with-rebase), you should never rebase commits you have pushed to a remote server. +This restriction makes it impossible to rebase work in progress that you already shared with your team, which is something we recommend. -## Award emojis on issues and merge requests +Rebasing also creates more work, since every time you rebase, you have to resolve similar conflicts. +Sometimes you can reuse recorded resolutions (`rerere`), but merging is better since you only have to resolve conflicts once. +Atlassian has a more thorough explanation of the tradeoffs between merging and rebasing [on their blog](https://www.atlassian.com/blog/git/git-team-workflows-merge-or-rebase). -![Emoji bar in GitLab](award_emoji.png) +A good way to prevent creating many merge commits is to not frequently merge `master` into the feature branch. +There are three reasons to merge in `master`: utilizing new code, resolving merge conflicts, and updating long-running branches. -It is common to voice approval or disapproval by using +1 or -1. In GitLab you -can use emojis to give a virtual high five on issues and merge requests. +If you need to utilize some code that was introduced in `master` after you created the feature branch, you can often solve this by just cherry-picking a commit. -## Pushing and removing branches +If your feature branch has a merge conflict, creating a merge commit is a standard way of solving this. -![Remove checkbox for branch in merge requests](remove_checkbox.png) +NOTE: **Note:** +Sometimes you can use .gitattributes to reduce merge conflicts. +For example, you can set your changelog file to use the [union merge driver](https://git-scm.com/docs/gitattributes#gitattributes-union) so that multiple new entries don't conflict with each other. -We recommend that people push their feature branches frequently, even when they are not ready for review yet. -By doing this you prevent team members from accidentally starting to work on the same issue. -Of course this situation should already be prevented by assigning someone to the issue in the issue tracking software. -However sometimes one of the two parties forgets to assign someone in the issue tracking software. -After a branch is merged it should be removed from the source control software. -In GitLab and similar systems this is an option when merging. -This ensures that the branch overview in the repository management software shows only work in progress. -This also ensures that when someone reopens the issue a new branch with the same name can be used without problem. -When you reopen an issue you need to create a new merge request. +The last reason for creating merge commits is to keep long-running feature branches up-to-date with the latest state of the project. +The solution here is to keep your feature branches short-lived. +Most feature branches should take less than one day of work. +If your feature branches often take more than a day of work, try to split your features into smaller units of work. + +If you need to keep a feature branch open for more than a day, there are a few strategies to keep it up-to-date. +One option is to use continuous integration (CI) to merge in `master` at the start of the day. +Another option is to only merge in from well-defined points in time, for example, a tagged release. +You could also use [feature toggles](https://martinfowler.com/bliki/FeatureToggle.html) to hide incomplete features so you can still merge back into `master` every day. + +> **Note:** Don't confuse automatic branch testing with continuous integration. +> Martin Fowler makes this distinction in [his article about feature branches](https://martinfowler.com/bliki/FeatureBranch.html): +> +> "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. +> That's continuous building, and a Good Thing, but there's no *integration*, so it's not CI." + +In conclusion, you should try to prevent merge commits, but not eliminate them. +Your codebase should be clean, but your history should represent what actually happened. +Developing software happens in small, messy steps, and it is OK to have your history reflect this. +You can use tools to view the network graphs of commits and understand the messy history that created your code. +If you rebase code, the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers. + +## Commit often and push frequently + +Another way to make your development work easier is to commit often. +Every time you have a working set of tests and code, you should make a commit. +Splitting up work into individual commits provides context for developers looking at your code later. +Smaller commits make it clear how a feature was developed, and they make it easy to roll back to a specific good point in time or to revert one code change without reverting several unrelated changes. + +Committing often also makes it easy to share your work, which is important so that everyone is aware of what you are working on. +You should push your feature branch frequently, even when it is not yet ready for review. +By sharing your work in a feature branch or [a merge request](#mergepull-requests-with-gitlab-flow), you prevent your team members from duplicating work. +Sharing your work before it's complete also allows for discussion and feedback about the changes, which can help improve the code before it gets to review. -## Committing often and with the right message +## How to write a good commit message ![Good and bad commit message](good_commit.png) -We recommend to commit early and often. -Each time you have a functioning set of tests and code a commit can be made. -The advantage is that when an extension or refactor goes wrong it is easy to revert to a working version. -This is quite a change for programmers that used SVN before, they used to commit when their work was ready to share. -The trick is to use the merge/pull request with multiple commits when your work is ready to share. -The commit message should reflect your intention, not the contents of the commit. -The contents of the commit can be easily seen anyway, the question is why you did it. -An example of a good commit message is: "Combine templates to dry up the user views.". -Some words that are bad commit messages because they don't contain much information are: change, improve and refactor. -The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number. -To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +A commit message should reflect your intention, not just the contents of the commit. +It is easy to see the changes in a commit, so the commit message should explain why you made those changes. +An example of a good commit message is: "Combine templates to reduce duplicate code in the user views." +The words "change," "improve," "fix," and "refactor" don't add much information to a commit message. +For example, "Improve XML generation" could be better written as "Properly escape special characters in XML generation." +For more information about formatting commit messages, please see this excellent [blog post by Tim Pope](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). ## Testing before merging -![Merge requests showing the test states, red, yellow and green](ci_mr.png) - -In old workflows the Continuous Integration (CI) server commonly ran tests on the master branch only. -Developers had to ensure their code did not break the master branch. -When using GitLab flow developers create their branches from this master branch so it is essential it is green. -Therefore each merge request must be tested before it is accepted. -CI software like Travis and GitLab CI show the build results right in the merge request itself to make this easy. -One drawback is that they are testing the feature branch itself and not the merged result. -What one can do to improve this is to test the merged result itself. -The problem is that the merge result changes every time something is merged into master. -Retesting on every commit to master is computationally expensive and means you are more frequently waiting for test results. -If there are no merge conflicts and the feature branches are short lived the risk is acceptable. -If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests. -If you have long lived feature branches that last for more than a few days you should make your issues smaller. +![Merge requests showing the test states: red, yellow, and green](ci_mr.png) -## Working with feature branches +In old workflows, the continuous integration (CI) server commonly ran tests on the `master` branch only. +Developers had to ensure their code did not break the `master` branch. +When using GitLab flow, developers create their branches from this `master` branch, so it is essential that it never breaks. +Therefore, each merge request must be tested before it is accepted. +CI software like Travis CI and GitLab CI show the build results right in the merge request itself to make this easy. -![Shell output showing git pull output](git_pull.png) +There is one drawback to testing merge requests: the CI server only tests the feature branch itself, not the merged result. +Ideally, the server could also test the `master` branch after each change. +However, retesting on every commit to `master` is computationally expensive and means you are more frequently waiting for test results. +Since feature branches should be short-lived, testing just the branch is an acceptable risk. +If new commits in `master` cause merge conflicts with the feature branch, merge `master` back into the branch to make the CI server re-run the tests. +As said before, if you often have feature branches that last for more than a few days, you should make your issues smaller. -When initiating a feature branch, always start with an up to date master to branch off from. -If you know beforehand that your work absolutely depends on another branch you can also branch from there. -If you need to merge in another branch after starting explain the reason in the merge commit. -If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch. -Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](https://lwn.net/Articles/328438/). -Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history. +## Working with feature branches -### References +![Shell output showing git pull output](git_pull.png) -- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/) +When creating a feature branch, always branch from an up-to-date `master`. +If you know before you start that your work depends on another branch, you can also branch from there. +If you need to merge in another branch after starting, explain the reason in the merge commit. +If you have not pushed your commits to a shared location yet, you can also incorporate changes by rebasing on `master` or another feature branch. +Do not merge from upstream again if your code can work and merge cleanly without doing so. +Merging only when needed prevents creating merge commits in your feature branch that later end up littering the `master` history. diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 9d23daafe95..8defc59224d 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -323,6 +323,22 @@ module API present paginate(commit.merge_requests), with: Entities::MergeRequestBasic end + + desc "Get a commit's GPG signature" do + success Entities::CommitSignature + end + params do + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' + end + get ':id/repository/commits/:sha/signature', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do + commit = user_project.commit(params[:sha]) + not_found! 'Commit' unless commit + + signature = commit.signature + not_found! 'GPG Signature' unless signature + + present signature, with: Entities::CommitSignature + end end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index beb8ce349b4..27da2c2e5ed 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -369,8 +369,9 @@ module API end class Commit < Grape::Entity - expose :id, :short_id, :title, :created_at + expose :id, :short_id, :created_at expose :parent_ids + expose :full_title, as: :title expose :safe_message, as: :message expose :author_name, :author_email, :authored_date expose :committer_name, :committer_email, :committed_date @@ -391,6 +392,13 @@ module API expose :project_id end + class CommitSignature < Grape::Entity + expose :gpg_key_id + expose :gpg_key_primary_keyid, :gpg_key_user_name, :gpg_key_user_email + expose :verification_status + expose :gpg_key_subkey_id + end + class BasicRef < Grape::Entity expose :type, :name end @@ -731,6 +739,12 @@ module API def build_available?(options) options[:project]&.feature_available?(:builds, options[:current_user]) end + + expose :user do + expose :can_merge do |merge_request, options| + merge_request.can_be_merged_by?(options[:current_user]) + end + end end class MergeRequestChanges < MergeRequest @@ -1003,7 +1017,7 @@ module API end class LabelBasic < Grape::Entity - expose :id, :name, :color, :description + expose :id, :name, :color, :description, :text_color end class Label < LabelBasic @@ -1031,6 +1045,9 @@ module API expose :priority do |label, options| label.priority(options[:parent]) end + expose :is_project_label do |label, options| + label.is_a?(::ProjectLabel) + end end class List < Grape::Entity diff --git a/lib/api/services.rb b/lib/api/services.rb index 36bdba2d765..163c7505a65 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -368,8 +368,9 @@ module API name: :webhook, type: String, desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…' - } - ], + }, + CHAT_NOTIFICATION_EVENTS + ].flatten, 'irker' => [ { required: true, diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index ef0e3decc2c..994074ddc67 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -11,9 +11,7 @@ module API } end - params :wiki_page_params do - requires :content, type: String, desc: 'Content of a wiki page' - requires :title, type: String, desc: 'Title of a wiki page' + params :common_wiki_page_params do optional :format, type: String, values: ProjectWiki::MARKUPS.values.map(&:to_s), @@ -54,7 +52,9 @@ module API success Entities::WikiPage end params do - use :wiki_page_params + requires :title, type: String, desc: 'Title of a wiki page' + requires :content, type: String, desc: 'Content of a wiki page' + use :common_wiki_page_params end post ':id/wikis' do authorize! :create_wiki, user_project @@ -72,7 +72,10 @@ module API success Entities::WikiPage end params do - use :wiki_page_params + optional :title, type: String, desc: 'Title of a wiki page' + optional :content, type: String, desc: 'Content of a wiki page' + use :common_wiki_page_params + at_least_one_of :content, :title, :format end put ':id/wikis/:slug' do authorize! :create_wiki, user_project diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb index 97527976437..de133774dfa 100644 --- a/lib/banzai/filter/footnote_filter.rb +++ b/lib/banzai/filter/footnote_filter.rb @@ -29,21 +29,30 @@ module Banzai # Sanitization stripped off the section wrapper - add it back in first_footnote.parent.wrap('<section class="footnotes">') rand_suffix = "-#{random_number}" + modified_footnotes = {} doc.css('sup > a[id]').each do |link_node| ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX) footnote_node = doc.at_css("li[id=#{fn_id(ref_num)}]") - backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]") - if ref_num =~ INTEGER_PATTERN && footnote_node && backref_node - link_node[:href] += rand_suffix - link_node[:id] += rand_suffix - footnote_node[:id] += rand_suffix - backref_node[:href] += rand_suffix + if INTEGER_PATTERN.match?(ref_num) && (footnote_node || modified_footnotes[ref_num]) + link_node[:href] += rand_suffix + link_node[:id] += rand_suffix # Sanitization stripped off class - add it back in link_node.parent.append_class('footnote-ref') - backref_node.append_class('footnote-backref') + + unless modified_footnotes[ref_num] + footnote_node[:id] += rand_suffix + backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]") + + if backref_node + backref_node[:href] += rand_suffix + backref_node.append_class('footnote-backref') + end + + modified_footnotes[ref_num] = true + end end end diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb index 1663c875426..9c705a1cd3e 100644 --- a/lib/gitlab/ci/build/policy/changes.rb +++ b/lib/gitlab/ci/build/policy/changes.rb @@ -10,7 +10,7 @@ module Gitlab end def satisfied_by?(pipeline, seed) - return true unless pipeline.branch_updated? + return true if pipeline.modified_paths.nil? pipeline.modified_paths.any? do |path| @globs.any? do |glob| diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb new file mode 100644 index 00000000000..b8428637e19 --- /dev/null +++ b/lib/gitlab/danger/helper.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true +require 'net/http' +require 'json' + +require_relative 'teammate' + +module Gitlab + module Danger + module Helper + ROULETTE_DATA_URL = URI.parse('https://about.gitlab.com/roulette.json').freeze + + # Returns a list of all files that have been added, modified or renamed. + # `git.modified_files` might contain paths that already have been renamed, + # so we need to remove them from the list. + # + # Considering these changes: + # + # - A new_file.rb + # - D deleted_file.rb + # - M modified_file.rb + # - R renamed_file_before.rb -> renamed_file_after.rb + # + # it will return + # ``` + # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ] + # ``` + # + # @return [Array<String>] + def all_changed_files + Set.new + .merge(git.added_files.to_a) + .merge(git.modified_files.to_a) + .merge(git.renamed_files.map { |x| x[:after] }) + .subtract(git.renamed_files.map { |x| x[:before] }) + .to_a + .sort + end + + def ee? + ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md') + end + + def project_name + ee? ? 'gitlab-ee' : 'gitlab-ce' + end + + # Looks up the current list of GitLab team members and parses it into a + # useful form + # + # @return [Array<Teammate>] + def team + @team ||= + begin + rsp = Net::HTTP.get_response(ROULETTE_DATA_URL) + raise "Failed to read #{ROULETTE_DATA_URL}: #{rsp.code} #{rsp.message}" unless + rsp.is_a?(Net::HTTPSuccess) + + data = JSON.parse(rsp.body) + data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) } + rescue JSON::ParserError + raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}" + end + end + + # Like +team+, but only returns teammates in the current project, based on + # project_name. + # + # @return [Array<Teammate>] + def project_team + team.select { |member| member.in_project?(project_name) } + end + + # @return [Hash<String,Array<String>>] + def changes_by_category + all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash| + hash[category_for_file(file)] << file + end + end + + # Determines the category a file is in, e.g., `:frontend` or `:backend` + # @return[Symbol] + def category_for_file(file) + _, category = CATEGORIES.find { |regexp, _| regexp.match?(file) } + + category || :unknown + end + + # Returns the GFM for a category label, making its best guess if it's not + # a category we know about. + # + # @return[String] + def label_for_category(category) + CATEGORY_LABELS.fetch(category, "~#{category}") + end + + CATEGORY_LABELS = { + docs: "~Documentation", + qa: "~QA" + }.freeze + + # rubocop:disable Style/RegexpLiteral + CATEGORIES = { + %r{\Adoc/} => :docs, + %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs, + + %r{\A(ee/)?app/(assets|views)/} => :frontend, + %r{\A(ee/)?public/} => :frontend, + %r{\A(ee/)?spec/javascripts/} => :frontend, + %r{\A(ee/)?vendor/assets/} => :frontend, + %r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend, + + %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend, + %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend, + %r{\A(ee/)?spec/(?!javascripts)[^/]+} => :backend, + %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend, + %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend, + %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend, + %r{\A[A-Z_]+_VERSION\z} => :backend, + + %r{\A(ee/)?db/} => :database, + %r{\A(ee/)?qa/} => :qa, + + # Fallbacks in case the above patterns miss anything + %r{\.rb\z} => :backend, + %r{\.(md|txt)\z} => :docs, + %r{\.js\z} => :frontend + }.freeze + # rubocop:enable Style/RegexpLiteral + end + end +end diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb new file mode 100644 index 00000000000..4b822aa86c5 --- /dev/null +++ b/lib/gitlab/danger/teammate.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Gitlab + module Danger + class Teammate + attr_reader :name, :username, :projects + + def initialize(options = {}) + @name = options['name'] + @username = options['username'] + @projects = options['projects'] + end + + def markdown_name + "[#{name}](https://gitlab.com/#{username}) (`@#{username}`)" + end + + def in_project?(name) + projects&.has_key?(name) + end + + # Traintainers also count as reviewers + def reviewer?(project, category) + capabilities(project) == "reviewer #{category}" || traintainer?(project, category) + end + + def traintainer?(project, category) + capabilities(project) == "trainee_maintainer #{category}" + end + + def maintainer?(project, category) + capabilities(project) == "maintainer #{category}" + end + + private + + def capabilities(project) + projects.fetch(project, '') + end + end + end +end diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb index 947caaaefee..725c1101d70 100644 --- a/lib/gitlab/import_export/shared.rb +++ b/lib/gitlab/import_export/shared.rb @@ -61,7 +61,7 @@ module Gitlab def log_base_data { importer: 'Import/Export', - import_jid: @project&.import_state&.import_jid, + import_jid: @project&.import_state&.jid, project_id: @project&.id, project_path: @project&.full_path } diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 651e241362c..ff3fffe7b95 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -19,7 +19,7 @@ module Gitlab # Returns the name of the series to use for storing method calls. def self.series - @series ||= "#{Metrics.series_prefix}method_calls" + @series ||= "#{::Gitlab::Metrics.series_prefix}method_calls" end # Instruments a class method. @@ -118,7 +118,7 @@ module Gitlab # mod - The module containing the method. # name - The name of the method to instrument. def self.instrument(type, mod, name) - return unless Metrics.enabled? + return unless ::Gitlab::Metrics.enabled? name = name.to_sym target = type == :instance ? mod : mod.singleton_class diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb index 85438011cb9..d0c63a862c2 100644 --- a/lib/gitlab/metrics/method_call.rb +++ b/lib/gitlab/metrics/method_call.rb @@ -65,7 +65,7 @@ module Gitlab # Returns true if the total runtime of this method exceeds the method call # threshold. def above_threshold? - real_time.in_milliseconds >= Metrics.method_call_threshold + real_time.in_milliseconds >= ::Gitlab::Metrics.method_call_threshold end end end diff --git a/lib/gitlab/metrics/methods.rb b/lib/gitlab/metrics/methods.rb index 447d03bfca4..cee601ff14c 100644 --- a/lib/gitlab/metrics/methods.rb +++ b/lib/gitlab/metrics/methods.rb @@ -58,11 +58,11 @@ module Gitlab def build_metric!(type, name, options) case type when :gauge - Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode) + ::Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode) when :counter - Gitlab::Metrics.counter(name, options.docstring, options.base_labels) + ::Gitlab::Metrics.counter(name, options.docstring, options.base_labels) when :histogram - Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets) + ::Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets) when :summary raise NotImplementedError, "summary metrics are not currently supported" else diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb index 74c956ab5af..26aa0910047 100644 --- a/lib/gitlab/metrics/requests_rack_middleware.rb +++ b/lib/gitlab/metrics/requests_rack_middleware.rb @@ -8,15 +8,15 @@ module Gitlab end def self.http_request_total - @http_request_total ||= Gitlab::Metrics.counter(:http_requests_total, 'Request count') + @http_request_total ||= ::Gitlab::Metrics.counter(:http_requests_total, 'Request count') end def self.rack_uncaught_errors_count - @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request handling uncaught errors count') + @rack_uncaught_errors_count ||= ::Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request handling uncaught errors count') end def self.http_request_duration_seconds - @http_request_duration_seconds ||= Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time', + @http_request_duration_seconds ||= ::Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time', {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 2.5, 5, 10, 25]) end diff --git a/lib/gitlab/metrics/samplers/influx_sampler.rb b/lib/gitlab/metrics/samplers/influx_sampler.rb index c4c38b23a55..5138b37f83e 100644 --- a/lib/gitlab/metrics/samplers/influx_sampler.rb +++ b/lib/gitlab/metrics/samplers/influx_sampler.rb @@ -10,7 +10,7 @@ module Gitlab # statistics, etc. class InfluxSampler < BaseSampler # interval - The sampling interval in seconds. - def initialize(interval = Metrics.settings[:sample_interval]) + def initialize(interval = ::Gitlab::Metrics.settings[:sample_interval]) super(interval) @last_step = nil @@ -32,7 +32,7 @@ module Gitlab end def flush - Metrics.submit_metrics(@metrics.map(&:to_hash)) + ::Gitlab::Metrics.submit_metrics(@metrics.map(&:to_hash)) end def sample_memory_usage diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb index 232a58a7d69..18a69321905 100644 --- a/lib/gitlab/metrics/samplers/ruby_sampler.rb +++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb @@ -24,14 +24,14 @@ module Gitlab def init_metrics metrics = {} - metrics[:sampler_duration] = Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels) - metrics[:total_time] = Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels) + metrics[:sampler_duration] = ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels) + metrics[:total_time] = ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels) GC.stat.keys.each do |key| - metrics[key] = Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels, :livesum) + metrics[key] = ::Gitlab::Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels, :livesum) end - metrics[:memory_usage] = Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum) - metrics[:file_descriptors] = Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum) + metrics[:memory_usage] = ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum) + metrics[:file_descriptors] = ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum) metrics end diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb index 4c5b849cc51..bec64e864b3 100644 --- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb +++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb @@ -9,11 +9,11 @@ module Gitlab end def unicorn_active_connections - @unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max) + @unicorn_active_connections ||= ::Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max) end def unicorn_queued_connections - @unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max) + @unicorn_queued_connections ||= ::Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max) end def enabled? diff --git a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb index 56e106b9612..71a5406815f 100644 --- a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb +++ b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb @@ -9,7 +9,7 @@ module Gitlab LOG_FILENAME = File.join(Rails.root, 'log', 'sidekiq_exporter.log') def enabled? - Gitlab::Metrics.metrics_folder_present? && settings.enabled + ::Gitlab::Metrics.metrics_folder_present? && settings.enabled end def settings diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb index f633e1a9d7c..01db507761b 100644 --- a/lib/gitlab/metrics/subscribers/rails_cache.rb +++ b/lib/gitlab/metrics/subscribers/rails_cache.rb @@ -64,7 +64,7 @@ module Gitlab end def metric_cache_operation_duration_seconds - @metric_cache_operation_duration_seconds ||= Gitlab::Metrics.histogram( + @metric_cache_operation_duration_seconds ||= ::Gitlab::Metrics.histogram( :gitlab_cache_operation_duration_seconds, 'Cache access time', Transaction::BASE_LABELS.merge({ action: nil }), @@ -73,7 +73,7 @@ module Gitlab end def metric_cache_misses_total - @metric_cache_misses_total ||= Gitlab::Metrics.counter( + @metric_cache_misses_total ||= ::Gitlab::Metrics.counter( :gitlab_cache_misses_total, 'Cache read miss', Transaction::BASE_LABELS diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index 468d7cb56fc..e91803ecd62 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -64,7 +64,7 @@ module Gitlab end def add_metric(series, values, tags = {}) - @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags) + @metrics << Metric.new("#{::Gitlab::Metrics.series_prefix}#{series}", values, tags) end # Tracks a business level event @@ -127,7 +127,7 @@ module Gitlab hash end - Metrics.submit_metrics(submit_hashes) + ::Gitlab::Metrics.submit_metrics(submit_hashes) end def labels diff --git a/lib/gitlab/middleware/rails_queue_duration.rb b/lib/gitlab/middleware/rails_queue_duration.rb index 96c6a0a7d28..a147e165262 100644 --- a/lib/gitlab/middleware/rails_queue_duration.rb +++ b/lib/gitlab/middleware/rails_queue_duration.rb @@ -7,6 +7,8 @@ module Gitlab module Middleware class RailsQueueDuration + GITLAB_RAILS_QUEUE_DURATION_KEY = 'GITLAB_RAILS_QUEUE_DURATION' + def initialize(app) @app = app end @@ -19,6 +21,7 @@ module Gitlab duration = Time.now.to_f * 1_000 - proxy_start.to_f / 1_000_000 trans.set(:rails_queue_duration, duration) metric_rails_queue_duration_seconds.observe(trans.labels, duration / 1_000) + env[GITLAB_RAILS_QUEUE_DURATION_KEY] = duration.round(2) end @app.call(env) diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake index 4beb94eeb8e..b1db4dc94a6 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -10,6 +10,7 @@ namespace :dev do desc "GitLab | Eager load application" task load: :environment do + Rails.configuration.eager_load = true Rails.application.eager_load! end end diff --git a/package.json b/package.json index 3cd023c0b0c..21edd0dcf41 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "prettier-staged-save": "node ./scripts/frontend/prettier.js save", "prettier-all": "node ./scripts/frontend/prettier.js check-all", "prettier-all-save": "node ./scripts/frontend/prettier.js save-all", + "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.*", "webpack": "webpack --config config/webpack.config.js", "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" }, @@ -168,7 +169,11 @@ "karma-webpack": "^4.0.0-beta.0", "nodemon": "^1.18.9", "pixelmatch": "^4.0.2", + "postcss": "^7.0.14", "prettier": "1.16.1", + "stylelint": "^9.10.1", + "stylelint-config-recommended": "^2.1.0", + "stylelint-scss": "^3.5.3", "vue-jest": "^3.0.2", "webpack-dev-server": "^3.1.14", "yarn-deduplicate": "^1.1.0" diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb index 6d1601dfa48..e0fbbed2567 100644 --- a/qa/qa/ce/strategy.rb +++ b/qa/qa/ce/strategy.rb @@ -8,7 +8,10 @@ module QA end def perform_before_hooks - # noop + # The login page could take some time to load the first time it is visited. + # We visit the login page and wait for it to properly load only once before the tests. + QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) + QA::Page::Main::Login.perform(&:assert_page_loaded) end end end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index e476cbe29a2..e03fe9ab83a 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -40,6 +40,12 @@ module QA element :login_page end + def assert_page_loaded + unless page_loaded? + raise QA::Runtime::Browser::NotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}" + end + end + def page_loaded? wait(max: 60) do has_element?(:login_page) diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 0bcf5e693f0..0b805b855ac 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -8,6 +8,8 @@ module QA class Browser include QA::Scenario::Actable + NotRespondingError = Class.new(RuntimeError) + def initialize self.class.configure! end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 3e7a6dd26ee..20a153f3f63 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -5,40 +5,16 @@ require_relative '../qa' end RSpec.configure do |config| - ServerNotRespondingError = Class.new(RuntimeError) - - # The login page could take some time to load the first time it is visited. - # We visit the login page and wait for it to properly load only once at the beginning of the suite. - config.before(:suite) do - if QA::Runtime::Scenario.respond_to?(:gitlab_address) - QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) - - unless QA::Page::Main::Login.perform(&:page_loaded?) - raise ServerNotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}" - end + config.before(:context) do + if self.class.metadata.keys.include?(:quarantine) + skip_or_run_quarantined_tests(self.class.metadata.keys, config.inclusion_filter.rules.keys) end end config.before do |example| QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug? - # If quarantine is tagged, skip tests that have other metadata unless - # they're also tagged. This lets us run quarantined tests in a particular - # category without running tests in other categories. - # E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged - # 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests - # using `--tag quarantine --tag smoke`, without this check we'd end up - # running that ldap test as well. - if config.inclusion_filter[:quarantine] - skip("Running tests tagged with all of #{config.inclusion_filter.rules.keys}") unless quarantine_and_optional_other_tag?(example, config) - end - end - - config.before(:each, :quarantine) do |example| - # Skip tests in quarantine unless we explicitly focus on them - # We could use an exclusion filter, but this way the test report will list - # the quarantined tests when they're not run so that we're aware of them - skip('In quarantine') unless config.inclusion_filter[:quarantine] + skip_or_run_quarantined_tests(example.metadata.keys, config.inclusion_filter.rules.keys) end config.expect_with :rspec do |expectations| @@ -57,18 +33,41 @@ RSpec.configure do |config| Kernel.srand config.seed end +# Skip tests in quarantine unless we explicitly focus on them. +# Skip the entire context if a context is tagged. This avoids running before +# blocks unnecessarily. +# If quarantine is focussed, skip tests/contexts that have other metadata +# unless they're also focussed. This lets us run quarantined tests in a +# particular category without running tests in other categories. +# E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged +# 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests +# using `--tag quarantine --tag smoke`, without this check we'd end up +# running that ldap test as well. +# We could use an exclusion filter, but this way the test report will list +# the quarantined tests when they're not run so that we're aware of them +def skip_or_run_quarantined_tests(metadata_keys, filter_keys) + included_filters = filters_other_than_quarantine(filter_keys) + + if filter_keys.include?(:quarantine) + skip("Only running tests tagged with :quarantine and any of #{included_filters}") unless quarantine_and_optional_other_tag?(metadata_keys, included_filters) + else + skip('In quarantine') if metadata_keys.include?(:quarantine) + end +end + +def filters_other_than_quarantine(filter_keys) + filter_keys.reject { |key| key == :quarantine } +end + # Checks if a test has the 'quarantine' tag and other tags in the inclusion filter. # # Returns true if -# - the example metadata includes the quarantine tag -# - and the metadata and inclusion filter both have any other tag -# - or no other tags are in the inclusion filter -def quarantine_and_optional_other_tag?(example, config) - return false unless example.metadata.keys.include? :quarantine - - filters_other_than_quarantine = config.inclusion_filter.rules.keys.reject { |key| key == :quarantine } - - return true if filters_other_than_quarantine.empty? +# - the metadata includes the quarantine tag +# - and the metadata and inclusion filter both have any other tag +# - or no other tags are in the inclusion filter +def quarantine_and_optional_other_tag?(metadata_keys, included_filters) + return false unless metadata_keys.include? :quarantine + return true if included_filters.empty? - filters_other_than_quarantine.any? { |key| example.metadata.keys.include? key } + included_filters.any? { |key| metadata_keys.include? key } end diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb index f001200fb52..2427999e110 100644 --- a/qa/spec/spec_helper_spec.rb +++ b/qa/spec/spec_helper_spec.rb @@ -10,79 +10,79 @@ describe 'rspec config tests' do end end + context 'default' do + it_behaves_like 'passing tests' + end + context 'foo', :foo do it_behaves_like 'passing tests' end - context 'default' do + context 'quarantine', :quarantine do + it_behaves_like 'passing tests' + end + + context 'bar quarantine', :bar, :quarantine do it_behaves_like 'passing tests' end end end - context 'default config' do - it 'tests are skipped if in quarantine' do + context 'with no tags focussed' do + before do group.run + end - foo_context = group.children.find { |c| c.description == "foo" } - foo_examples = foo_context.descendant_filtered_examples - expect(foo_examples.count).to eq(2) - - ex = foo_examples.find { |e| e.description == "not in quarantine" } - expect(ex.execution_result.status).to eq(:passed) + context 'in a context tagged :foo' do + it 'skips tests in quarantine' do + context = group.children.find { |c| c.description == "foo" } + examples = context.descendant_filtered_examples + expect(examples.count).to eq(2) - ex = foo_examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('In quarantine') + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:passed) - default_context = group.children.find { |c| c.description == "default" } - default_examples = default_context.descendant_filtered_examples - expect(default_examples.count).to eq(2) + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + expect(ex.execution_result.pending_message).to eq('In quarantine') + end + end - ex = default_examples.find { |e| e.description == "not in quarantine" } - expect(ex.execution_result.status).to eq(:passed) + context 'in an untagged context' do + it 'skips tests in quarantine' do + context = group.children.find { |c| c.description == "default" } + examples = context.descendant_filtered_examples + expect(examples.count).to eq(2) - ex = default_examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('In quarantine') - end - end + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:passed) - context "with 'quarantine' tagged" do - before do - RSpec.configure do |config| - config.inclusion_filter = :quarantine - end - end - after do - RSpec.configure do |config| - config.inclusion_filter.clear + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + expect(ex.execution_result.pending_message).to eq('In quarantine') end end - it "only quarantined tests are run" do - group.run - - foo_context = group.children.find { |c| c.description == "foo" } - foo_examples = foo_context.descendant_filtered_examples - expect(foo_examples.count).to be(1) - - ex = foo_examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to eq(:passed) + context 'in a context tagged :quarantine' do + it 'skips all tests' do + context = group.children.find { |c| c.description == "quarantine" } + examples = context.descendant_filtered_examples + expect(examples.count).to eq(2) - default_context = group.children.find { |c| c.description == "default" } - default_examples = default_context.descendant_filtered_examples - expect(default_examples.count).to be(1) + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:pending) - ex = default_examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to eq(:passed) + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + expect(ex.execution_result.pending_message).to eq('In quarantine') + end end end - context "with 'foo' tagged" do + context 'with :quarantine focussed' do before do RSpec.configure do |config| - config.inclusion_filter = :foo + config.inclusion_filter = :quarantine end group.run @@ -93,30 +93,50 @@ describe 'rspec config tests' do end end - it "tests are not run if not tagged 'foo'" do - default_context = group.children.find { |c| c.description == "default" } - expect(default_context.descendant_filtered_examples.count).to eq(0) + context 'in an untagged context' do + it 'only runs quarantined tests' do + context = group.children.find { |c| c.description == "default" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(1) + + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:passed) + end end - it "tests are skipped if in quarantine" do - foo_context = group.children.find { |c| c.description == "foo" } - foo_examples = foo_context.descendant_filtered_examples - expect(foo_examples.count).to eq(2) + context 'in a context tagged :foo' do + it 'only runs quarantined tests' do + context = group.children.find { |c| c.description == "foo" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(1) - ex = foo_examples.find { |e| e.description == "not in quarantine" } - expect(ex.execution_result.status).to eq(:passed) + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:passed) + end + end + + context 'in a context tagged :quarantine' do + it 'runs all tests' do + context = group.children.find { |c| c.description == "quarantine" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) + + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:passed) - ex = foo_examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('In quarantine') + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:passed) + end end end - context "with 'quarantine' and 'foo' tagged" do + context 'with a non-quarantine tag (:foo) focussed' do before do RSpec.configure do |config| - config.inclusion_filter = { quarantine: true, foo: true } + config.inclusion_filter = :foo end + + group.run end after do RSpec.configure do |config| @@ -124,38 +144,43 @@ describe 'rspec config tests' do end end - it 'of tests tagged foo, only tests in quarantine run' do - group.run + context 'in an untagged context' do + it 'runs no tests' do + context = group.children.find { |c| c.description == "default" } + expect(context.descendant_filtered_examples.count).to eq(0) + end + end - foo_context = group.children.find { |c| c.description == "foo" } - foo_examples = foo_context.descendant_filtered_examples - expect(foo_examples.count).to eq(2) + context 'in a context tagged :foo' do + it 'skips quarantined tests' do + context = group.children.find { |c| c.description == "foo" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) - ex = foo_examples.find { |e| e.description == "not in quarantine" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]') + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:passed) - ex = foo_examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to eq(:passed) + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + expect(ex.execution_result.pending_message).to eq('In quarantine') + end end - it 'if tests are not tagged they are skipped, even if they are in quarantine' do - group.run - default_context = group.children.find { |c| c.description == "default" } - default_examples = default_context.descendant_filtered_examples - expect(default_examples.count).to eq(1) - - ex = default_examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]') + context 'in a context tagged :quarantine' do + it 'runs no tests' do + context = group.children.find { |c| c.description == "quarantine" } + expect(context.descendant_filtered_examples.count).to eq(0) + end end end - context "with 'foo' and 'bar' tagged" do + context 'with :quarantine and a non-quarantine tag (:foo) focussed' do before do RSpec.configure do |config| - config.inclusion_filter = { bar: true, foo: true } + config.inclusion_filter = { quarantine: true, foo: true } end + + group.run end after do RSpec.configure do |config| @@ -163,67 +188,67 @@ describe 'rspec config tests' do end end - it "runs tests tagged either 'foo' or 'bar'" do - group = RSpec.describe do - example 'foo', :foo do - end - example 'bar', :bar do - end - example 'foo and bar', :foo, :bar do - end - end - - group.run - expect(group.examples.count).to eq(3) + context 'in an untagged context' do + it 'ignores untagged tests and skips tests even if in quarantine' do + context = group.children.find { |c| c.description == "default" } + examples = context.descendant_filtered_examples + expect(examples.count).to eq(1) - ex = group.examples.find { |e| e.description == "foo" } - expect(ex.execution_result.status).to eq(:passed) + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + end + end - ex = group.examples.find { |e| e.description == "bar" } - expect(ex.execution_result.status).to eq(:passed) + context 'in a context tagged :foo' do + it 'only runs quarantined tests' do + context = group.children.find { |c| c.description == "foo" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) - ex = group.examples.find { |e| e.description == "foo and bar" } - expect(ex.execution_result.status).to eq(:passed) - end + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:passed) - it "skips quarantined tests tagged 'foo' and/or 'bar'" do - group = RSpec.describe do - example 'foo in quarantine', :foo, :quarantine do - end - example 'foo and bar in quarantine', :foo, :bar, :quarantine do - end + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:pending) end + end - group.run - expect(group.examples.count).to eq(2) + context 'in a context tagged :quarantine' do + it 'skips all tests' do + context = group.children.find { |c| c.description == "quarantine" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) - ex = group.examples.find { |e| e.description == "foo in quarantine" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('In quarantine') + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) - ex = group.examples.find { |e| e.description == "foo and bar in quarantine" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('In quarantine') + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + end end - it "ignores quarantined tests not tagged either 'foo' or 'bar'" do - group = RSpec.describe do - example 'in quarantine', :quarantine do - end - end + context 'in a context tagged :bar and :quarantine' do + it 'skips all tests' do + context = group.children.find { |c| c.description == "quarantine" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) - group.run + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) - ex = group.examples.find { |e| e.description == "in quarantine" } - expect(ex.execution_result.status).to be_nil + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + end end end - context "with 'foo' and 'bar' and 'quarantined' tagged" do + context 'with :quarantine and multiple non-quarantine tags focussed' do before do RSpec.configure do |config| config.inclusion_filter = { bar: true, foo: true, quarantine: true } end + + group.run end after do RSpec.configure do |config| @@ -231,34 +256,49 @@ describe 'rspec config tests' do end end - it "runs tests tagged 'quarantine' and 'foo' or 'bar'" do - group = RSpec.describe do - example 'foo', :foo do - end - example 'bar and quarantine', :bar, :quarantine do - end - example 'foo and bar', :foo, :bar do - end - example 'foo, bar, and quarantine', :foo, :bar, :quarantine do - end + context 'in a context tagged :foo' do + it 'only runs quarantined tests' do + context = group.children.find { |c| c.description == "foo" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) + + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:passed) + + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]') end + end - group.run - expect(group.examples.count).to eq(4) + context 'in a context tagged :quarantine' do + it 'skips all tests' do + context = group.children.find { |c| c.description == "quarantine" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) - ex = group.examples.find { |e| e.description == "foo" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]') + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]') - ex = group.examples.find { |e| e.description == "bar and quarantine" } - expect(ex.execution_result.status).to eq(:passed) + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:pending) + expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]') + end + end - ex = group.examples.find { |e| e.description == "foo and bar" } - expect(ex.execution_result.status).to eq(:pending) - expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]') + context 'in a context tagged :bar and :quarantine' do + it 'runs all tests' do + context = group.children.find { |c| c.description == "bar quarantine" } + examples = context.descendant_filtered_examples + expect(examples.count).to be(2) - ex = group.examples.find { |e| e.description == "foo, bar, and quarantine" } - expect(ex.execution_result.status).to eq(:passed) + ex = examples.find { |e| e.description == "in quarantine" } + expect(ex.execution_result.status).to eq(:passed) + + ex = examples.find { |e| e.description == "not in quarantine" } + expect(ex.execution_result.status).to eq(:passed) + end end end end diff --git a/scripts/static-analysis b/scripts/static-analysis index 25ba7ec6c8e..642c50ec0a8 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -29,6 +29,7 @@ tasks = [ %w[bin/rake lint:all], %w[bundle exec license_finder], %w[yarn run eslint], + %w[yarn run stylelint], %w[yarn run prettier-all], %w[bundle exec rubocop --parallel], %w[scripts/lint-conflicts.sh], diff --git a/spec/config/application_spec.rb b/spec/config/application_spec.rb new file mode 100644 index 00000000000..01ed81964c3 --- /dev/null +++ b/spec/config/application_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Application do # rubocop:disable RSpec/FilePath + using RSpec::Parameterized::TableSyntax + + FILTERED_PARAM = ActionDispatch::Http::ParameterFilter::FILTERED + + context 'when parameters are logged' do + describe 'rails does not leak confidential parameters' do + def request_for_url(input_url) + env = Rack::MockRequest.env_for(input_url) + env['action_dispatch.parameter_filter'] = described_class.config.filter_parameters + + ActionDispatch::Request.new(env) + end + + where(:input_url, :output_query) do + '/' | {} + '/?safe=1' | { 'safe' => '1' } + '/?private_token=secret' | { 'private_token' => FILTERED_PARAM } + '/?mixed=1&private_token=secret' | { 'mixed' => '1', 'private_token' => FILTERED_PARAM } + '/?note=secret¬eable=1&prefix_note=2' | { 'note' => FILTERED_PARAM, 'noteable' => '1', 'prefix_note' => '2' } + '/?note[note]=secret&target_type=1' | { 'note' => FILTERED_PARAM, 'target_type' => '1' } + '/?safe[note]=secret&target_type=1' | { 'safe' => { 'note' => FILTERED_PARAM }, 'target_type' => '1' } + end + + with_them do + it { expect(request_for_url(input_url).filtered_parameters).to eq(output_query) } + end + end + end +end diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index a07113a6156..cf3b24f50a3 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -52,6 +52,23 @@ describe SendFileUpload do end end + context 'with inline image' do + let(:filename) { 'test.png' } + let(:params) { { disposition: 'inline', attachment: filename } } + + it 'sends a file with inline disposition' do + # Notice the filename= is omitted from the disposition; this is because + # Rails 5 will append this header in send_file + expected_params = { + filename: 'test.png', + disposition: "inline; filename*=UTF-8''test.png" + } + expect(controller).to receive(:send_file).with(uploader.path, expected_params) + + subject + end + end + context 'with attachment' do let(:filename) { 'test.js' } let(:params) { { attachment: filename } } diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index 5cb284e7e2d..dca67c18caa 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -37,6 +37,46 @@ describe HelpController do expect(assigns[:help_index]).to eq '[external](https://some.external.link)' end end + + context 'when relative url with external on same line' do + it 'prefix it with /help/' do + stub_readme("[API](api/README.md) [external](https://some.external.link)") + + get :index + + expect(assigns[:help_index]).to eq '[API](/help/api/README.md) [external](https://some.external.link)' + end + end + + context 'when relative url with http:// in query' do + it 'prefix it with /help/' do + stub_readme("[API](api/README.md?go=https://example.com/)") + + get :index + + expect(assigns[:help_index]).to eq '[API](/help/api/README.md?go=https://example.com/)' + end + end + + context 'when mailto URL' do + it 'do not change it' do + stub_readme("[report bug](mailto:bugs@example.com)") + + get :index + + expect(assigns[:help_index]).to eq '[report bug](mailto:bugs@example.com)' + end + end + + context 'when protocol-relative link' do + it 'do not change it' do + stub_readme("[protocol-relative](//example.com)") + + get :index + + expect(assigns[:help_index]).to eq '[protocol-relative](//example.com)' + end + end end describe 'GET #show' do diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 51f158d3045..fd8677feab5 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -126,7 +126,7 @@ describe 'Dashboard Todos' do it 'shows you added a todo message' do page.within('.js-todos-all') do - expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}") + expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}") expect(page).not_to have_content('to yourself') end end @@ -140,7 +140,7 @@ describe 'Dashboard Todos' do it 'shows you mentioned yourself message' do page.within('.js-todos-all') do - expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}") + expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}") expect(page).not_to have_content('to yourself') end end @@ -154,7 +154,7 @@ describe 'Dashboard Todos' do it 'shows you directly addressed yourself message' do page.within('.js-todos-all') do - expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}") + expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}") expect(page).not_to have_content('to yourself') end end @@ -170,7 +170,7 @@ describe 'Dashboard Todos' do it 'shows you set yourself as an approver message' do page.within('.js-todos-all') do - expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}") + expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}") expect(page).not_to have_content('to yourself') end end diff --git a/spec/features/groups/labels/create_spec.rb b/spec/features/groups/labels/create_spec.rb new file mode 100644 index 00000000000..f5062a65321 --- /dev/null +++ b/spec/features/groups/labels/create_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Create a group label' do + let(:user) { create(:user) } + let(:group) { create(:group) } + + before do + group.add_owner(user) + sign_in(user) + visit group_labels_path(group) + end + + it 'creates a new label' do + click_link 'New label' + fill_in 'Title', with: 'test-label' + click_button 'Create label' + + expect(page).to have_content 'test-label' + expect(current_path).to eq(group_labels_path(group)) + end +end diff --git a/spec/features/groups/labels/index_spec.rb b/spec/features/groups/labels/index_spec.rb index 0ce7dad4040..62308d3b518 100644 --- a/spec/features/groups/labels/index_spec.rb +++ b/spec/features/groups/labels/index_spec.rb @@ -1,9 +1,12 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Group labels' do let(:user) { create(:user) } let(:group) { create(:group) } let!(:label) { create(:group_label, group: group) } + let!(:label2) { create(:group_label) } before do group.add_owner(user) @@ -11,7 +14,16 @@ describe 'Group labels' do visit group_labels_path(group) end - it 'label has edit button', :js do + it 'shows labels that belong to the group' do + expect(page).to have_content(label.name) + expect(page).not_to have_content(label2.name) + end + + it 'shows a new label button' do + expect(page).to have_link('New label') + end + + it 'shows an edit label button', :js do expect(page).to have_selector('.label-action.edit') end end diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json index f6c327abfdd..fbde45f2904 100644 --- a/spec/fixtures/api/schemas/public_api/v4/group_labels.json +++ b/spec/fixtures/api/schemas/public_api/v4/group_labels.json @@ -6,6 +6,7 @@ "id" : { "type": "integer" }, "name" : { "type": "string "}, "color" : { "type": "string "}, + "text_color" : { "type": "string "}, "description" : { "type": "string "}, "open_issues_count" : { "type": "integer "}, "closed_issues_count" : { "type": "integer "}, diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index c5e413a29d8..baf6e111f9f 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -14,7 +14,7 @@ import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants'; import diffFileMockData from '../mock_data/diff_file'; import { noteableDataMock } from '../../notes/mock_data'; -const getDiffFileMock = () => Object.assign({}, diffFileMockData); +const getDiffFileMock = () => JSON.parse(JSON.stringify(diffFileMockData)); describe('DiffsStoreUtils', () => { describe('findDiffFile', () => { @@ -80,30 +80,44 @@ describe('DiffsStoreUtils', () => { }); describe('addContextLines', () => { - it('should add context lines properly with bottom parameter', () => { + it('should add context lines', () => { const diffFile = getDiffFileMock(); const inlineLines = diffFile.highlighted_diff_lines; const parallelLines = diffFile.parallel_diff_lines; const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 }; - const contextLines = [{ lineNumber: 42 }]; - const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true }; + const contextLines = [{ lineNumber: 42, line_code: '123' }]; + const options = { inlineLines, parallelLines, contextLines, lineNumbers }; const inlineIndex = utils.findIndexInInlineLines(inlineLines, lineNumbers); const parallelIndex = utils.findIndexInParallelLines(parallelLines, lineNumbers); const normalizedParallelLine = { left: options.contextLines[0], right: options.contextLines[0], + line_code: '123', }; utils.addContextLines(options); - expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]); - expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine); + expect(inlineLines[inlineIndex]).toEqual(contextLines[0]); + expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine); + }); + + it('should add context lines properly with bottom parameter', () => { + const diffFile = getDiffFileMock(); + const inlineLines = diffFile.highlighted_diff_lines; + const parallelLines = diffFile.parallel_diff_lines; + const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 }; + const contextLines = [{ lineNumber: 42, line_code: '123' }]; + const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true }; + const normalizedParallelLine = { + left: options.contextLines[0], + right: options.contextLines[0], + line_code: '123', + }; - delete options.bottom; utils.addContextLines(options); - expect(inlineLines[inlineIndex]).toEqual(contextLines[0]); - expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine); + expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]); + expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine); }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 9aa3cbaa231..6230da77f49 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -755,6 +755,17 @@ describe('Filtered Search Visual Tokens', () => { expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0); }); + it('does not update user token appearance for `None` filter', () => { + const { tokenNameElement } = findElements(authorToken); + + const tokenName = tokenNameElement.innerText; + const tokenValue = 'None'; + + subject.renderVisualTokenValue(authorToken, tokenName, tokenValue); + + expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0); + }); + it('does not update user token appearance for `none` filter', () => { const { tokenNameElement } = findElements(authorToken); diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js index 1972408356e..88652202a8e 100644 --- a/spec/javascripts/helpers/vuex_action_helper.js +++ b/spec/javascripts/helpers/vuex_action_helper.js @@ -84,7 +84,10 @@ export default ( done(); }; - const result = action({ commit, state, dispatch, rootState: state, rootGetters: state }, payload); + const result = action( + { commit, state, dispatch, rootState: state, rootGetters: state, getters: state }, + payload, + ); return new Promise(resolve => { setImmediate(resolve); diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js index 595a2f927e9..d94cc1a8faa 100644 --- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js @@ -41,6 +41,15 @@ describe('new file modal component', () => { expect(vm.$el.querySelector('.label-bold').textContent.trim()).toBe('Name'); }); + it(`${type === 'tree' ? 'does not show' : 'shows'} file templates`, () => { + const templateFilesEl = vm.$el.querySelector('.file-templates'); + if (type === 'tree') { + expect(templateFilesEl).toBeNull(); + } else { + expect(templateFilesEl instanceof Element).toBeTruthy(); + } + }); + describe('createEntryInStore', () => { it('$emits create', () => { spyOn(vm, 'createTempEntry'); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 3eff3f655ee..c02e37950f8 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -855,6 +855,7 @@ describe('common_utils', () => { }); it('returns true when provided `el` is in viewport', () => { + el.setAttribute('style', `position: absolute; right: ${window.innerWidth + 0.2};`); document.body.appendChild(el); expect(commonUtils.isInViewport(el)).toBe(true); diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js index 8ade6fc2ced..9420713ceca 100644 --- a/spec/javascripts/notes/components/noteable_note_spec.js +++ b/spec/javascripts/notes/components/noteable_note_spec.js @@ -1,86 +1,138 @@ -import $ from 'jquery'; import _ from 'underscore'; -import Vue from 'vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import createStore from '~/notes/stores'; import issueNote from '~/notes/components/noteable_note.vue'; +import NoteHeader from '~/notes/components/note_header.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import NoteActions from '~/notes/components/note_actions.vue'; +import NoteBody from '~/notes/components/note_body.vue'; import { noteableDataMock, notesDataMock, note } from '../mock_data'; describe('issue_note', () => { let store; - let vm; + let wrapper; beforeEach(() => { - const Component = Vue.extend(issueNote); - store = createStore(); store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); - vm = new Component({ + const localVue = createLocalVue(); + wrapper = shallowMount(issueNote, { store, propsData: { note, }, - }).$mount(); + sync: false, + localVue, + }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('should render user information', () => { - expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual( - note.author.avatar_url, - ); + const { author } = note; + const avatar = wrapper.find(UserAvatarLink); + const avatarProps = avatar.props(); + + expect(avatarProps.linkHref).toBe(author.path); + expect(avatarProps.imgSrc).toBe(author.avatar_url); + expect(avatarProps.imgAlt).toBe(author.name); + expect(avatarProps.imgSize).toBe(40); }); it('should render note header content', () => { - const el = vm.$el.querySelector('.note-header .note-header-author-name'); + const noteHeader = wrapper.find(NoteHeader); + const noteHeaderProps = noteHeader.props(); - expect(el.textContent.trim()).toEqual(note.author.name); + expect(noteHeaderProps.author).toEqual(note.author); + expect(noteHeaderProps.createdAt).toEqual(note.created_at); + expect(noteHeaderProps.noteId).toEqual(note.id); }); it('should render note actions', () => { - expect(vm.$el.querySelector('.note-actions')).toBeDefined(); + const { author } = note; + const noteActions = wrapper.find(NoteActions); + const noteActionsProps = noteActions.props(); + + expect(noteActionsProps.authorId).toBe(author.id); + expect(noteActionsProps.noteId).toBe(note.id); + expect(noteActionsProps.noteUrl).toBe(note.noteable_note_url); + expect(noteActionsProps.accessLevel).toBe(note.human_access); + expect(noteActionsProps.canEdit).toBe(note.current_user.can_edit); + expect(noteActionsProps.canAwardEmoji).toBe(note.current_user.can_award_emoji); + expect(noteActionsProps.canDelete).toBe(note.current_user.can_edit); + expect(noteActionsProps.canReportAsAbuse).toBe(true); + expect(noteActionsProps.canResolve).toBe(false); + expect(noteActionsProps.reportAbusePath).toBe(note.report_abuse_path); + expect(noteActionsProps.resolvable).toBe(false); + expect(noteActionsProps.isResolved).toBe(false); + expect(noteActionsProps.isResolving).toBe(false); + expect(noteActionsProps.resolvedBy).toEqual({}); }); it('should render issue body', () => { - expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html); + const noteBody = wrapper.find(NoteBody); + const noteBodyProps = noteBody.props(); + + expect(noteBodyProps.note).toEqual(note); + expect(noteBodyProps.line).toBe(null); + expect(noteBodyProps.canEdit).toBe(note.current_user.can_edit); + expect(noteBodyProps.isEditing).toBe(false); + expect(noteBodyProps.helpPagePath).toBe(''); }); it('prevents note preview xss', done => { const imgSrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; const noteBody = `<img src="${imgSrc}" onload="alert(1)" />`; const alertSpy = spyOn(window, 'alert'); - vm.updateNote = () => new Promise($.noop); + store.hotUpdate({ + actions: { + updateNote() {}, + }, + }); + const noteBodyComponent = wrapper.find(NoteBody); - vm.formUpdateHandler(noteBody, null, $.noop); + noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {}); setTimeout(() => { expect(alertSpy).not.toHaveBeenCalled(); - expect(vm.note.note_html).toEqual(_.escape(noteBody)); + expect(wrapper.vm.note.note_html).toEqual(_.escape(noteBody)); done(); }, 0); }); describe('cancel edit', () => { it('restores content of updated note', done => { - const noteBody = 'updated note text'; - vm.updateNote = () => Promise.resolve(); - - vm.formUpdateHandler(noteBody, null, $.noop); - - setTimeout(() => { - expect(vm.note.note_html).toEqual(noteBody); - - vm.formCancelHandler(); - - setTimeout(() => { - expect(vm.note.note_html).toEqual(noteBody); - - done(); - }); + const updatedText = 'updated note text'; + store.hotUpdate({ + actions: { + updateNote() {}, + }, }); + const noteBody = wrapper.find(NoteBody); + noteBody.vm.resetAutoSave = () => {}; + + noteBody.vm.$emit('handleFormUpdate', updatedText, null, () => {}); + + wrapper.vm + .$nextTick() + .then(() => { + const noteBodyProps = noteBody.props(); + + expect(noteBodyProps.note.note_html).toBe(updatedText); + noteBody.vm.$emit('cancelForm'); + }) + .then(() => wrapper.vm.$nextTick()) + .then(() => { + const noteBodyProps = noteBody.props(); + + expect(noteBodyProps.note.note_html).toBe(note.note_html); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 73f960dd21e..4f70570ffcc 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -1,8 +1,11 @@ import Vue from 'vue'; import $ from 'jquery'; import _ from 'underscore'; +import { TEST_HOST } from 'spec/test_constants'; import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; import * as actions from '~/notes/stores/actions'; +import * as mutationTypes from '~/notes/stores/mutation_types'; +import * as notesConstants from '~/notes/constants'; import createStore from '~/notes/stores'; import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub'; import testAction from '../../helpers/vuex_action_helper'; @@ -599,4 +602,139 @@ describe('Actions Notes Store', () => { ); }); }); + + describe('updateOrCreateNotes', () => { + let commit; + let dispatch; + let state; + + beforeEach(() => { + commit = jasmine.createSpy('commit'); + dispatch = jasmine.createSpy('dispatch'); + state = {}; + }); + + afterEach(() => { + commit.calls.reset(); + dispatch.calls.reset(); + }); + + it('Updates existing note', () => { + const note = { id: 1234 }; + const getters = { notesById: { 1234: note } }; + + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]); + + expect(commit.calls.allArgs()).toEqual([[mutationTypes.UPDATE_NOTE, note]]); + }); + + it('Creates a new note if none exisits', () => { + const note = { id: 1234 }; + const getters = { notesById: {} }; + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]); + + expect(commit.calls.allArgs()).toEqual([[mutationTypes.ADD_NEW_NOTE, note]]); + }); + + describe('Discussion notes', () => { + let note; + let getters; + + beforeEach(() => { + note = { id: 1234 }; + getters = { notesById: {} }; + }); + + it('Adds a reply to an existing discussion', () => { + state = { discussions: [note] }; + const discussionNote = { + ...note, + type: notesConstants.DISCUSSION_NOTE, + discussion_id: 1234, + }; + + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [discussionNote]); + + expect(commit.calls.allArgs()).toEqual([ + [mutationTypes.ADD_NEW_REPLY_TO_DISCUSSION, discussionNote], + ]); + }); + + it('fetches discussions for diff notes', () => { + state = { discussions: [], notesData: { discussionsPath: 'Hello world' } }; + const diffNote = { ...note, type: notesConstants.DIFF_NOTE, discussion_id: 1234 }; + + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [diffNote]); + + expect(dispatch.calls.allArgs()).toEqual([ + ['fetchDiscussions', { path: state.notesData.discussionsPath }], + ]); + }); + + it('Adds a new note', () => { + state = { discussions: [] }; + const discussionNote = { + ...note, + type: notesConstants.DISCUSSION_NOTE, + discussion_id: 1234, + }; + + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [discussionNote]); + + expect(commit.calls.allArgs()).toEqual([[mutationTypes.ADD_NEW_NOTE, discussionNote]]); + }); + }); + }); + + describe('replyToDiscussion', () => { + let res = { discussion: { notes: [] } }; + const payload = { endpoint: TEST_HOST, data: {} }; + const interceptor = (request, next) => { + next( + request.respondWith(JSON.stringify(res), { + status: 200, + }), + ); + }; + + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); + + it('updates discussion if response contains disussion', done => { + testAction( + actions.replyToDiscussion, + payload, + { + notesById: {}, + }, + [{ type: mutationTypes.UPDATE_DISCUSSION, payload: res.discussion }], + [ + { type: 'updateMergeRequestWidget' }, + { type: 'startTaskList' }, + { type: 'updateResolvableDiscussonsCounts' }, + ], + done, + ); + }); + + it('adds a reply to a discussion', done => { + res = {}; + + testAction( + actions.replyToDiscussion, + payload, + { + notesById: {}, + }, + [{ type: mutationTypes.ADD_NEW_REPLY_TO_DISCUSSION, payload: res }], + [], + done, + ); + }); + }); }); diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index 4f8d3069bb5..88c40dbc5ea 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -527,17 +527,13 @@ describe('Notes Store mutations', () => { id: 42, individual_note: true, }; - state = { discussions: [discussion] }; + state = { convertedDisscussionIds: [] }; }); - it('toggles individual_note', () => { + it('adds a disucssion to convertedDisscussionIds', () => { mutations.CONVERT_TO_DISCUSSION(state, discussion.id); - expect(discussion.individual_note).toBe(false); - }); - - it('throws if discussion was not found', () => { - expect(() => mutations.CONVERT_TO_DISCUSSION(state, 99)).toThrow(); + expect(state.convertedDisscussionIds).toContain(discussion.id); }); }); }); diff --git a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js index 5b1038840c7..634ba8403d5 100644 --- a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js +++ b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js @@ -5,27 +5,40 @@ import createComponent from 'spec/helpers/vue_mount_component_helper'; describe('Changed file icon', () => { let vm; - beforeEach(() => { + function factory(props = {}) { const component = Vue.extend(changedFileIcon); vm = createComponent(component, { + ...props, file: { tempFile: false, changed: true, }, }); - }); + } afterEach(() => { vm.$destroy(); }); + it('centers icon', () => { + factory({ + isCentered: true, + }); + + expect(vm.$el.classList).toContain('ml-auto'); + }); + describe('changedIcon', () => { it('equals file-modified when not a temp file and has changes', () => { + factory(); + expect(vm.changedIcon).toBe('file-modified'); }); it('equals file-addition when a temp file', () => { + factory(); + vm.file.tempFile = true; expect(vm.changedIcon).toBe('file-addition'); @@ -34,10 +47,14 @@ describe('Changed file icon', () => { describe('changedIconClass', () => { it('includes file-modified when not a temp file', () => { + factory(); + expect(vm.changedIconClass).toContain('file-modified'); }); it('includes file-addition when a temp file', () => { + factory(); + vm.file.tempFile = true; expect(vm.changedIconClass).toContain('file-addition'); diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js index f2472fd377c..80aa75847ae 100644 --- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js +++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js @@ -1,13 +1,14 @@ import _ from 'underscore'; import Vue from 'vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import { TEST_HOST } from 'spec/test_constants'; describe('User Avatar Link Component', function() { beforeEach(function() { this.propsData = { - linkHref: 'myavatarurl.com', + linkHref: `${TEST_HOST}/myavatarurl.com`, imgSize: 99, - imgSrc: 'myavatarurl.com', + imgSrc: `${TEST_HOST}/myavatarurl.com`, imgAlt: 'mydisplayname', imgCssClasses: 'myextraavatarclass', tooltipText: 'tooltip text', @@ -37,11 +38,18 @@ describe('User Avatar Link Component', function() { }); it('should render <a> as a child element', function() { - expect(this.userAvatarLink.$el.tagName).toBe('A'); + const link = this.userAvatarLink.$el; + + expect(link.tagName).toBe('A'); + expect(link.href).toBe(this.propsData.linkHref); }); - it('should have <img> as a child element', function() { - expect(this.userAvatarLink.$el.querySelector('img')).not.toBeNull(); + it('renders imgSrc with imgSize as image', function() { + const { imgSrc, imgSize } = this.propsData; + const image = this.userAvatarLink.$el.querySelector('img'); + + expect(image).not.toBeNull(); + expect(image.src).toBe(`${imgSrc}?width=${imgSize}`); }); it('should return necessary props as defined', function() { diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb index 2e50e4e2351..c6dcb4e46fd 100644 --- a/spec/lib/banzai/filter/footnote_filter_spec.rb +++ b/spec/lib/banzai/filter/footnote_filter_spec.rb @@ -11,6 +11,7 @@ describe Banzai::Filter::FootnoteFilter do let(:footnote) do <<~EOF <p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p> + <p>same reference<sup><a href="#fn1" id="fnref1">1</a></sup></p> <ol> <li id="fn1"> <p>one <a href="#fnref1">↩</a></p> @@ -25,6 +26,7 @@ describe Banzai::Filter::FootnoteFilter do let(:filtered_footnote) do <<~EOF <p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p> + <p>same reference<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup></p> <section class="footnotes"><ol> <li id="fn1-#{identifier}"> <p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p> diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb index 5fee37bb43e..dc3329061d1 100644 --- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb @@ -73,9 +73,9 @@ describe Gitlab::Ci::Build::Policy::Changes do expect(policy).not_to be_satisfied_by(pipeline, seed) end - context 'when pipelines does not run for a branch update' do + context 'when modified paths can not be evaluated' do before do - pipeline.before_sha = Gitlab::Git::BLANK_SHA + allow(pipeline).to receive(:modified_paths) { nil } end it 'is always satisfied' do @@ -115,5 +115,57 @@ describe Gitlab::Ci::Build::Policy::Changes do expect(policy).not_to be_satisfied_by(pipeline, seed) end end + + context 'when branch is created' do + let(:pipeline) do + create(:ci_empty_pipeline, project: project, + ref: 'feature', + source: source, + sha: '0b4bc9a4', + before_sha: Gitlab::Git::BLANK_SHA, + merge_request: merge_request) + end + + let(:ci_build) do + build(:ci_build, pipeline: pipeline, project: project, ref: 'feature') + end + + let(:seed) { double('build seed', to_resource: ci_build) } + + context 'when source is merge request' do + let(:source) { :merge_request } + + let(:merge_request) do + create(:merge_request, + source_project: project, + source_branch: 'feature', + target_project: project, + target_branch: 'master') + end + + it 'is satified by changes in the merge request' do + policy = described_class.new(%w[files/ruby/feature.rb]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satified by changes not in the merge request' do + policy = described_class.new(%w[foo.rb]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + end + + context 'when source is push' do + let(:source) { :push } + let(:merge_request) { nil } + + it 'is always satified' do + policy = described_class.new(%w[foo.rb]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + end + end end end diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb new file mode 100644 index 00000000000..ac3e4b04230 --- /dev/null +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -0,0 +1,295 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' +require 'webmock/rspec' + +require 'gitlab/danger/helper' + +describe Gitlab::Danger::Helper do + using RSpec::Parameterized::TableSyntax + + class FakeDanger + include Gitlab::Danger::Helper + + attr_reader :git + + def initialize(git:) + @git = git + end + end + + let(:teammate_json) do + <<~JSON + [ + { + "username": "in-gitlab-ce", + "name": "CE maintainer", + "projects":{ "gitlab-ce": "maintainer backend" } + }, + { + "username": "in-gitlab-ee", + "name": "EE reviewer", + "projects":{ "gitlab-ee": "reviewer frontend" } + } + ] + JSON + end + + let(:ce_teammate_matcher) do + satisfy do |teammate| + teammate.username == 'in-gitlab-ce' && + teammate.name == 'CE maintainer' && + teammate.projects == { 'gitlab-ce' => 'maintainer backend' } + end + end + + let(:ee_teammate_matcher) do + satisfy do |teammate| + teammate.username == 'in-gitlab-ee' && + teammate.name == 'EE reviewer' && + teammate.projects == { 'gitlab-ee' => 'reviewer frontend' } + end + end + + let(:fake_git) { double('fake-git') } + + subject(:helper) { FakeDanger.new(git: fake_git) } + + describe '#all_changed_files' do + subject { helper.all_changed_files } + + it 'interprets a list of changes from the danger git plugin' do + expect(fake_git).to receive(:added_files) { %w[a b c.old] } + expect(fake_git).to receive(:modified_files) { %w[d e] } + expect(fake_git) + .to receive(:renamed_files) + .at_least(:once) + .and_return([{ before: 'c.old', after: 'c.new' }]) + + is_expected.to contain_exactly('a', 'b', 'c.new', 'd', 'e') + end + end + + describe '#ee?' do + subject { helper.ee? } + + it 'returns true if CI_PROJECT_NAME if set to gitlab-ee' do + stub_env('CI_PROJECT_NAME', 'gitlab-ee') + expect(File).not_to receive(:exist?) + + is_expected.to be_truthy + end + + it 'delegates to CHANGELOG-EE.md existence if CI_PROJECT_NAME is set to something else' do + stub_env('CI_PROJECT_NAME', 'something else') + expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true } + + is_expected.to be_truthy + end + + it 'returns true if CHANGELOG-EE.md exists' do + stub_env('CI_PROJECT_NAME', nil) + expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true } + + is_expected.to be_truthy + end + + it "returns false if CHANGELOG-EE.md doesn't exist" do + stub_env('CI_PROJECT_NAME', nil) + expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { false } + + is_expected.to be_falsy + end + end + + describe '#project_name' do + subject { helper.project_name } + + it 'returns gitlab-ee if ee? returns true' do + expect(helper).to receive(:ee?) { true } + + is_expected.to eq('gitlab-ee') + end + + it 'returns gitlab-ce if ee? returns false' do + expect(helper).to receive(:ee?) { false } + + is_expected.to eq('gitlab-ce') + end + end + + describe '#team' do + subject(:team) { helper.team } + + context 'HTTP failure' do + before do + WebMock + .stub_request(:get, 'https://about.gitlab.com/roulette.json') + .to_return(status: 404) + end + + it 'raises a pretty error' do + expect { team }.to raise_error(/Failed to read/) + end + end + + context 'JSON failure' do + before do + WebMock + .stub_request(:get, 'https://about.gitlab.com/roulette.json') + .to_return(body: 'INVALID JSON') + end + + it 'raises a pretty error' do + expect { team }.to raise_error(/Failed to parse/) + end + end + + context 'success' do + before do + WebMock + .stub_request(:get, 'https://about.gitlab.com/roulette.json') + .to_return(body: teammate_json) + end + + it 'returns an array of teammates' do + is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher) + end + + it 'memoizes the result' do + expect(team.object_id).to eq(helper.team.object_id) + end + end + end + + describe '#project_team' do + subject { helper.project_team } + + before do + WebMock + .stub_request(:get, 'https://about.gitlab.com/roulette.json') + .to_return(body: teammate_json) + end + + it 'filters team by project_name' do + expect(helper) + .to receive(:project_name) + .at_least(:once) + .and_return('gitlab-ce') + + is_expected.to contain_exactly(ce_teammate_matcher) + end + end + + describe '#changes_by_category' do + it 'categorizes changed files' do + expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo qa/foo] } + allow(fake_git).to receive(:modified_files) { [] } + allow(fake_git).to receive(:renamed_files) { [] } + + expect(helper.changes_by_category).to eq( + backend: %w[foo.rb], + database: %w[db/foo], + docs: %w[foo.md], + frontend: %w[foo.js], + qa: %w[qa/foo], + unknown: %w[foo] + ) + end + end + + describe '#category_for_file' do + where(:path, :expected_category) do + 'doc/foo' | :docs + 'CONTRIBUTING.md' | :docs + 'LICENSE' | :docs + 'MAINTENANCE.md' | :docs + 'PHILOSOPHY.md' | :docs + 'PROCESS.md' | :docs + 'README.md' | :docs + + 'ee/doc/foo' | :unknown + 'ee/README' | :unknown + + 'app/assets/foo' | :frontend + 'app/views/foo' | :frontend + 'public/foo' | :frontend + 'spec/javascripts/foo' | :frontend + 'vendor/assets/foo' | :frontend + 'jest.config.js' | :frontend + 'package.json' | :frontend + 'yarn.lock' | :frontend + + 'ee/app/assets/foo' | :frontend + 'ee/app/views/foo' | :frontend + 'ee/spec/javascripts/foo' | :frontend + + 'app/models/foo' | :backend + 'bin/foo' | :backend + 'config/foo' | :backend + 'danger/foo' | :backend + 'lib/foo' | :backend + 'rubocop/foo' | :backend + 'scripts/foo' | :backend + 'spec/foo' | :backend + 'spec/foo/bar' | :backend + + 'ee/app/foo' | :backend + 'ee/bin/foo' | :backend + 'ee/spec/foo' | :backend + 'ee/spec/foo/bar' | :backend + + 'generator_templates/foo' | :backend + 'vendor/languages.yml' | :backend + 'vendor/licenses.csv' | :backend + + 'Dangerfile' | :backend + 'Gemfile' | :backend + 'Gemfile.lock' | :backend + 'Procfile' | :backend + 'Rakefile' | :backend + '.gitlab-ci.yml' | :backend + 'FOO_VERSION' | :backend + + 'ee/FOO_VERSION' | :unknown + + 'db/foo' | :database + 'qa/foo' | :qa + + 'ee/db/foo' | :database + 'ee/qa/foo' | :qa + + 'FOO' | :unknown + 'foo' | :unknown + + 'foo/bar.rb' | :backend + 'foo/bar.js' | :frontend + 'foo/bar.txt' | :docs + 'foo/bar.md' | :docs + end + + with_them do + subject { helper.category_for_file(path) } + + it { is_expected.to eq(expected_category) } + end + end + + describe '#label_for_category' do + where(:category, :expected_label) do + :backend | '~backend' + :database | '~database' + :docs | '~Documentation' + :foo | '~foo' + :frontend | '~frontend' + :qa | '~QA' + end + + with_them do + subject { helper.label_for_category(category) } + + it { is_expected.to eq(expected_label) } + end + end +end diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb index f2d750c6595..2c288cff6ef 100644 --- a/spec/lib/gitlab/import_export/shared_spec.rb +++ b/spec/lib/gitlab/import_export/shared_spec.rb @@ -14,6 +14,16 @@ describe Gitlab::ImportExport::Shared do expect(subject.errors).to eq(['Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]']) end + it 'updates the import JID' do + import_state = create(:import_state, project: project, jid: 'jid-test') + + expect_next_instance_of(Gitlab::Import::Logger) do |logger| + expect(logger).to receive(:error).with(hash_including(import_jid: import_state.jid)) + end + + subject.error(error) + end + it 'calls the error logger with the full message' do expect(subject).to receive(:log_error).with(hash_including(message: error.message)) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 72a0df96a80..460b5c8cd31 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1172,8 +1172,26 @@ describe Ci::Pipeline, :mailer do pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA) end - it 'raises an error' do - expect { pipeline.modified_paths }.to raise_error(ArgumentError) + it 'returns nil' do + expect(pipeline.modified_paths).to be_nil + end + end + + context 'when source is merge request' do + let(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request) + end + + let(:merge_request) do + create(:merge_request, + source_project: project, + source_branch: 'feature', + target_project: project, + target_branch: 'master') + end + + it 'returns merge request modified paths' do + expect(pipeline.modified_paths).to match(merge_request.modified_paths) end end end diff --git a/spec/models/releases/link_spec.rb b/spec/models/releases/link_spec.rb index 06ed1438688..4dd26c976cc 100644 --- a/spec/models/releases/link_spec.rb +++ b/spec/models/releases/link_spec.rb @@ -77,4 +77,28 @@ describe Releases::Link do it { is_expected.to be_truthy } end + + describe 'supported protocols' do + where(:protocol) do + %w(http https ftp) + end + + with_them do + let(:link) { build(:release_link, url: protocol + '://assets.com/download') } + + it 'will be valid' do + expect(link).to be_valid + end + end + end + + describe 'unsupported protocol' do + context 'for torrent' do + let(:link) { build(:release_link, url: 'torrent://assets.com/download') } + + it 'will be invalid' do + expect(link).to be_invalid + end + end + end end diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb new file mode 100644 index 00000000000..4b76d65ef69 --- /dev/null +++ b/spec/policies/board_policy_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe BoardPolicy do + let(:user) { create(:user) } + let(:project) { create(:project, :private) } + let(:group) { create(:group, :private) } + let(:group_board) { create(:board, group: group) } + let(:project_board) { create(:board, project: project) } + + let(:board_permissions) do + [ + :read_parent, + :read_milestone, + :read_issue + ] + end + + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } + end + + context 'group board' do + subject { described_class.new(user, group_board) } + + context 'user has access' do + before do + group.add_developer(user) + end + + it do + expect_allowed(*board_permissions) + end + end + + context 'user does not have access' do + it do + expect_disallowed(*board_permissions) + end + end + end + + context 'project board' do + subject { described_class.new(user, project_board) } + + context 'user has access' do + before do + project.add_developer(user) + end + + it do + expect_allowed(*board_permissions) + end + end + + context 'user does not have access' do + it do + expect_disallowed(*board_permissions) + end + end + end +end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 6b9bc6eda6a..066f1d6862a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -237,7 +237,7 @@ describe API::Commits do end describe 'create' do - let(:message) { 'Created file' } + let(:message) { 'Created a new file with a very very looooooooooooooooooooooooooooooooooooooooooooooong commit message' } let(:invalid_c_params) do { branch: 'master', @@ -1457,4 +1457,42 @@ describe API::Commits do expect(response).to have_gitlab_http_status(404) end end + + describe 'GET /projects/:id/repository/commits/:sha/signature' do + let!(:project) { create(:project, :repository, :public) } + let(:project_id) { project.id } + let(:commit_id) { project.repository.commit.id } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/signature" } + + context 'when commit does not exist' do + let(:commit_id) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Commit Not Found' } + end + end + + context 'unsigned commit' do + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 GPG Signature Not Found'} + end + end + + context 'signed commit' do + let(:commit) { project.repository.commit(GpgHelpers::SIGNED_COMMIT_SHA) } + let(:commit_id) { commit.id } + + it 'returns correct JSON' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['gpg_key_id']).to eq(commit.signature.gpg_key_id) + expect(json_response['gpg_key_subkey_id']).to eq(commit.signature.gpg_key_subkey_id) + expect(json_response['gpg_key_primary_keyid']).to eq(commit.signature.gpg_key_primary_keyid) + expect(json_response['verification_status']).to eq(commit.signature.verification_status) + end + end + end end diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 49eea2e362b..518181e4d93 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -20,9 +20,9 @@ describe API::Labels do create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project ) expected_keys = %w( - id name color description + id name color text_color description open_issues_count closed_issues_count open_merge_requests_count - subscribed priority + subscribed priority is_project_label ) get api("/projects/#{project.id}/labels", user) @@ -43,27 +43,33 @@ describe API::Labels do expect(label1_response['open_merge_requests_count']).to eq(0) expect(label1_response['name']).to eq(label1.name) expect(label1_response['color']).to be_present + expect(label1_response['text_color']).to be_present expect(label1_response['description']).to be_nil expect(label1_response['priority']).to be_nil expect(label1_response['subscribed']).to be_falsey + expect(label1_response['is_project_label']).to be_truthy expect(group_label_response['open_issues_count']).to eq(1) expect(group_label_response['closed_issues_count']).to eq(0) expect(group_label_response['open_merge_requests_count']).to eq(0) expect(group_label_response['name']).to eq(group_label.name) expect(group_label_response['color']).to be_present + expect(group_label_response['text_color']).to be_present expect(group_label_response['description']).to be_nil expect(group_label_response['priority']).to be_nil expect(group_label_response['subscribed']).to be_falsey + expect(group_label_response['is_project_label']).to be_falsey expect(priority_label_response['open_issues_count']).to eq(0) expect(priority_label_response['closed_issues_count']).to eq(0) expect(priority_label_response['open_merge_requests_count']).to eq(1) expect(priority_label_response['name']).to eq(priority_label.name) expect(priority_label_response['color']).to be_present + expect(priority_label_response['text_color']).to be_present expect(priority_label_response['description']).to be_nil expect(priority_label_response['priority']).to eq(3) expect(priority_label_response['subscribed']).to be_falsey + expect(priority_label_response['is_project_label']).to be_truthy end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 0f5f6e38819..b8426126bc6 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -372,6 +372,7 @@ describe API::MergeRequests do expect(json_response['force_close_merge_request']).to be_falsy expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size) expect(json_response['merge_error']).to eq(merge_request.merge_error) + expect(json_response['user']['can_merge']).to be_truthy expect(json_response).not_to include('rebase_in_progress') end @@ -499,6 +500,15 @@ describe API::MergeRequests do expect(json_response['allow_maintainer_to_push']).to be_truthy end end + + it 'indicates if a user cannot merge the MR' do + user2 = create(:user) + project.add_reporter(user2) + + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user2) + + expect(json_response['user']['can_merge']).to be_falsy + end end describe 'GET /projects/:id/merge_requests/:merge_request_iid/participants' do diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index 6109829aad1..d1b58aac104 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -100,6 +100,8 @@ describe API::Wikis do shared_examples_for 'updates wiki page' do it 'updates the wiki page' do + put(api(url, user), params: payload) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(4) expect(json_response.keys).to match_array(expected_keys_with_content) @@ -107,6 +109,16 @@ describe API::Wikis do expect(json_response['slug']).to eq(payload[:title].tr(' ', '-')) expect(json_response['title']).to eq(payload[:title]) end + + [:title, :content, :format].each do |part| + it "it updates with wiki with missing #{part}" do + payload.delete(part) + + put(api(url, user), params: payload) + + expect(response).to have_gitlab_http_status(200) + end + end end shared_examples_for '403 Forbidden' do @@ -528,8 +540,6 @@ describe API::Wikis do context 'when user is developer' do before do project.add_developer(user) - - put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -537,6 +547,10 @@ describe API::Wikis do context 'when page is not existing' do let(:url) { "/projects/#{project.id}/wikis/unknown" } + before do + put(api(url, user), params: payload) + end + include_examples '404 Wiki Page Not Found' end end @@ -544,8 +558,6 @@ describe API::Wikis do context 'when user is maintainer' do before do project.add_maintainer(user) - - put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -553,6 +565,10 @@ describe API::Wikis do context 'when page is not existing' do let(:url) { "/projects/#{project.id}/wikis/unknown" } + before do + put(api(url, user), params: payload) + end + include_examples '404 Wiki Page Not Found' end end @@ -572,8 +588,6 @@ describe API::Wikis do context 'when user is developer' do before do project.add_developer(user) - - put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -581,6 +595,10 @@ describe API::Wikis do context 'when page is not existing' do let(:url) { "/projects/#{project.id}/wikis/unknown" } + before do + put(api(url, user), params: payload) + end + include_examples '404 Wiki Page Not Found' end end @@ -588,8 +606,6 @@ describe API::Wikis do context 'when user is maintainer' do before do project.add_maintainer(user) - - put(api(url, user), params: payload) end include_examples 'updates wiki page' @@ -597,6 +613,10 @@ describe API::Wikis do context 'when page is not existing' do let(:url) { "/projects/#{project.id}/wikis/unknown" } + before do + put(api(url, user), params: payload) + end + include_examples '404 Wiki Page Not Found' end end @@ -605,10 +625,6 @@ describe API::Wikis do context 'when wiki belongs to a group project' do let(:project) { create(:project, :wiki_repo, namespace: group) } - before do - put(api(url, user), params: payload) - end - include_examples 'updates wiki page' end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 48f1d696ff6..1645b67c329 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -311,7 +311,14 @@ describe Notes::CreateService do end it 'converts existing note to DiscussionNote' do - expect { subject }.to change { existing_note.reload.type }.from(nil).to('DiscussionNote') + expect do + existing_note + + Timecop.freeze(Time.now + 1.minute) { subject } + + existing_note.reload + end.to change { existing_note.type }.from(nil).to('DiscussionNote') + .and change { existing_note.updated_at } end end end diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb index 7c5480d382f..b1260cf740a 100644 --- a/spec/services/task_list_toggle_service_spec.rb +++ b/spec/services/task_list_toggle_service_spec.rb @@ -12,6 +12,10 @@ describe TaskListToggleService do 1. [X] Item 1 - [ ] Sub-item 1 + + - [ ] loose list + + with an embedded paragraph EOT end @@ -26,16 +30,22 @@ describe TaskListToggleService do </li> </ul> <p data-sourcepos="4:1-4:11" dir="auto">A paragraph</p> - <ol data-sourcepos="6:1-7:19" class="task-list" dir="auto"> - <li data-sourcepos="6:1-7:19" class="task-list-item"> - <input type="checkbox" class="task-list-item-checkbox" disabled checked> Item 1 - <ul data-sourcepos="7:4-7:19" class="task-list"> - <li data-sourcepos="7:4-7:19" class="task-list-item"> - <input type="checkbox" class="task-list-item-checkbox" disabled> Sub-item 1 + <ol data-sourcepos="6:1-8:0" class="task-list" dir="auto"> + <li data-sourcepos="6:1-8:0" class="task-list-item"> + <input type="checkbox" class="task-list-item-checkbox" checked="" disabled=""> Item 1 + <ul data-sourcepos="7:4-8:0" class="task-list"> + <li data-sourcepos="7:4-8:0" class="task-list-item"> + <input type="checkbox" class="task-list-item-checkbox" disabled=""> Sub-item 1 </li> </ul> </li> </ol> + <ul data-sourcepos="9:1-11:28" class="task-list" dir="auto"> + <li data-sourcepos="9:1-11:28" class="task-list-item"> + <p data-sourcepos="9:3-9:16"><input type="checkbox" class="task-list-item-checkbox" disabled=""> loose list</p> + <p data-sourcepos="11:3-11:28">with an embedded paragraph</p> + </li> + </ul> EOT end @@ -59,6 +69,16 @@ describe TaskListToggleService do expect(toggler.updated_markdown_html).to include('disabled> Item 1') end + it 'checks task in loose list' do + toggler = described_class.new(markdown, markdown_html, + toggle_as_checked: true, + line_source: '- [ ] loose list', line_number: 9) + + expect(toggler.execute).to be_truthy + expect(toggler.updated_markdown.lines[8]).to eq "- [x] loose list\n" + expect(toggler.updated_markdown_html).to include('disabled checked> loose list') + end + it 'returns false if line_source does not match the text' do toggler = described_class.new(markdown, markdown_html, toggle_as_checked: false, diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index 719b4adf212..3c0a4ac8e18 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -26,6 +26,12 @@ describe Users::ActivityService do .from(last_activity_on) .to(Date.today) end + + it 'tries to obtain ExclusiveLease' do + expect(Gitlab::ExclusiveLease).to receive(:new).and_call_original + + subject.execute + end end context 'when a bad object is passed' do @@ -46,6 +52,12 @@ describe Users::ActivityService do it 'does not update last_activity_on' do expect { subject.execute }.not_to change(user, :last_activity_on) end + + it 'does not try to obtain ExclusiveLease' do + expect(Gitlab::ExclusiveLease).not_to receive(:new) + + subject.execute + end end context 'when in GitLab read-only instance' do diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb index 4061a8d1bc9..48258692304 100644 --- a/spec/support/helpers/stub_feature_flags.rb +++ b/spec/support/helpers/stub_feature_flags.rb @@ -1,11 +1,30 @@ module StubFeatureFlags # Stub Feature flags with `flag_name: true/false` # - # @param [Hash] features where key is feature name and value is boolean whether enabled or not + # @param [Hash] features where key is feature name and value is boolean whether enabled or not. + # Alternatively, you can specify Hash to enable the flag on a specific thing. + # + # Examples + # - `stub_feature_flags(ci_live_trace: false)` ... Disable `ci_live_trace` + # feature flag globally. + # - `stub_feature_flags(ci_live_trace: { enabled: false, thing: project })` ... + # Disable `ci_live_trace` feature flag on the specified project. def stub_feature_flags(features) - features.each do |feature_name, enabled| - allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled } - allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled } + features.each do |feature_name, option| + if option.is_a?(Hash) + enabled, thing = option.values_at(:enabled, :thing) + else + enabled = option + thing = nil + end + + if thing + allow(Feature).to receive(:enabled?).with(feature_name, thing, any_args) { enabled } + allow(Feature).to receive(:enabled?).with(feature_name.to_s, thing, any_args) { enabled } + else + allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled } + allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled } + end end end end diff --git a/yarn.lock b/yarn.lock index 8ddcd305d48..47df016b7a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,7 +9,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.2.2": +"@babel/core@>=7.1.0", "@babel/core@^7.2.2": version "7.2.2" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.2.tgz#07adba6dde27bb5ad8d8672f15fde3e08184a687" integrity sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw== @@ -674,6 +674,19 @@ vue "^2.5.21" vue-loader "^15.4.2" +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -750,6 +763,28 @@ dependencies: source-map "^0.6.1" +"@types/unist@*", "@types/unist@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.2.tgz#5dc0a7f76809b7518c0df58689cd16a19bd751c6" + integrity sha512-iHI60IbyfQilNubmxsq4zqSjdynlmc2Q/QvH9kjzg9+CCYVVzq1O6tc7VBzSygIwnmOt07w80IG6HDQvjv3Liw== + +"@types/vfile-message@*": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-1.0.1.tgz#e1e9895cc6b36c462d4244e64e6d0b6eaf65355a" + integrity sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA== + dependencies: + "@types/node" "*" + "@types/unist" "*" + +"@types/vfile@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-3.0.2.tgz#19c18cd232df11ce6fa6ad80259bc86c366b09b9" + integrity sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw== + dependencies: + "@types/node" "*" + "@types/unist" "*" + "@types/vfile-message" "*" + "@types/webpack@^4.4.19": version "4.4.23" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.23.tgz#059d6f4598cfd65ddee0e2db38317ef989696712" @@ -1012,10 +1047,10 @@ ajv-keywords@^3.1.0: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" - integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== +ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" + integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -1064,6 +1099,11 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-regex@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" + integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -1242,6 +1282,11 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + array-find@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8" @@ -1262,7 +1307,7 @@ array-slice@^0.2.3: resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU= -array-union@^1.0.1: +array-union@^1.0.1, array-union@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= @@ -1364,6 +1409,18 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +autoprefixer@^9.0.0: + version "9.4.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.4.7.tgz#f997994f9a810eae47b38fa6d8a119772051c4ff" + integrity sha512-qS5wW6aXHkm53Y4z73tFGsUhmZu4aMPV9iHXYlF0c/wxjknXNHuj/1cIQb+6YH692DbJGGWcckAXX+VxKvahMA== + dependencies: + browserslist "^4.4.1" + caniuse-lite "^1.0.30000932" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.14" + postcss-value-parser "^3.3.1" + autosize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.0.tgz#7a0599b1ba84d73bd7589b0d9da3870152c69237" @@ -1645,6 +1702,11 @@ backo2@1.0.2: resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= +bail@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3" + integrity sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg== + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -1912,13 +1974,13 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.3.4: - version "4.3.7" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.7.tgz#f1de479a6466ea47a0a26dcc725e7504817e624a" - integrity sha512-pWQv51Ynb0MNk9JGMCZ8VkM785/4MQNXiFYtPqI7EEP0TJO+/d/NqRVn1uiAN0DNbnlUSpL2sh16Kspasv3pUQ== +browserslist@^4.3.4, browserslist@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.1.tgz#42e828954b6b29a7a53e352277be429478a69062" + integrity sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A== dependencies: - caniuse-lite "^1.0.30000925" - electron-to-chromium "^1.3.96" + caniuse-lite "^1.0.30000929" + electron-to-chromium "^1.3.103" node-releases "^1.1.3" bser@^2.0.0: @@ -1952,11 +2014,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -2026,6 +2083,18 @@ cacheable-request@^2.1.1: normalize-url "2.0.1" responselike "1.0.2" +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + caller-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" @@ -2033,6 +2102,13 @@ caller-path@^0.1.0: dependencies: callsites "^0.2.0" +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + callsite@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" @@ -2048,6 +2124,15 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= +camelcase-keys@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" + integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= + dependencies: + camelcase "^4.1.0" + map-obj "^2.0.0" + quick-lru "^1.0.0" + camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -2058,10 +2143,10 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== -caniuse-lite@^1.0.30000925: - version "1.0.30000927" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000927.tgz#114a9de4ff1e01f5790fe578ecd93421c7524665" - integrity sha512-ogq4NbUWf1uG/j66k0AmiO3GjqJAlQyF8n4w8a954cbCyFKmYGvRtgz6qkq2fWuduTXHibX7GyYL5Pg58Aks2g== +caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000932: + version "1.0.30000936" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz#5d33b118763988bf721b9b8ad436d0400e4a116b" + integrity sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw== capture-exit@^1.2.0: version "1.2.0" @@ -2087,6 +2172,11 @@ catharsis@~0.8.9: dependencies: underscore-contrib "~0.3.0" +ccount@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" + integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw== + chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -2098,15 +2188,35 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" +character-entities-html4@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.2.tgz#c44fdde3ce66b52e8d321d6c1bf46101f0150610" + integrity sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw== + +character-entities-legacy@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c" + integrity sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA== + +character-entities@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363" + integrity sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ== + +character-reference-invalid@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed" + integrity sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ== + chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" @@ -2265,6 +2375,14 @@ cliui@^4.0.0: strip-ansi "^4.0.0" wrap-ansi "^2.0.0" +clone-regexp@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-1.0.1.tgz#051805cd33173375d82118fc0918606da39fd60f" + integrity sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw== + dependencies: + is-regexp "^1.0.0" + is-supported-regexp-flag "^1.0.0" + clone-response@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" @@ -2306,6 +2424,11 @@ codesandbox-import-utils@^1.2.3: istextorbinary "^2.2.1" lz-string "^1.4.4" +collapse-white-space@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091" + integrity sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw== + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -2562,6 +2685,16 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cosmiconfig@^5.0.0: + version "5.0.7" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04" + integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^4.0.0" + create-ecdh@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" @@ -2700,6 +2833,11 @@ cssesc@^0.1.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" @@ -2712,6 +2850,13 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + dependencies: + array-find-index "^1.0.1" + custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" @@ -3029,10 +3174,10 @@ debug@^3.1.0, debug@^3.2.5: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" - integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== +debug@^4.0.0, debug@^4.0.1, debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" @@ -3043,7 +3188,15 @@ debug@~3.1.0: dependencies: ms "2.0.0" -decamelize@^1.1.1, decamelize@^1.2.0: +decamelize-keys@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -3247,6 +3400,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" + integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== + dependencies: + path-type "^3.0.0" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -3423,10 +3583,10 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== -electron-to-chromium@^1.3.96: - version "1.3.100" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13" - integrity sha512-cEUzis2g/RatrVf8x26L8lK5VEls1AGnLHk6msluBUg/NTB4wcXzExTsGscFq+Vs4WBBU2zbLLySvD4C0C3hwg== +electron-to-chromium@^1.3.103: + version "1.3.113" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9" + integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g== elliptic@^6.0.0: version "6.4.0" @@ -3441,7 +3601,7 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -emoji-regex@^7.0.3: +emoji-regex@^7.0.1, emoji-regex@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== @@ -3929,6 +4089,13 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execall@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execall/-/execall-1.0.0.tgz#73d0904e395b3cab0658b08d09ec25307f29bb73" + integrity sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M= + dependencies: + clone-regexp "^1.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4117,6 +4284,18 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-glob@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.6.tgz#a5d5b697ec8deda468d85a74035290a025a95295" + integrity sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -4180,6 +4359,13 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" +file-entry-cache@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-4.0.0.tgz#633567d15364aefe0b299e1e217735e8f3a9f6e8" + integrity sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA== + dependencies: + flat-cache "^2.0.1" + file-loader@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa" @@ -4317,6 +4503,20 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" + integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== + flush-write-stream@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" @@ -4531,7 +4731,12 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -"glob@5 - 7", glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= + +"glob@5 - 7", glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -4575,6 +4780,13 @@ global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -4586,6 +4798,15 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + globals@^11.1.0, globals@^11.7.0: version "11.7.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673" @@ -4619,6 +4840,31 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-9.0.0.tgz#3800df736dc711266df39b4ce33fe0d481f94c23" + integrity sha512-q0qiO/p1w/yJ0hk8V9x1UXlgsXUxlGd0AHUOXZVXBO6aznDtpx7M8D1kBrCAItoPm+4l8r6ATXV1JpjY2SBQOw== + dependencies: + array-union "^1.0.2" + dir-glob "^2.2.1" + fast-glob "^2.2.6" + glob "^7.1.3" + ignore "^4.0.3" + pify "^4.0.1" + slash "^2.0.0" + +globjoin@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" + integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= + +gonzales-pe@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.3.tgz#41091703625433285e0aee3aa47829fc1fbeb6f2" + integrity sha512-Kjhohco0esHQnOiqqdJeNz/5fyPkOMD/d6XVjwTAoPGUFh0mCollPUTUTa2OZy4dYNAqlPIQdTiNzJTWdd9Htw== + dependencies: + minimist "1.1.x" + good-listener@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" @@ -4922,6 +5168,11 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" integrity sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI= +html-tags@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" + integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= + htmlparser2@^3.10.0, htmlparser2@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" @@ -5032,11 +5283,16 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^4.0.6: +ignore@^4.0.3, ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.0.4: + version "5.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.5.tgz#c663c548d6ce186fb33616a8ccb5d46e56bdbbf9" + integrity sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -5047,11 +5303,24 @@ immutable-tuple@^0.4.9: resolved "https://registry.yarnpkg.com/immutable-tuple/-/immutable-tuple-0.4.9.tgz#473ebdd6c169c461913a454bf87ef8f601a20ff0" integrity sha512-LWbJPZnidF8eczu7XmcnLBsumuyRBkpwIRPCZxlojouhBo5jEBO4toj6n7hMy6IxHU/c+MqDSWkvaTpPlMQcyA== +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= +import-lazy@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-3.1.0.tgz#891279202c8a2280fdbd6674dbd8da1a1dfc67cc" + integrity sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ== + import-local@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" @@ -5081,6 +5350,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -5109,7 +5383,7 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= -ini@^1.3.4, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -5219,6 +5493,24 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-alphabetical@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41" + integrity sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg== + +is-alphanumeric@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" + integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ= + +is-alphanumerical@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40" + integrity sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5236,12 +5528,10 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= - dependencies: - builtin-modules "^1.0.0" +is-buffer@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" + integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== is-callable@^1.1.1, is-callable@^1.1.3: version "1.1.4" @@ -5274,6 +5564,11 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= +is-decimal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff" + integrity sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg== + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -5292,6 +5587,11 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -5371,6 +5671,11 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835" + integrity sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A== + is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -5444,7 +5749,7 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" -is-plain-obj@^1.0.0: +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= @@ -5503,6 +5808,11 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-supported-regexp-flag@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz#21ee16518d2c1dd3edd3e9a0d57e50207ac364ca" + integrity sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ== + is-symbol@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" @@ -5520,11 +5830,21 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= +is-whitespace-character@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed" + integrity sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ== + is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +is-word-character@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.2.tgz#46a5dac3f2a1840898b91e576cd40d493f3ae553" + integrity sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA== + is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" @@ -6147,10 +6467,10 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.7.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" - integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== +js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0: + version "3.12.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" + integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -6454,6 +6774,11 @@ kleur@^2.0.1: resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +known-css-properties@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.11.0.tgz#0da784f115ea77c76b81536d7052e90ee6c86a8a" + integrity sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w== + latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -6645,7 +6970,7 @@ lodash@4.x, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lo resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -log-symbols@^2.1.0: +log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== @@ -6668,6 +6993,11 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" integrity sha1-lbOD+Ro8J1b9SrCTZn5DCRYfK80= +longest-streak@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" + integrity sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA== + loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -6675,6 +7005,14 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + lowercase-keys@1.0.0, lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" @@ -6739,6 +7077,16 @@ map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" + integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= + map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -6746,6 +7094,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-escapes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122" + integrity sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA== + markdown-it@^8.4.2: version "8.4.2" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" @@ -6757,6 +7110,11 @@ markdown-it@^8.4.2: mdurl "^1.0.1" uc.micro "^1.0.5" +markdown-table@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.2.tgz#c78db948fa879903a41bce522e3b96f801c63786" + integrity sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw== + marked@^0.3.12, marked@~0.3.6: version "0.3.19" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" @@ -6767,6 +7125,11 @@ math-random@^1.0.1: resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= +mathml-tag-names@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c" + integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg== + md5.js@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -6775,6 +7138,13 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" +mdast-util-compact@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649" + integrity sha512-d2WS98JSDVbpSsBfVvD9TaDMlqPRz7ohM/11G0rp5jOBb5q96RJ6YLszQ/09AAixyzh23FeIpCGqfaamEADtWg== + dependencies: + unist-util-visit "^1.1.0" + mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -6814,6 +7184,21 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +meow@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" + integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig== + dependencies: + camelcase-keys "^4.0.0" + decamelize-keys "^1.0.0" + loud-rejection "^1.0.0" + minimist-options "^3.0.1" + normalize-package-data "^2.3.4" + read-pkg-up "^3.0.0" + redent "^2.0.0" + trim-newlines "^2.0.0" + yargs-parser "^10.0.0" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -6833,6 +7218,11 @@ merge-stream@^1.0.1: dependencies: readable-stream "^2.0.1" +merge2@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" + integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== + merge@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" @@ -6876,7 +7266,7 @@ micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.0.4, micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9: +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -6952,11 +7342,24 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" +minimist-options@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" + integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= +minimist@1.1.x: + version "1.1.3" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" + integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= + minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -7248,13 +7651,13 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" - integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" + resolve "^1.10.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" @@ -7270,6 +7673,16 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-selector@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" + integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM= + normalize-url@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" @@ -7314,6 +7727,11 @@ null-check@^1.0.0: resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -7651,6 +8069,18 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" +parse-entities@^1.0.2, parse-entities@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4" + integrity sha512-XXtDdOPLSB0sHecbEapQi6/58U/ODj/KWfIXmmMCJF/eRn8laX6LZbOyioMoETOOJoWRW8/qTSl5VQkUIfKM5g== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -7752,7 +8182,7 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-parse@^1.0.5: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== @@ -7811,6 +8241,11 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pify@^4.0.0, pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + pikaday@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.6.1.tgz#b91bcb9b8539cedd8d6d08e4e7465e12095671b0" @@ -7897,6 +8332,40 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +postcss-html@^0.36.0: + version "0.36.0" + resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204" + integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw== + dependencies: + htmlparser2 "^3.10.0" + +postcss-jsx@^0.36.0: + version "0.36.0" + resolved "https://registry.yarnpkg.com/postcss-jsx/-/postcss-jsx-0.36.0.tgz#b7685ed3d070a175ef0aa48f83d9015bd772c82d" + integrity sha512-/lWOSXSX5jlITCKFkuYU2WLFdrncZmjSVyNpHAunEgirZXLwI8RjU556e3Uz4mv0WVHnJA9d3JWb36lK9Yx99g== + dependencies: + "@babel/core" ">=7.1.0" + +postcss-less@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.2.tgz#fb67e7ba351dbdf69de3c52eebd1184c52bfaea6" + integrity sha512-66ZBVo1JGkQ7r13M97xcHcyarWpgg21RaqIZWZXHE3XOtb5+ywK1uZWeY1DYkYRkIX/l8Hvxnx9iSKB68nFr+w== + dependencies: + postcss "^7.0.14" + +postcss-markdown@^0.36.0: + version "0.36.0" + resolved "https://registry.yarnpkg.com/postcss-markdown/-/postcss-markdown-0.36.0.tgz#7f22849ae0e3db18820b7b0d5e7833f13a447560" + integrity sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ== + dependencies: + remark "^10.0.1" + unist-util-find-all-after "^1.0.2" + +postcss-media-query-parser@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" + integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= + postcss-modules-extract-imports@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a" @@ -7928,7 +8397,44 @@ postcss-modules-values@^1.3.0: icss-replace-symbols "^1.1.0" postcss "^6.0.1" -postcss-selector-parser@^3.1.1: +postcss-reporter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-6.0.1.tgz#7c055120060a97c8837b4e48215661aafb74245f" + integrity sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw== + dependencies: + chalk "^2.4.1" + lodash "^4.17.11" + log-symbols "^2.2.0" + postcss "^7.0.7" + +postcss-resolve-nested-selector@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" + integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4= + +postcss-safe-parser@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz#8756d9e4c36fdce2c72b091bbc8ca176ab1fcdea" + integrity sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ== + dependencies: + postcss "^7.0.0" + +postcss-sass@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.3.5.tgz#6d3e39f101a53d2efa091f953493116d32beb68c" + integrity sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A== + dependencies: + gonzales-pe "^4.2.3" + postcss "^7.0.1" + +postcss-scss@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.0.0.tgz#248b0a28af77ea7b32b1011aba0f738bda27dea1" + integrity sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug== + dependencies: + postcss "^7.0.0" + +postcss-selector-parser@^3.1.0, postcss-selector-parser@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" integrity sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU= @@ -7937,10 +8443,24 @@ postcss-selector-parser@^3.1.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-value-parser@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" - integrity sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU= +postcss-selector-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-syntax@^0.36.2: + version "0.36.2" + resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" + integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== + +postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20, postcss@^6.0.23: version "6.0.23" @@ -7951,6 +8471,15 @@ postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20, postcss@^6.0.23: source-map "^0.6.1" supports-color "^5.4.0" +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.7: + version "7.0.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" + integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -8273,6 +8802,11 @@ querystringify@^2.0.0: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg== +quick-lru@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" + integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= + randomatic@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" @@ -8358,6 +8892,14 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + read-pkg-up@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" @@ -8444,6 +8986,14 @@ realpath-native@^1.0.0: dependencies: util.promisify "^1.0.0" +redent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" + integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= + dependencies: + indent-string "^3.0.0" + strip-indent "^2.0.0" + regenerate-unicode-properties@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" @@ -8562,6 +9112,56 @@ regjsparser@^0.3.0: dependencies: jsesc "~0.5.0" +remark-parse@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a" + integrity sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg== + dependencies: + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^1.1.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^1.0.0" + vfile-location "^2.0.0" + xtend "^4.0.1" + +remark-stringify@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088" + integrity sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg== + dependencies: + ccount "^1.0.0" + is-alphanumeric "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + longest-streak "^2.0.1" + markdown-escapes "^1.0.0" + markdown-table "^1.1.0" + mdast-util-compact "^1.0.0" + parse-entities "^1.0.2" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + stringify-entities "^1.0.1" + unherit "^1.0.4" + xtend "^4.0.1" + +remark@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df" + integrity sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ== + dependencies: + remark-parse "^6.0.0" + remark-stringify "^6.0.0" + unified "^7.0.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -8577,7 +9177,7 @@ repeat-string@^0.2.2: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae" integrity sha1-x6jTI2BoNiBZp+RlH8aITosftK4= -repeat-string@^1.5.2, repeat-string@^1.6.1: +repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -8589,6 +9189,11 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +replace-ext@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= + request-promise-core@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" @@ -8686,6 +9291,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8696,12 +9306,12 @@ resolve@1.1.7, resolve@1.1.x: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== +resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== dependencies: - path-parse "^1.0.5" + path-parse "^1.0.6" responselike@1.0.2: version "1.0.2" @@ -8728,12 +9338,12 @@ rfdc@^1.1.2: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349" integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA== -rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== +rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: - glob "^7.0.5" + glob "^7.1.3" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.1" @@ -9052,10 +9662,15 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= -slice-ansi@2.0.0: +slash@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7" - integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ== + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== dependencies: ansi-styles "^3.2.0" astral-regex "^1.0.0" @@ -9281,6 +9896,11 @@ spdy@^4.0.0: select-hose "^2.0.0" spdy-transport "^3.0.0" +specificity@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" + integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -9333,6 +9953,11 @@ stack-utils@^1.0.1: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +state-toggle@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a" + integrity sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og== + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -9433,6 +10058,15 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.0.0.tgz#5a1690a57cc78211fffd9bf24bbe24d090604eb1" + integrity sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.0.0" + string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -9445,6 +10079,16 @@ string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= +stringify-entities@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7" + integrity sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + is-alphanumerical "^1.0.0" + is-hexadecimal "^1.0.0" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -9459,6 +10103,13 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f" + integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow== + dependencies: + ansi-regex "^4.0.0" + strip-bom@3.0.0, strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -9483,6 +10134,11 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= + strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -9496,6 +10152,87 @@ style-loader@^0.23.1: loader-utils "^1.1.0" schema-utils "^1.0.0" +style-search@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" + integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= + +stylelint-config-recommended@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz#f526d5c771c6811186d9eaedbed02195fee30858" + integrity sha512-ajMbivOD7JxdsnlS5945KYhvt7L/HwN6YeYF2BH6kE4UCLJR0YvXMf+2j7nQpJyYLZx9uZzU5G1ZOSBiWAc6yA== + +stylelint-scss@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.3.tgz#e158b3061eeec26d7f6088f346998a797432f3c8" + integrity sha512-QESQUOY1ldU5tlJTTM3Megz/QtJ39S58ByjZ7dZobGDq9qMjy5jbC7PDUasrv/T7pB1UbpPojpxX9K1OR7IPEg== + dependencies: + lodash "^4.17.11" + postcss-media-query-parser "^0.2.3" + postcss-resolve-nested-selector "^0.1.1" + postcss-selector-parser "^5.0.0" + postcss-value-parser "^3.3.1" + +stylelint@^9.10.1: + version "9.10.1" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-9.10.1.tgz#5f0ee3701461dff1d68284e1386efe8f0677a75d" + integrity sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ== + dependencies: + autoprefixer "^9.0.0" + balanced-match "^1.0.0" + chalk "^2.4.1" + cosmiconfig "^5.0.0" + debug "^4.0.0" + execall "^1.0.0" + file-entry-cache "^4.0.0" + get-stdin "^6.0.0" + global-modules "^2.0.0" + globby "^9.0.0" + globjoin "^0.1.4" + html-tags "^2.0.0" + ignore "^5.0.4" + import-lazy "^3.1.0" + imurmurhash "^0.1.4" + known-css-properties "^0.11.0" + leven "^2.1.0" + lodash "^4.17.4" + log-symbols "^2.0.0" + mathml-tag-names "^2.0.1" + meow "^5.0.0" + micromatch "^3.1.10" + normalize-selector "^0.2.0" + pify "^4.0.0" + postcss "^7.0.13" + postcss-html "^0.36.0" + postcss-jsx "^0.36.0" + postcss-less "^3.1.0" + postcss-markdown "^0.36.0" + postcss-media-query-parser "^0.2.3" + postcss-reporter "^6.0.0" + postcss-resolve-nested-selector "^0.1.1" + postcss-safe-parser "^4.0.0" + postcss-sass "^0.3.5" + postcss-scss "^2.0.0" + postcss-selector-parser "^3.1.0" + postcss-syntax "^0.36.2" + postcss-value-parser "^3.3.0" + resolve-from "^4.0.0" + signal-exit "^3.0.2" + slash "^2.0.0" + specificity "^0.4.1" + string-width "^3.0.0" + style-search "^0.1.0" + sugarss "^2.0.0" + svg-tags "^1.0.0" + table "^5.0.0" + +sugarss@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" + integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ== + dependencies: + postcss "^7.0.2" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -9515,6 +10252,18 @@ supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-co dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= + svg4everybody@2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d" @@ -9530,15 +10279,15 @@ symbol-tree@^3.2.2: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= -table@^5.0.2: - version "5.1.1" - resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837" - integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw== +table@^5.0.0, table@^5.0.2: + version "5.2.3" + resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" + integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== dependencies: - ajv "^6.6.1" + ajv "^6.9.1" lodash "^4.17.11" - slice-ansi "2.0.0" - string-width "^2.1.1" + slice-ansi "^2.1.0" + string-width "^3.0.0" taffydb@2.6.2: version "2.6.2" @@ -9831,11 +10580,31 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +trim-newlines@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" + integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +trim-trailing-lines@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz#e0ec0810fd3c3f1730516b45f49083caaf2774d9" + integrity sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg== + +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= + +trough@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24" + integrity sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw== + tryer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7" @@ -9945,6 +10714,14 @@ underscore@~1.8.3: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= +unherit@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c" + integrity sha512-+XZuV691Cn4zHsK0vkKYwBEwB74T3IZIcxrgn2E4rKwTfFyI1zCh7X7grwh9Re08fdPlarIdyWgI8aVB3F5A5g== + dependencies: + inherits "^2.0.1" + xtend "^4.0.1" + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -9968,6 +10745,20 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg== +unified@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-7.1.0.tgz#5032f1c1ee3364bd09da12e27fdd4a7553c7be13" + integrity sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw== + dependencies: + "@types/unist" "^2.0.0" + "@types/vfile" "^3.0.0" + bail "^1.0.0" + extend "^3.0.0" + is-plain-obj "^1.1.0" + trough "^1.0.0" + vfile "^3.0.0" + x-is-string "^0.1.0" + union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" @@ -10004,6 +10795,44 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +unist-util-find-all-after@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-1.0.2.tgz#9be49cfbae5ca1566b27536670a92836bf2f8d6d" + integrity sha512-nDl79mKpffXojLpCimVXnxhlH/jjaTnDuScznU9J4jjsaUtBdDbxmlc109XtcqxY4SDO0SwzngsxxW8DIISt1w== + dependencies: + unist-util-is "^2.0.0" + +unist-util-is@^2.0.0, unist-util-is@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db" + integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw== + +unist-util-remove-position@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb" + integrity sha512-XxoNOBvq1WXRKXxgnSYbtCF76TJrRoe5++pD4cCBsssSiWSnPEktyFrFLE8LTk3JW5mt9hB0Sk5zn4x/JeWY7Q== + dependencies: + unist-util-visit "^1.1.0" + +unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" + integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ== + +unist-util-visit-parents@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217" + integrity sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA== + dependencies: + unist-util-is "^2.1.2" + +unist-util-visit@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1" + integrity sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw== + dependencies: + unist-util-visit-parents "^2.0.0" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -10178,6 +11007,28 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vfile-location@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.4.tgz#2a5e7297dd0d9e2da4381464d04acc6b834d3e55" + integrity sha512-KRL5uXQPoUKu+NGvQVL4XLORw45W62v4U4gxJ3vRlDfI9QsT4ZN1PNXn/zQpKUulqGDpYuT0XDfp5q9O87/y/w== + +vfile-message@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1" + integrity sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA== + dependencies: + unist-util-stringify-position "^1.1.1" + +vfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803" + integrity sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ== + dependencies: + is-buffer "^2.0.0" + replace-ext "1.0.0" + unist-util-stringify-position "^1.0.0" + vfile-message "^1.0.0" + visibilityjs@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/visibilityjs/-/visibilityjs-1.2.4.tgz#bff8663da62c8c10ad4ee5ae6a1ae6fac4259d63" @@ -10529,7 +11380,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0: +which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -10598,6 +11449,13 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" @@ -10628,6 +11486,11 @@ ws@~3.3.1: safe-buffer "~5.1.0" ultron "~1.1.0" +x-is-string@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" + integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= + xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" @@ -10698,7 +11561,7 @@ yallist@^3.0.0, yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k= -yargs-parser@^10.1.0: +yargs-parser@^10.0.0, yargs-parser@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== |