Commit fcd8d219 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'ph/frontendDisplayConflicts' into 'master'

Display merge conflicts in diffs app

See merge request gitlab-org/gitlab!47313
parents cc90547e d4d084c2
<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"
>
&nbsp;
</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>
......
......@@ -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';
......@@ -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;
}
}
}
......
@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);
}
}
}
}
// 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
......@@ -198,6 +198,8 @@ $dark-il: #de935f;
}
}
@include conflict-colors('dark');
// highlight line via anchor
pre .hll {
background-color: $dark-pre-hll-bg !important;
......
......@@ -198,6 +198,8 @@ $monokai-gi: #a6e22e;
}
}
@include conflict-colors('monokai');
// highlight line via anchor
pre .hll {
background-color: $monokai-hll !important;
......
......@@ -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;
......
......@@ -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;
......
.code.white {
@import '../white_base';
@include conflict-colors('white');
}
......@@ -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);
}
}
......
@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);
}
}
}
......
......@@ -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
......
......@@ -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
......
......@@ -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);
......
......@@ -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"
])
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment