diff options
29 files changed, 563 insertions, 240 deletions
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index d9a01d25342..04f6473099c 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -13.14.0 +13.15.0 diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue index c0719e2a7d9..9a43a7405c6 100644 --- a/app/assets/javascripts/diffs/components/diff_row.vue +++ b/app/assets/javascripts/diffs/components/diff_row.vue @@ -1,7 +1,15 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; -import { CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE } from '../constants'; +import { + CONTEXT_LINE_CLASS_NAME, + PARALLEL_DIFF_VIEW_TYPE, + CONFLICT_MARKER_OUR, + CONFLICT_MARKER_THEIR, + CONFLICT_OUR, + CONFLICT_THEIR, + CONFLICT_MARKER, +} from '../constants'; import DiffGutterAvatars from './diff_gutter_avatars.vue'; import * as utils from './diff_row_utils'; @@ -71,6 +79,12 @@ export default { addCommentTooltipRight() { return utils.addCommentTooltip(this.line.right); }, + emptyCellRightClassMap() { + return { conflict_their: this.line.left?.type === CONFLICT_OUR }; + }, + emptyCellLeftClassMap() { + return { conflict_our: this.line.right?.type === CONFLICT_THEIR }; + }, shouldRenderCommentButton() { return ( this.isLoggedIn && @@ -80,6 +94,9 @@ export default { !this.line.hasDiscussionsRight ); }, + isLeftConflictMarker() { + return [CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(this.line.left?.type); + }, }, mounted() { this.scrollToLineIfNeededParallel(this.line); @@ -107,37 +124,50 @@ export default { handleCommentButton(line) { this.showCommentForm({ lineCode: line.line_code, fileHash: this.fileHash }); }, + conflictText(line) { + return line.type === CONFLICT_MARKER_THEIR + ? this.$options.THEIR_CHANGES + : this.$options.OUR_CHANGES; + }, }, + OUR_CHANGES: 'HEAD//our changes', + THEIR_CHANGES: 'origin//their changes', + CONFLICT_MARKER, + CONFLICT_MARKER_THEIR, + CONFLICT_OUR, + CONFLICT_THEIR, }; </script> <template> <div :class="classNameMap" class="diff-grid-row diff-tr line_holder"> <div class="diff-grid-left left-side"> - <template v-if="line.left"> + <template v-if="line.left && line.left.type !== $options.CONFLICT_MARKER"> <div :class="classNameMapCellLeft" data-testid="leftLineNumber" class="diff-td diff-line-num old_line" > - <span - v-if="shouldRenderCommentButton" - v-gl-tooltip - data-testid="leftCommentButton" - class="add-diff-note tooltip-wrapper" - :title="addCommentTooltipLeft" - > - <button - type="button" - class="add-diff-note note-button js-add-diff-note-button qa-diff-comment" - :disabled="line.left.commentsDisabled" - @click="handleCommentButton(line.left)" + <template v-if="!isLeftConflictMarker"> + <span + v-if="shouldRenderCommentButton" + v-gl-tooltip + data-testid="leftCommentButton" + class="add-diff-note tooltip-wrapper" + :title="addCommentTooltipLeft" > - <gl-icon :size="12" name="comment" /> - </button> - </span> + <button + type="button" + class="add-diff-note note-button js-add-diff-note-button qa-diff-comment" + :disabled="line.left.commentsDisabled" + @click="handleCommentButton(line.left)" + > + <gl-icon :size="12" name="comment" /> + </button> + </span> + </template> <a - v-if="line.left.old_line" + v-if="line.left.old_line && line.left.type !== $options.CONFLICT_THEIR" :data-linenumber="line.left.old_line" :href="line.lineHrefOld" @click="setHighlightedRow(line.lineCode)" @@ -159,7 +189,7 @@ export default { </div> <div v-if="inline" :class="classNameMapCellLeft" class="diff-td diff-line-num old_line"> <a - v-if="line.left.new_line" + v-if="line.left.new_line && line.left.type !== $options.CONFLICT_OUR" :data-linenumber="line.left.new_line" :href="line.lineHrefOld" @click="setHighlightedRow(line.lineCode)" @@ -170,39 +200,59 @@ export default { <div :id="line.left.line_code" :key="line.left.line_code" - v-safe-html="line.left.rich_text" :class="parallelViewLeftLineType" class="diff-td line_content with-coverage parallel left-side" data-testid="leftContent" @mousedown="handleParallelLineMouseDown" - ></div> + > + <strong v-if="isLeftConflictMarker">{{ conflictText(line.left) }}</strong> + <span v-else v-safe-html="line.left.rich_text"></span> + </div> </template> - <template v-else> - <div data-testid="leftEmptyCell" class="diff-td diff-line-num old_line empty-cell"></div> - <div v-if="inline" class="diff-td diff-line-num old_line empty-cell"></div> - <div class="diff-td line-coverage left-side empty-cell"></div> - <div class="diff-td line_content with-coverage parallel left-side empty-cell"></div> + <template v-else-if="!inline || (line.left && line.left.type === $options.CONFLICT_MARKER)"> + <div + data-testid="leftEmptyCell" + class="diff-td diff-line-num old_line empty-cell" + :class="emptyCellLeftClassMap" + > + + </div> + <div + v-if="inline" + class="diff-td diff-line-num old_line empty-cell" + :class="emptyCellLeftClassMap" + ></div> + <div + class="diff-td line-coverage left-side empty-cell" + :class="emptyCellLeftClassMap" + ></div> + <div + class="diff-td line_content with-coverage parallel left-side empty-cell" + :class="emptyCellLeftClassMap" + ></div> </template> </div> <div v-if="!inline" class="diff-grid-right right-side"> <template v-if="line.right"> <div :class="classNameMapCellRight" class="diff-td diff-line-num new_line"> - <span - v-if="shouldRenderCommentButton" - v-gl-tooltip - data-testid="rightCommentButton" - class="add-diff-note tooltip-wrapper" - :title="addCommentTooltipRight" - > - <button - type="button" - class="add-diff-note note-button js-add-diff-note-button qa-diff-comment" - :disabled="line.right.commentsDisabled" - @click="handleCommentButton(line.right)" + <template v-if="line.right.type !== $options.CONFLICT_MARKER_THEIR"> + <span + v-if="shouldRenderCommentButton" + v-gl-tooltip + data-testid="rightCommentButton" + class="add-diff-note tooltip-wrapper" + :title="addCommentTooltipRight" > - <gl-icon :size="12" name="comment" /> - </button> - </span> + <button + type="button" + class="add-diff-note note-button js-add-diff-note-button qa-diff-comment" + :disabled="line.right.commentsDisabled" + @click="handleCommentButton(line.right)" + > + <gl-icon :size="12" name="comment" /> + </button> + </span> + </template> <a v-if="line.right.new_line" :data-linenumber="line.right.new_line" @@ -233,22 +283,35 @@ export default { <div :id="line.right.line_code" :key="line.right.rich_text" - v-safe-html="line.right.rich_text" - :class="[ - line.right.type, - { - hll: isHighlighted, - }, - ]" + :class="[line.right.type, { hll: isHighlighted }]" class="diff-td line_content with-coverage parallel right-side" @mousedown="handleParallelLineMouseDown" - ></div> + > + <strong v-if="line.right.type === $options.CONFLICT_MARKER_THEIR">{{ + conflictText(line.right) + }}</strong> + <span v-else v-safe-html="line.right.rich_text"></span> + </div> </template> <template v-else> - <div data-testid="rightEmptyCell" class="diff-td diff-line-num old_line empty-cell"></div> - <div class="diff-td diff-line-num old_line empty-cell"></div> - <div class="diff-td line-coverage right-side empty-cell"></div> - <div class="diff-td line_content with-coverage parallel right-side empty-cell"></div> + <div + data-testid="rightEmptyCell" + class="diff-td diff-line-num old_line empty-cell" + :class="emptyCellRightClassMap" + ></div> + <div + v-if="inline" + class="diff-td diff-line-num old_line empty-cell" + :class="emptyCellRightClassMap" + ></div> + <div + class="diff-td line-coverage right-side empty-cell" + :class="emptyCellRightClassMap" + ></div> + <div + class="diff-td line_content with-coverage parallel right-side empty-cell" + :class="emptyCellRightClassMap" + ></div> </template> </div> </div> diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js index 07e27bd8e47..7080348ee7d 100644 --- a/app/assets/javascripts/diffs/constants.js +++ b/app/assets/javascripts/diffs/constants.js @@ -109,3 +109,9 @@ export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd'; export const EVT_PERF_MARK_DIFF_FILES_START = 'mr:diffs:perf:filesStart'; export const EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN = 'mr:diffs:perf:firstFileShown'; export const EVT_PERF_MARK_DIFF_FILES_END = 'mr:diffs:perf:filesEnd'; + +export const CONFLICT_OUR = 'conflict_our'; +export const CONFLICT_THEIR = 'conflict_their'; +export const CONFLICT_MARKER = 'conflict_marker'; +export const CONFLICT_MARKER_OUR = 'conflict_marker_our'; +export const CONFLICT_MARKER_THEIR = 'conflict_marker_their'; diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 1839df12c96..27702eaf91e 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -15,6 +15,11 @@ import { INLINE_DIFF_LINES_KEY, SHOW_WHITESPACE, NO_SHOW_WHITESPACE, + CONFLICT_OUR, + CONFLICT_THEIR, + CONFLICT_MARKER, + CONFLICT_MARKER_OUR, + CONFLICT_MARKER_THEIR, } from '../constants'; import { prepareRawDiffFile } from '../utils/diff_file'; @@ -22,6 +27,11 @@ export const isAdded = line => ['new', 'new-nonewline'].includes(line.type); export const isRemoved = line => ['old', 'old-nonewline'].includes(line.type); export const isUnchanged = line => !line.type; export const isMeta = line => ['match', 'new-nonewline', 'old-nonewline'].includes(line.type); +export const isConflictMarker = line => + [CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(line.type); +export const isConflictSeperator = line => line.type === CONFLICT_MARKER; +export const isConflictOur = line => line.type === CONFLICT_OUR; +export const isConflictTheir = line => line.type === CONFLICT_THEIR; /** * Pass in the inline diff lines array which gets converted @@ -42,12 +52,13 @@ export const isMeta = line => ['match', 'new-nonewline', 'old-nonewline'].includ export const parallelizeDiffLines = (diffLines, inline) => { let freeRightIndex = null; + let conflictStartIndex = -1; const lines = []; for (let i = 0, diffLinesLength = diffLines.length, index = 0; i < diffLinesLength; i += 1) { const line = diffLines[i]; - if (isRemoved(line) || inline) { + if (isRemoved(line) || isConflictOur(line) || inline) { lines.push({ [LINE_POSITION_LEFT]: line, [LINE_POSITION_RIGHT]: null, @@ -58,7 +69,7 @@ export const parallelizeDiffLines = (diffLines, inline) => { freeRightIndex = index; } index += 1; - } else if (isAdded(line)) { + } else if (isAdded(line) || isConflictTheir(line)) { if (freeRightIndex !== null) { // If an old line came before this without a line on the right, this // line can be put to the right of it. @@ -77,15 +88,28 @@ export const parallelizeDiffLines = (diffLines, inline) => { freeRightIndex = null; index += 1; } - } else if (isMeta(line) || isUnchanged(line)) { - // line in the right panel is the same as in the left one - lines.push({ - [LINE_POSITION_LEFT]: line, - [LINE_POSITION_RIGHT]: line, - }); + } else if ( + isMeta(line) || + isUnchanged(line) || + isConflictMarker(line) || + (isConflictSeperator(line) && inline) + ) { + if (conflictStartIndex <= 0) { + // line in the right panel is the same as in the left one + lines.push({ + [LINE_POSITION_LEFT]: line, + [LINE_POSITION_RIGHT]: !inline && line, + }); - freeRightIndex = null; - index += 1; + if (!inline && isConflictMarker(line)) { + conflictStartIndex = index; + } + freeRightIndex = null; + index += 1; + } else { + lines[conflictStartIndex][LINE_POSITION_RIGHT] = line; + conflictStartIndex = -1; + } } } diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss index d9b9f3694c1..5c6668814cb 100644 --- a/app/assets/stylesheets/highlight/common.scss +++ b/app/assets/stylesheets/highlight/common.scss @@ -1,4 +1,5 @@ @import '../framework/variables'; +@import './conflict_colors'; @mixin diff-background($background, $idiff, $border) { background: $background; @@ -51,3 +52,44 @@ color: darken($color, 15%); } } + +@mixin conflict-colors($theme) { + .diff-line-num { + &.conflict_marker_our, + &.conflict_our { + background-color: map-get($conflict-colors, #{$theme}-header-head-neutral); + border-color: map-get($conflict-colors, #{$theme}-header-head-neutral); + } + + &.conflict_marker_their, + &.conflict_their { + background-color: map-get($conflict-colors, #{$theme}-header-origin-neutral); + border-color: map-get($conflict-colors, #{$theme}-header-origin-neutral); + } + } + + .line_holder { + .line_content, + .line-coverage { + &.conflict_marker_our { + background-color: map-get($conflict-colors, #{$theme}-header-head-neutral); + border-color: map-get($conflict-colors, #{$theme}-header-head-neutral); + } + + &.conflict_marker_their { + background-color: map-get($conflict-colors, #{$theme}-header-origin-neutral); + border-color: map-get($conflict-colors, #{$theme}-header-origin-neutral); + } + + &.conflict_our { + background-color: map-get($conflict-colors, #{$theme}-line-head-neutral); + border-color: map-get($conflict-colors, #{$theme}-line-head-neutral); + } + + &.conflict_their { + background-color: map-get($conflict-colors, #{$theme}-line-origin-neutral); + border-color: map-get($conflict-colors, #{$theme}-line-origin-neutral); + } + } + } +} diff --git a/app/assets/stylesheets/highlight/conflict_colors.scss b/app/assets/stylesheets/highlight/conflict_colors.scss new file mode 100644 index 00000000000..98ca3775b72 --- /dev/null +++ b/app/assets/stylesheets/highlight/conflict_colors.scss @@ -0,0 +1,119 @@ +// Disabled to use the color map for creating color schemes +// scss-lint:disable ColorVariable +$conflict-colors: ( + white-header-head-neutral : #e1fad7, + white-line-head-neutral : #effdec, + white-button-head-neutral : #9adb84, + + white-header-head-chosen : #baf0a8, + white-line-head-chosen : #e1fad7, + white-button-head-chosen : #52c22d, + + white-header-origin-neutral : #e0f0ff, + white-line-origin-neutral : #f2f9ff, + white-button-origin-neutral : #87c2fa, + + white-header-origin-chosen : #add8ff, + white-line-origin-chosen : #e0f0ff, + white-button-origin-chosen : #268ced, + + white-header-not-chosen : #f0f0f0, + white-line-not-chosen : $gray-light, + + dark-header-head-neutral : rgba(#3f3, 0.2), + dark-line-head-neutral : rgba(#3f3, 0.1), + dark-button-head-neutral : #40874f, + + dark-header-head-chosen : rgba(#3f3, 0.33), + dark-line-head-chosen : rgba(#3f3, 0.2), + dark-button-head-chosen : #258537, + + dark-header-origin-neutral : rgba(#2878c9, 0.4), + dark-line-origin-neutral : rgba(#2878c9, 0.3), + dark-button-origin-neutral : #2a5c8c, + + dark-header-origin-chosen : rgba(#2878c9, 0.6), + dark-line-origin-chosen : rgba(#2878c9, 0.4), + dark-button-origin-chosen : #1d6cbf, + + dark-header-not-chosen : rgba(#fff, 0.25), + dark-line-not-chosen : rgba(#fff, 0.1), + + monokai-header-head-neutral : rgba(#a6e22e, 0.25), + monokai-line-head-neutral : rgba(#a6e22e, 0.1), + monokai-button-head-neutral : #376b20, + + monokai-header-head-chosen : rgba(#a6e22e, 0.4), + monokai-line-head-chosen : rgba(#a6e22e, 0.25), + monokai-button-head-chosen : #39800d, + + monokai-header-origin-neutral : rgba(#60d9f1, 0.35), + monokai-line-origin-neutral : rgba(#60d9f1, 0.15), + monokai-button-origin-neutral : #38848c, + + monokai-header-origin-chosen : rgba(#60d9f1, 0.5), + monokai-line-origin-chosen : rgba(#60d9f1, 0.35), + monokai-button-origin-chosen : #3ea4b2, + + monokai-header-not-chosen : rgba(#76715d, 0.24), + monokai-line-not-chosen : rgba(#76715d, 0.1), + + solarized-light-header-head-neutral : rgba(#859900, 0.37), + solarized-light-line-head-neutral : rgba(#859900, 0.2), + solarized-light-button-head-neutral : #afb262, + + solarized-light-header-head-chosen : rgba(#859900, 0.5), + solarized-light-line-head-chosen : rgba(#859900, 0.37), + solarized-light-button-head-chosen : #94993d, + + solarized-light-header-origin-neutral : rgba(#2878c9, 0.37), + solarized-light-line-origin-neutral : rgba(#2878c9, 0.15), + solarized-light-button-origin-neutral : #60a1bf, + + solarized-light-header-origin-chosen : rgba(#2878c9, 0.6), + solarized-light-line-origin-chosen : rgba(#2878c9, 0.37), + solarized-light-button-origin-chosen : #2482b2, + + solarized-light-header-not-chosen : rgba(#839496, 0.37), + solarized-light-line-not-chosen : rgba(#839496, 0.2), + + solarized-dark-header-head-neutral : rgba(#859900, 0.35), + solarized-dark-line-head-neutral : rgba(#859900, 0.15), + solarized-dark-button-head-neutral : #376b20, + + solarized-dark-header-head-chosen : rgba(#859900, 0.5), + solarized-dark-line-head-chosen : rgba(#859900, 0.35), + solarized-dark-button-head-chosen : #39800d, + + solarized-dark-header-origin-neutral : rgba(#2878c9, 0.35), + solarized-dark-line-origin-neutral : rgba(#2878c9, 0.15), + solarized-dark-button-origin-neutral : #086799, + + solarized-dark-header-origin-chosen : rgba(#2878c9, 0.6), + solarized-dark-line-origin-chosen : rgba(#2878c9, 0.35), + solarized-dark-button-origin-chosen : #0082cc, + + solarized_dark_header_not_chosen : rgba(#839496, 0.25), + solarized_dark_line_not_chosen : rgba(#839496, 0.15), + + none_header_head_neutral : $gray-normal, + none_line_head_neutral : $gray-normal, + none_button_head_neutral : $gray-normal, + + none_header_head_chosen : $gray-darker, + none_line_head_chosen : $gray-darker, + none_button_head_chosen : $gray-darker, + + none_header_origin_neutral : $gray-normal, + none_line_origin_neutral : $gray-normal, + none_button_origin_neutral : $gray-normal, + + none_header_origin_chosen : $gray-darker, + none_line_origin_chosen : $gray-darker, + none_button_origin_chosen : $gray-darker, + + none_header_not_chosen : $gray-light, + none_line_not_chosen : $gray-light + +); +// scss-lint:enable ColorVariable diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss index d51d5b7137d..ecfff50726a 100644 --- a/app/assets/stylesheets/highlight/themes/dark.scss +++ b/app/assets/stylesheets/highlight/themes/dark.scss @@ -198,6 +198,8 @@ $dark-il: #de935f; } } + @include conflict-colors('dark'); + // highlight line via anchor pre .hll { background-color: $dark-pre-hll-bg !important; diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss index e690f9c7c74..119908ffba8 100644 --- a/app/assets/stylesheets/highlight/themes/monokai.scss +++ b/app/assets/stylesheets/highlight/themes/monokai.scss @@ -198,6 +198,8 @@ $monokai-gi: #a6e22e; } } + @include conflict-colors('monokai'); + // highlight line via anchor pre .hll { background-color: $monokai-hll !important; diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss index 8c532f53182..f95f5393323 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss @@ -202,6 +202,8 @@ $solarized-dark-il: #2aa198; } } + @include conflict-colors('solarized-dark'); + // highlight line via anchor pre .hll { background-color: $solarized-dark-hll-bg !important; diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss index 1f9042a9534..dc4bc2f32c2 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-light.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss @@ -210,6 +210,8 @@ $solarized-light-il: #2aa198; } } + @include conflict-colors('solarized-light'); + // highlight line via anchor pre .hll { background-color: $solarized-light-hll-bg !important; diff --git a/app/assets/stylesheets/highlight/themes/white.scss b/app/assets/stylesheets/highlight/themes/white.scss index 6362dd734f6..ed1d9c924c0 100644 --- a/app/assets/stylesheets/highlight/themes/white.scss +++ b/app/assets/stylesheets/highlight/themes/white.scss @@ -1,3 +1,5 @@ .code.white { @import '../white_base'; + + @include conflict-colors('white'); } diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss index bb5ca94af33..e54dc685515 100644 --- a/app/assets/stylesheets/highlight/white_base.scss +++ b/app/assets/stylesheets/highlight/white_base.scss @@ -81,6 +81,17 @@ $white-gc-bg: #eaf2f5; .line-numbers, .diff-line-num { background-color: $gray-light; + + &.conflict_marker, + &.conflict_our { + background-color: map-get($conflict-colors, 'white-header-head-neutral'); + border-color: map-get($conflict-colors, 'white-header-head-neutral'); + } + + &.conflict_their { + background-color: map-get($conflict-colors, 'white-header-origin-neutral'); + border-color: map-get($conflict-colors, 'white-header-origin-neutral'); + } } .diff-line-num, @@ -115,7 +126,7 @@ pre.code, .diff-grid-left:hover, .diff-grid-right:hover { - .diff-line-num:not(.empty-cell) { + .diff-line-num:not(.empty-cell):not(.conflict_marker_their):not(.conflict_marker_our) { @include line-number-hover($white-over-bg); } } diff --git a/app/assets/stylesheets/page_bundles/merge_conflicts.scss b/app/assets/stylesheets/page_bundles/merge_conflicts.scss index a26affb10a9..d7473d2c942 100644 --- a/app/assets/stylesheets/page_bundles/merge_conflicts.scss +++ b/app/assets/stylesheets/page_bundles/merge_conflicts.scss @@ -1,183 +1,65 @@ @import 'mixins_and_variables_and_functions'; -// Disabled to use the color map for creating color schemes -// scss-lint:disable ColorVariable -$colors: ( - white-header-head-neutral : #e1fad7, - white-line-head-neutral : #effdec, - white-button-head-neutral : #9adb84, - - white-header-head-chosen : #baf0a8, - white-line-head-chosen : #e1fad7, - white-button-head-chosen : #52c22d, - - white-header-origin-neutral : #e0f0ff, - white-line-origin-neutral : #f2f9ff, - white-button-origin-neutral : #87c2fa, - - white-header-origin-chosen : #add8ff, - white-line-origin-chosen : #e0f0ff, - white-button-origin-chosen : #268ced, - - white-header-not-chosen : #f0f0f0, - white-line-not-chosen : $gray-light, - - dark-header-head-neutral : rgba(#3f3, 0.2), - dark-line-head-neutral : rgba(#3f3, 0.1), - dark-button-head-neutral : #40874f, - - dark-header-head-chosen : rgba(#3f3, 0.33), - dark-line-head-chosen : rgba(#3f3, 0.2), - dark-button-head-chosen : #258537, - - dark-header-origin-neutral : rgba(#2878c9, 0.4), - dark-line-origin-neutral : rgba(#2878c9, 0.3), - dark-button-origin-neutral : #2a5c8c, - - dark-header-origin-chosen : rgba(#2878c9, 0.6), - dark-line-origin-chosen : rgba(#2878c9, 0.4), - dark-button-origin-chosen : #1d6cbf, - - dark-header-not-chosen : rgba(#fff, 0.25), - dark-line-not-chosen : rgba(#fff, 0.1), - - monokai-header-head-neutral : rgba(#a6e22e, 0.25), - monokai-line-head-neutral : rgba(#a6e22e, 0.1), - monokai-button-head-neutral : #376b20, - - monokai-header-head-chosen : rgba(#a6e22e, 0.4), - monokai-line-head-chosen : rgba(#a6e22e, 0.25), - monokai-button-head-chosen : #39800d, - - monokai-header-origin-neutral : rgba(#60d9f1, 0.35), - monokai-line-origin-neutral : rgba(#60d9f1, 0.15), - monokai-button-origin-neutral : #38848c, - - monokai-header-origin-chosen : rgba(#60d9f1, 0.5), - monokai-line-origin-chosen : rgba(#60d9f1, 0.35), - monokai-button-origin-chosen : #3ea4b2, - - monokai-header-not-chosen : rgba(#76715d, 0.24), - monokai-line-not-chosen : rgba(#76715d, 0.1), - - solarized-light-header-head-neutral : rgba(#859900, 0.37), - solarized-light-line-head-neutral : rgba(#859900, 0.2), - solarized-light-button-head-neutral : #afb262, - - solarized-light-header-head-chosen : rgba(#859900, 0.5), - solarized-light-line-head-chosen : rgba(#859900, 0.37), - solarized-light-button-head-chosen : #94993d, - - solarized-light-header-origin-neutral : rgba(#2878c9, 0.37), - solarized-light-line-origin-neutral : rgba(#2878c9, 0.15), - solarized-light-button-origin-neutral : #60a1bf, - - solarized-light-header-origin-chosen : rgba(#2878c9, 0.6), - solarized-light-line-origin-chosen : rgba(#2878c9, 0.37), - solarized-light-button-origin-chosen : #2482b2, - - solarized-light-header-not-chosen : rgba(#839496, 0.37), - solarized-light-line-not-chosen : rgba(#839496, 0.2), - - solarized-dark-header-head-neutral : rgba(#859900, 0.35), - solarized-dark-line-head-neutral : rgba(#859900, 0.15), - solarized-dark-button-head-neutral : #376b20, - - solarized-dark-header-head-chosen : rgba(#859900, 0.5), - solarized-dark-line-head-chosen : rgba(#859900, 0.35), - solarized-dark-button-head-chosen : #39800d, - - solarized-dark-header-origin-neutral : rgba(#2878c9, 0.35), - solarized-dark-line-origin-neutral : rgba(#2878c9, 0.15), - solarized-dark-button-origin-neutral : #086799, - - solarized-dark-header-origin-chosen : rgba(#2878c9, 0.6), - solarized-dark-line-origin-chosen : rgba(#2878c9, 0.35), - solarized-dark-button-origin-chosen : #0082cc, - - solarized_dark_header_not_chosen : rgba(#839496, 0.25), - solarized_dark_line_not_chosen : rgba(#839496, 0.15), - - none_header_head_neutral : $gray-normal, - none_line_head_neutral : $gray-normal, - none_button_head_neutral : $gray-normal, - - none_header_head_chosen : $gray-darker, - none_line_head_chosen : $gray-darker, - none_button_head_chosen : $gray-darker, - - none_header_origin_neutral : $gray-normal, - none_line_origin_neutral : $gray-normal, - none_button_origin_neutral : $gray-normal, - - none_header_origin_chosen : $gray-darker, - none_line_origin_chosen : $gray-darker, - none_button_origin_chosen : $gray-darker, - - none_header_not_chosen : $gray-light, - none_line_not_chosen : $gray-light - -); -// scss-lint:enable ColorVariable +@import '../highlight/conflict_colors'; @mixin color-scheme($color) { .header.line_content, .diff-line-num { &.origin { - background-color: map-get($colors, #{$color}-header-origin-neutral); - border-color: map-get($colors, #{$color}-header-origin-neutral); + background-color: map-get($conflict-colors, #{$color}-header-origin-neutral); + border-color: map-get($conflict-colors, #{$color}-header-origin-neutral); button { - background-color: map-get($colors, #{$color}-button-origin-neutral); - border-color: darken(map-get($colors, #{$color}-button-origin-neutral), 15); + background-color: map-get($conflict-colors, #{$color}-button-origin-neutral); + border-color: darken(map-get($conflict-colors, #{$color}-button-origin-neutral), 15); } &.selected { - background-color: map-get($colors, #{$color}-header-origin-chosen); - border-color: map-get($colors, #{$color}-header-origin-chosen); + background-color: map-get($conflict-colors, #{$color}-header-origin-chosen); + border-color: map-get($conflict-colors, #{$color}-header-origin-chosen); button { - background-color: map-get($colors, #{$color}-button-origin-chosen); - border-color: darken(map-get($colors, #{$color}-button-origin-chosen), 15); + background-color: map-get($conflict-colors, #{$color}-button-origin-chosen); + border-color: darken(map-get($conflict-colors, #{$color}-button-origin-chosen), 15); } } &.unselected { - background-color: map-get($colors, #{$color}-header-not-chosen); - border-color: map-get($colors, #{$color}-header-not-chosen); + background-color: map-get($conflict-colors, #{$color}-header-not-chosen); + border-color: map-get($conflict-colors, #{$color}-header-not-chosen); button { - background-color: lighten(map-get($colors, #{$color}-button-origin-neutral), 15); - border-color: map-get($colors, #{$color}-button-origin-neutral); + background-color: lighten(map-get($conflict-colors, #{$color}-button-origin-neutral), 15); + border-color: map-get($conflict-colors, #{$color}-button-origin-neutral); } } } &.head { - background-color: map-get($colors, #{$color}-header-head-neutral); - border-color: map-get($colors, #{$color}-header-head-neutral); + background-color: map-get($conflict-colors, #{$color}-header-head-neutral); + border-color: map-get($conflict-colors, #{$color}-header-head-neutral); button { - background-color: map-get($colors, #{$color}-button-head-neutral); - border-color: darken(map-get($colors, #{$color}-button-head-neutral), 15); + background-color: map-get($conflict-colors, #{$color}-button-head-neutral); + border-color: darken(map-get($conflict-colors, #{$color}-button-head-neutral), 15); } &.selected { - background-color: map-get($colors, #{$color}-header-head-chosen); - border-color: map-get($colors, #{$color}-header-head-chosen); + background-color: map-get($conflict-colors, #{$color}-header-head-chosen); + border-color: map-get($conflict-colors, #{$color}-header-head-chosen); button { - background-color: map-get($colors, #{$color}-button-head-chosen); - border-color: darken(map-get($colors, #{$color}-button-head-chosen), 15); + background-color: map-get($conflict-colors, #{$color}-button-head-chosen); + border-color: darken(map-get($conflict-colors, #{$color}-button-head-chosen), 15); } } &.unselected { - background-color: map-get($colors, #{$color}-header-not-chosen); - border-color: map-get($colors, #{$color}-header-not-chosen); + background-color: map-get($conflict-colors, #{$color}-header-not-chosen); + border-color: map-get($conflict-colors, #{$color}-header-not-chosen); button { - background-color: lighten(map-get($colors, #{$color}-button-head-neutral), 15); - border-color: map-get($colors, #{$color}-button-head-neutral); + background-color: lighten(map-get($conflict-colors, #{$color}-button-head-neutral), 15); + border-color: map-get($conflict-colors, #{$color}-button-head-neutral); } } } @@ -185,26 +67,26 @@ $colors: ( .line_content { &.origin { - background-color: map-get($colors, #{$color}-line-origin-neutral); + background-color: map-get($conflict-colors, #{$color}-line-origin-neutral); &.selected { - background-color: map-get($colors, #{$color}-line-origin-chosen); + background-color: map-get($conflict-colors, #{$color}-line-origin-chosen); } &.unselected { - background-color: map-get($colors, #{$color}-line-not-chosen); + background-color: map-get($conflict-colors, #{$color}-line-not-chosen); } } &.head { - background-color: map-get($colors, #{$color}-line-head-neutral); + background-color: map-get($conflict-colors, #{$color}-line-head-neutral); &.selected { - background-color: map-get($colors, #{$color}-line-head-chosen); + background-color: map-get($conflict-colors, #{$color}-line-head-chosen); } &.unselected { - background-color: map-get($colors, #{$color}-line-not-chosen); + background-color: map-get($conflict-colors, #{$color}-line-not-chosen); } } } diff --git a/config/feature_flags/development/ci_pipeline_open_merge_requests.yml b/config/feature_flags/development/ci_pipeline_open_merge_requests.yml index e3be9e7323d..d50e49b6aa1 100644 --- a/config/feature_flags/development/ci_pipeline_open_merge_requests.yml +++ b/config/feature_flags/development/ci_pipeline_open_merge_requests.yml @@ -2,6 +2,7 @@ name: ci_pipeline_open_merge_requests introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38673 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292727 +milestone: '13.7' group: group::memory type: development default_enabled: false diff --git a/config/feature_flags/development/value_stream_analytics_extended_form.yml b/config/feature_flags/development/value_stream_analytics_extended_form.yml new file mode 100644 index 00000000000..1cdb9111af4 --- /dev/null +++ b/config/feature_flags/development/value_stream_analytics_extended_form.yml @@ -0,0 +1,8 @@ +--- +name: value_stream_analytics_extended_form +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50229 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/294190 +milestone: '13.7' +type: development +group: group::optimize +default_enabled: false diff --git a/doc/administration/postgresql/replication_and_failover.md b/doc/administration/postgresql/replication_and_failover.md index 303246c9c82..702891fab65 100644 --- a/doc/administration/postgresql/replication_and_failover.md +++ b/doc/administration/postgresql/replication_and_failover.md @@ -943,6 +943,16 @@ standby nodes. gitlab-ctl repmgr standby follow NEW_MASTER ``` +#### Geo secondary site considerations + +When a Geo secondary site is replicating from a primary site that uses `repmgr` and `PgBouncer`, [replicating through PgBouncer is not supported](https://github.com/pgbouncer/pgbouncer/issues/382#issuecomment-517911529) and the secondary must replicate directly from the leader node in the `repmgr` cluster. Therefore, when there is a failover in the `repmgr` cluster, you will need to manually re-point your secondary site to replicate from the new leader with: + +```shell +sudo gitlab-ctl replicate-geo-database --host=<new_leader_ip> --replication-slot=<slot_name> +``` + +Otherwise, the replication will not happen anymore, even if the original node gets re-added as a follower node. This will re-sync your secondary site database and may take a long time depending on the amount of data to sync. + ### Restore procedure If a node fails, it can be removed from the cluster, or added back as a standby diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md index 370247ed856..d9eab28bfd6 100644 --- a/doc/administration/reference_architectures/3k_users.md +++ b/doc/administration/reference_architectures/3k_users.md @@ -17,6 +17,8 @@ highly-available GitLab deployment. If you do not have the expertise or need to maintain a highly-available environment, you can have a simpler and less costly-to-operate environment by using the [2,000-user reference architecture](2k_users.md). +If you have fewer than 3,000 users and still want a highly-available GitLab deployment, +you should still use this reference architecture but scale down the node sizes. > - **Supported users (approximate):** 3,000 > - **High Availability:** Yes diff --git a/doc/ci/pipelines/index.md b/doc/ci/pipelines/index.md index 22e331f2de0..160b10aefa2 100644 --- a/doc/ci/pipelines/index.md +++ b/doc/ci/pipelines/index.md @@ -132,6 +132,10 @@ Pipelines can be manually executed, with predefined or manually-specified [varia You might do this if the results of a pipeline (for example, a code build) are required outside the normal operation of the pipeline. +[In GitLab 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30101), +the variables fields are pre-filled with any global variables defined in the +`.gitlab-ci.yml` file. + To execute a pipeline manually: 1. Navigate to your project's **CI/CD > Pipelines**. diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md index 7c5333c9aa6..f0667cdbcd5 100644 --- a/doc/development/feature_flags/development.md +++ b/doc/development/feature_flags/development.md @@ -378,6 +378,18 @@ You can also enable a feature flag for a given gate: Feature.enable(:feature_flag_name, Project.find_by_full_path("root/my-project")) ``` +### Removing a feature flag locally (in development) + +When manually enabling or disabling a feature flag from the Rails console, its default value gets overwritten. +This can cause confusion when changing the flag's `default_enabled` attribute. + +To reset the feature flag to the default status, you can remove it in the rails console (`rails c`) +as follows: + +```ruby +Feature.remove(:feature_flag_name) +``` + ## Feature flags in tests Introducing a feature flag into the codebase creates an additional code path that should be tested. diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md index 751915f84a0..b6d47b56bce 100644 --- a/doc/user/packages/composer_repository/index.md +++ b/doc/user/packages/composer_repository/index.md @@ -132,6 +132,13 @@ A more detailed Composer CI/CD file is also available as a `.gitlab-ci.yml` temp WARNING: Do not save unless you want to overwrite the existing CI/CD file. +## Publishing packages with the same name or version + +When you publish: + +- The same package with different data, it overwrites the existing package. +- The same package with the same data, a `404 Bad request` error occurs. + ## Install a Composer package Install a package from the Package Registry so you can use it as a dependency. diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md index 51b41b842fa..c411fd556df 100644 --- a/doc/user/packages/npm_registry/index.md +++ b/doc/user/packages/npm_registry/index.md @@ -465,3 +465,26 @@ If you get this error, your package name may not meet the Ensure the name meets the convention exactly, including the case. Then try to publish again. + +### `npm publish` returns `npm ERR! 500 Internal Server Error - PUT` + +This is a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/238950) in GitLab +13.3.x and later. The error in the logs will appear as: + +```plaintext +>NoMethodError - undefined method `preferred_language' for #<Rack::Response +``` + +This might be accompanied by another error: + +```plaintext +>Errno::EACCES","exception.message":"Permission denied +``` + +This is usually a permissions issue with either: + +- `'packages_storage_path'` default `/var/opt/gitlab/gitlab-rails/shared/packages/`. +- The remote bucket if [object storage](../../../administration/packages/#using-object-storage) + is used. + +In the latter case, ensure the bucket exists and the GitLab has write access to it. diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md index e78224f89d1..954ea91ba28 100644 --- a/doc/user/packages/pypi_repository/index.md +++ b/doc/user/packages/pypi_repository/index.md @@ -301,6 +301,12 @@ python -m twine upload --repository <source_name> dist/<package_file> - `<package_file>` is your package filename, ending in `.tar.gz` or `.whl`. - `<source_name>` is the [source name used during setup](#authenticate-with-the-package-registry). +### Publishing packages with the same name or version + +You cannot publish a package if a package of the same name and version already exists. +You must delete the existing package first. If you attempt to publish the same package +more than once, a `404 Bad request` error occurs. + ## Install a PyPI package To install the latest version of a package, use the following command: diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md index fe2371947b4..e3501be8e8e 100644 --- a/doc/user/search/advanced_search_syntax.md +++ b/doc/user/search/advanced_search_syntax.md @@ -52,14 +52,17 @@ The Advanced Search Syntax also supports the use of filters. The available filte - blob: Filters by Git `object ID`. Exact match only. To use them, add them to your keyword in the format `<filter_name>:<value>` without -any spaces between the colon (`:`) and the value. A keyword or an asterisk (`*`) is required for filter searches and has to be added in front of the filter separated by a space. +any spaces between the colon (`:`) and the value. When no keyword is provided, an asterisk (`*`) will be used as the keyword. Examples: - Finding a file with any content named `search_results.rb`: [`* filename:search_results.rb`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=*+filename%3Asearch_results.rb&group_id=9970&project_id=278964) -- Finding a file named `found_blob_spec.rb` with the text `CHANGELOG` inside of it: [`CHANGELOG filename:found_blob_spec.rb](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=CHANGELOG+filename%3Afound_blob_spec.rb&group_id=9970&project_id=278964) +- The leading asterisk (`*`) can be ignored in the case above: [`filename:search_results.rb`](https://gitlab.com/search?group_id=9970&project_id=278964&scope=blobs&search=filename%3Asearch_results.rb) +- Finding a file named `found_blob_spec.rb` with the text `CHANGELOG` inside of it: [`CHANGELOG filename:found_blob_spec.rb`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=CHANGELOG+filename%3Afound_blob_spec.rb&group_id=9970&project_id=278964) - Finding the text `EpicLinks` inside files with the `.rb` extension: [`EpicLinks extension:rb`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=EpicLinks+extension%3Arb&group_id=9970&project_id=278964) +- Finding any file with the `.yaml` extension: [`extension:yaml`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=extension%3Ayaml&group_id=9970&project_id=278964) - Finding the text `Sidekiq` in a file, when that file is in a path that includes `elastic`: [`Sidekiq path:elastic`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=Sidekiq+path%3Aelastic&group_id=9970&project_id=278964) +- Finding any file in a path that includes `elasticsearch`: [`path:elasticsearch`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=path%3Aelasticsearch&group_id=9970&project_id=278964) - Finding the files represented by the Git object ID `998707b421c89bd9a3063333f9f728ef3e43d101`: [`* blob:998707b421c89bd9a3063333f9f728ef3e43d101`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=false&scope=blobs&repository_ref=&search=*+blob%3A998707b421c89bd9a3063333f9f728ef3e43d101&group_id=9970) - Syntax filters can be combined for complex filtering. Finding any file starting with `search` containing `eventHub` and with the `.js` extension: [`eventHub filename:search* extension:js`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=eventHub+filename%3Asearch*+extension%3Ajs&group_id=9970&project_id=278964) diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 4d7590a8e38..fbf021345ca 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -9,9 +9,13 @@ module Gitlab CONTEXT_LINES = 3 + CONFLICT_MARKER_OUR = 'conflict_marker_our' + CONFLICT_MARKER_THEIR = 'conflict_marker_their' + CONFLICT_MARKER_SEPARATOR = 'conflict_marker' + CONFLICT_TYPES = { - "old" => "conflict_marker_their", - "new" => "conflict_marker_our" + "old" => "conflict_their", + "new" => "conflict_our" }.freeze attr_reader :merge_request @@ -59,18 +63,25 @@ module Gitlab if section[:conflict] lines = [] - initial_type = nil + lines << create_separator_line(section[:lines].first, CONFLICT_MARKER_OUR) + + current_type = section[:lines].first.type section[:lines].each do |line| - if line.type != initial_type - lines << create_separator_line(line) - initial_type = line.type + if line.type != current_type # insert a separator between our changes and theirs + lines << create_separator_line(line, CONFLICT_MARKER_SEPARATOR) + current_type = line.type end line.type = CONFLICT_TYPES[line.type] + + # Swap the positions around due to conflicts/diffs display inconsistency + # https://gitlab.com/gitlab-org/gitlab/-/issues/291989 + line.old_pos, line.new_pos = line.new_pos, line.old_pos + lines << line end - lines << create_separator_line(lines.last) + lines << create_separator_line(lines.last, CONFLICT_MARKER_THEIR) lines else @@ -156,8 +167,8 @@ module Gitlab Gitlab::Diff::Line.new('', 'match', line.index, line.old_pos, line.new_pos) end - def create_separator_line(line) - Gitlab::Diff::Line.new('', 'conflict_marker', line.index, nil, nil) + def create_separator_line(line, type) + Gitlab::Diff::Line.new('', type, line.index, nil, nil) end # Any line beginning with a letter, an underscore, or a dollar can be used in a diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index af9140215f0..98ed2400d82 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -8,9 +8,9 @@ module Gitlab # SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze - attr_reader :line_code, :old_pos, :new_pos + attr_reader :line_code attr_writer :rich_text - attr_accessor :text, :index, :type + attr_accessor :text, :index, :type, :old_pos, :new_pos def initialize(text, type, index, old_pos, new_pos, parent_file: nil, line_code: nil, rich_text: nil) @text, @type, @index = text, type, index diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 176e2c4d29d..0fd057100b0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8617,6 +8617,9 @@ msgstr "" msgid "DastProfiles|Active" msgstr "" +msgid "DastProfiles|Additional request headers (Optional)" +msgstr "" + msgid "DastProfiles|Are you sure you want to delete this profile?" msgstr "" @@ -8686,6 +8689,9 @@ msgstr "" msgid "DastProfiles|Excluded URLs" msgstr "" +msgid "DastProfiles|Excluded URLs (Optional)" +msgstr "" + msgid "DastProfiles|Hide debug messages" msgstr "" diff --git a/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb b/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb index 782a7e3bfb6..ac0c66524f0 100644 --- a/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb +++ b/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb @@ -69,7 +69,8 @@ RSpec.describe 'Batch diffs', :js do end context 'which is in at least page 2 of the batched pages of diffs' do - it 'scrolls to the correct discussion' do + it 'scrolls to the correct discussion', + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/293814' } do page.within('.diff-file.file-holder:last-of-type') do click_link('just now') end diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js index 7ee97224707..3c44ba3fc07 100644 --- a/spec/frontend/diffs/store/utils_spec.js +++ b/spec/frontend/diffs/store/utils_spec.js @@ -1119,6 +1119,42 @@ describe('DiffsStoreUtils', () => { }); }); + describe('isConflictMarker', () => { + it.each` + type | expected + ${'conflict_marker_our'} | ${true} + ${'conflict_marker_their'} | ${true} + ${'conflict_their'} | ${false} + ${'conflict_our'} | ${false} + `('returns $expected when type is $type', ({ type, expected }) => { + expect(utils.isConflictMarker({ type })).toBe(expected); + }); + }); + + describe('isConflictOur', () => { + it.each` + type | expected + ${'conflict_marker_our'} | ${false} + ${'conflict_marker_their'} | ${false} + ${'conflict_their'} | ${false} + ${'conflict_our'} | ${true} + `('returns $expected when type is $type', ({ type, expected }) => { + expect(utils.isConflictOur({ type })).toBe(expected); + }); + }); + + describe('isConflictTheir', () => { + it.each` + type | expected + ${'conflict_marker_our'} | ${false} + ${'conflict_marker_their'} | ${false} + ${'conflict_their'} | ${true} + ${'conflict_our'} | ${false} + `('returns $expected when type is $type', ({ type, expected }) => { + expect(utils.isConflictTheir({ type })).toBe(expected); + }); + }); + describe('parallelizeDiffLines', () => { it('converts inline diff lines to parallel diff lines', () => { const file = getDiffFileMock(); @@ -1128,6 +1164,34 @@ describe('DiffsStoreUtils', () => { ); }); + it('converts conflicted diffs line', () => { + const lines = [ + { type: 'new' }, + { type: 'conflict_marker_our' }, + { type: 'conflict_our' }, + { type: 'conflict_marker' }, + { type: 'conflict_their' }, + { type: 'conflict_marker_their' }, + ]; + + expect(utils.parallelizeDiffLines(lines)).toEqual([ + { + left: null, + right: { + type: 'new', + }, + }, + { + left: { type: 'conflict_marker_our' }, + right: { type: 'conflict_marker_their' }, + }, + { + left: { type: 'conflict_our' }, + right: { type: 'conflict_their' }, + }, + ]); + }); + it('converts inline diff lines', () => { const file = getDiffFileMock(); const files = utils.parallelizeDiffLines(file.highlighted_diff_lines, true); diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index 0de944d3f8a..bb9bee763d8 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -97,19 +97,27 @@ RSpec.describe Gitlab::Conflict::File do let(:diff_line_types) { conflict_file.diff_lines_for_serializer.map(&:type) } it 'assigns conflict types to the diff lines' do - expect(diff_line_types[4]).to eq('conflict_marker') - expect(diff_line_types[5..10]).to eq(['conflict_marker_our'] * 6) + expect(diff_line_types[4]).to eq('conflict_marker_our') + expect(diff_line_types[5..10]).to eq(['conflict_our'] * 6) expect(diff_line_types[11]).to eq('conflict_marker') - expect(diff_line_types[12..17]).to eq(['conflict_marker_their'] * 6) - expect(diff_line_types[18]).to eq('conflict_marker') + expect(diff_line_types[12..17]).to eq(['conflict_their'] * 6) + expect(diff_line_types[18]).to eq('conflict_marker_their') expect(diff_line_types[19..24]).to eq([nil] * 6) - expect(diff_line_types[25]).to eq('conflict_marker') - expect(diff_line_types[26..27]).to eq(['conflict_marker_our'] * 2) + expect(diff_line_types[25]).to eq('conflict_marker_our') + expect(diff_line_types[26..27]).to eq(['conflict_our'] * 2) expect(diff_line_types[28]).to eq('conflict_marker') - expect(diff_line_types[29..30]).to eq(['conflict_marker_their'] * 2) - expect(diff_line_types[31]).to eq('conflict_marker') + expect(diff_line_types[29..30]).to eq(['conflict_their'] * 2) + expect(diff_line_types[31]).to eq('conflict_marker_their') + end + + # Swap the positions around due to conflicts/diffs display inconsistency + # https://gitlab.com/gitlab-org/gitlab/-/issues/291989 + it 'swaps the new and old positions around' do + lines = conflict_file.diff_lines_for_serializer + expect(lines.map(&:old_pos)[26..27]).to eq([21, 22]) + expect(lines.map(&:new_pos)[29..30]).to eq([21, 22]) end it 'does not add a match line to the end of the section' do @@ -124,13 +132,13 @@ RSpec.describe Gitlab::Conflict::File do expect(diff_line_types).to eq([ 'match', nil, nil, nil, - "conflict_marker", "conflict_marker_our", + "conflict_our", "conflict_marker", + "conflict_their", + "conflict_their", + "conflict_their", "conflict_marker_their", - "conflict_marker_their", - "conflict_marker_their", - "conflict_marker", nil, nil, nil, "match" ]) |