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> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; 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 DiffGutterAvatars from './diff_gutter_avatars.vue';
import * as utils from './diff_row_utils'; import * as utils from './diff_row_utils';
...@@ -71,6 +79,12 @@ export default { ...@@ -71,6 +79,12 @@ export default {
addCommentTooltipRight() { addCommentTooltipRight() {
return utils.addCommentTooltip(this.line.right); 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() { shouldRenderCommentButton() {
return ( return (
this.isLoggedIn && this.isLoggedIn &&
...@@ -80,6 +94,9 @@ export default { ...@@ -80,6 +94,9 @@ export default {
!this.line.hasDiscussionsRight !this.line.hasDiscussionsRight
); );
}, },
isLeftConflictMarker() {
return [CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(this.line.left?.type);
},
}, },
mounted() { mounted() {
this.scrollToLineIfNeededParallel(this.line); this.scrollToLineIfNeededParallel(this.line);
...@@ -107,37 +124,50 @@ export default { ...@@ -107,37 +124,50 @@ export default {
handleCommentButton(line) { handleCommentButton(line) {
this.showCommentForm({ lineCode: line.line_code, fileHash: this.fileHash }); 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> </script>
<template> <template>
<div :class="classNameMap" class="diff-grid-row diff-tr line_holder"> <div :class="classNameMap" class="diff-grid-row diff-tr line_holder">
<div class="diff-grid-left left-side"> <div class="diff-grid-left left-side">
<template v-if="line.left"> <template v-if="line.left && line.left.type !== $options.CONFLICT_MARKER">
<div <div
:class="classNameMapCellLeft" :class="classNameMapCellLeft"
data-testid="leftLineNumber" data-testid="leftLineNumber"
class="diff-td diff-line-num old_line" class="diff-td diff-line-num old_line"
> >
<span <template v-if="!isLeftConflictMarker">
v-if="shouldRenderCommentButton" <span
v-gl-tooltip v-if="shouldRenderCommentButton"
data-testid="leftCommentButton" v-gl-tooltip
class="add-diff-note tooltip-wrapper" data-testid="leftCommentButton"
:title="addCommentTooltipLeft" 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)"
> >
<gl-icon :size="12" name="comment" /> <button
</button> type="button"
</span> 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 <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" :data-linenumber="line.left.old_line"
:href="line.lineHrefOld" :href="line.lineHrefOld"
@click="setHighlightedRow(line.lineCode)" @click="setHighlightedRow(line.lineCode)"
...@@ -159,7 +189,7 @@ export default { ...@@ -159,7 +189,7 @@ export default {
</div> </div>
<div v-if="inline" :class="classNameMapCellLeft" class="diff-td diff-line-num old_line"> <div v-if="inline" :class="classNameMapCellLeft" class="diff-td diff-line-num old_line">
<a <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" :data-linenumber="line.left.new_line"
:href="line.lineHrefOld" :href="line.lineHrefOld"
@click="setHighlightedRow(line.lineCode)" @click="setHighlightedRow(line.lineCode)"
...@@ -170,39 +200,59 @@ export default { ...@@ -170,39 +200,59 @@ export default {
<div <div
:id="line.left.line_code" :id="line.left.line_code"
:key="line.left.line_code" :key="line.left.line_code"
v-safe-html="line.left.rich_text"
:class="parallelViewLeftLineType" :class="parallelViewLeftLineType"
class="diff-td line_content with-coverage parallel left-side" class="diff-td line_content with-coverage parallel left-side"
data-testid="leftContent" data-testid="leftContent"
@mousedown="handleParallelLineMouseDown" @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>
<template v-else> <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"></div> <div
<div v-if="inline" class="diff-td diff-line-num old_line empty-cell"></div> data-testid="leftEmptyCell"
<div class="diff-td line-coverage left-side empty-cell"></div> class="diff-td diff-line-num old_line empty-cell"
<div class="diff-td line_content with-coverage parallel left-side empty-cell"></div> :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> </template>
</div> </div>
<div v-if="!inline" class="diff-grid-right right-side"> <div v-if="!inline" class="diff-grid-right right-side">
<template v-if="line.right"> <template v-if="line.right">
<div :class="classNameMapCellRight" class="diff-td diff-line-num new_line"> <div :class="classNameMapCellRight" class="diff-td diff-line-num new_line">
<span <template v-if="line.right.type !== $options.CONFLICT_MARKER_THEIR">
v-if="shouldRenderCommentButton" <span
v-gl-tooltip v-if="shouldRenderCommentButton"
data-testid="rightCommentButton" v-gl-tooltip
class="add-diff-note tooltip-wrapper" data-testid="rightCommentButton"
:title="addCommentTooltipRight" 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)"
> >
<gl-icon :size="12" name="comment" /> <button
</button> type="button"
</span> 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 <a
v-if="line.right.new_line" v-if="line.right.new_line"
:data-linenumber="line.right.new_line" :data-linenumber="line.right.new_line"
...@@ -233,22 +283,35 @@ export default { ...@@ -233,22 +283,35 @@ export default {
<div <div
:id="line.right.line_code" :id="line.right.line_code"
:key="line.right.rich_text" :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" class="diff-td line_content with-coverage parallel right-side"
@mousedown="handleParallelLineMouseDown" @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>
<template v-else> <template v-else>
<div data-testid="rightEmptyCell" class="diff-td diff-line-num old_line empty-cell"></div> <div
<div class="diff-td diff-line-num old_line empty-cell"></div> data-testid="rightEmptyCell"
<div class="diff-td line-coverage right-side empty-cell"></div> class="diff-td diff-line-num old_line empty-cell"
<div class="diff-td line_content with-coverage parallel right-side empty-cell"></div> :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> </template>
</div> </div>
</div> </div>
......
...@@ -109,3 +109,9 @@ export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd'; ...@@ -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_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_FIRST_DIFF_FILE_SHOWN = 'mr:diffs:perf:firstFileShown';
export const EVT_PERF_MARK_DIFF_FILES_END = 'mr:diffs:perf:filesEnd'; 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 { ...@@ -15,6 +15,11 @@ import {
INLINE_DIFF_LINES_KEY, INLINE_DIFF_LINES_KEY,
SHOW_WHITESPACE, SHOW_WHITESPACE,
NO_SHOW_WHITESPACE, NO_SHOW_WHITESPACE,
CONFLICT_OUR,
CONFLICT_THEIR,
CONFLICT_MARKER,
CONFLICT_MARKER_OUR,
CONFLICT_MARKER_THEIR,
} from '../constants'; } from '../constants';
import { prepareRawDiffFile } from '../utils/diff_file'; import { prepareRawDiffFile } from '../utils/diff_file';
...@@ -22,6 +27,11 @@ export const isAdded = line => ['new', 'new-nonewline'].includes(line.type); ...@@ -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 isRemoved = line => ['old', 'old-nonewline'].includes(line.type);
export const isUnchanged = line => !line.type; export const isUnchanged = line => !line.type;
export const isMeta = line => ['match', 'new-nonewline', 'old-nonewline'].includes(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 * Pass in the inline diff lines array which gets converted
...@@ -42,12 +52,13 @@ export const isMeta = line => ['match', 'new-nonewline', 'old-nonewline'].includ ...@@ -42,12 +52,13 @@ export const isMeta = line => ['match', 'new-nonewline', 'old-nonewline'].includ
export const parallelizeDiffLines = (diffLines, inline) => { export const parallelizeDiffLines = (diffLines, inline) => {
let freeRightIndex = null; let freeRightIndex = null;
let conflictStartIndex = -1;
const lines = []; const lines = [];
for (let i = 0, diffLinesLength = diffLines.length, index = 0; i < diffLinesLength; i += 1) { for (let i = 0, diffLinesLength = diffLines.length, index = 0; i < diffLinesLength; i += 1) {
const line = diffLines[i]; const line = diffLines[i];
if (isRemoved(line) || inline) { if (isRemoved(line) || isConflictOur(line) || inline) {
lines.push({ lines.push({
[LINE_POSITION_LEFT]: line, [LINE_POSITION_LEFT]: line,
[LINE_POSITION_RIGHT]: null, [LINE_POSITION_RIGHT]: null,
...@@ -58,7 +69,7 @@ export const parallelizeDiffLines = (diffLines, inline) => { ...@@ -58,7 +69,7 @@ export const parallelizeDiffLines = (diffLines, inline) => {
freeRightIndex = index; freeRightIndex = index;
} }
index += 1; index += 1;
} else if (isAdded(line)) { } else if (isAdded(line) || isConflictTheir(line)) {
if (freeRightIndex !== null) { if (freeRightIndex !== null) {
// If an old line came before this without a line on the right, this // If an old line came before this without a line on the right, this
// line can be put to the right of it. // line can be put to the right of it.
...@@ -77,15 +88,28 @@ export const parallelizeDiffLines = (diffLines, inline) => { ...@@ -77,15 +88,28 @@ export const parallelizeDiffLines = (diffLines, inline) => {
freeRightIndex = null; freeRightIndex = null;
index += 1; index += 1;
} }
} else if (isMeta(line) || isUnchanged(line)) { } else if (
// line in the right panel is the same as in the left one isMeta(line) ||
lines.push({ isUnchanged(line) ||
[LINE_POSITION_LEFT]: line, isConflictMarker(line) ||
[LINE_POSITION_RIGHT]: 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; if (!inline && isConflictMarker(line)) {
index += 1; conflictStartIndex = index;
}
freeRightIndex = null;
index += 1;
} else {
lines[conflictStartIndex][LINE_POSITION_RIGHT] = line;
conflictStartIndex = -1;
}
} }
} }
......
@import '../framework/variables'; @import '../framework/variables';
@import './conflict_colors';
@mixin diff-background($background, $idiff, $border) { @mixin diff-background($background, $idiff, $border) {
background: $background; background: $background;
...@@ -51,3 +52,44 @@ ...@@ -51,3 +52,44 @@
color: darken($color, 15%); 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; ...@@ -198,6 +198,8 @@ $dark-il: #de935f;
} }
} }
@include conflict-colors('dark');
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: $dark-pre-hll-bg !important; background-color: $dark-pre-hll-bg !important;
......
...@@ -198,6 +198,8 @@ $monokai-gi: #a6e22e; ...@@ -198,6 +198,8 @@ $monokai-gi: #a6e22e;
} }
} }
@include conflict-colors('monokai');
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: $monokai-hll !important; background-color: $monokai-hll !important;
......
...@@ -202,6 +202,8 @@ $solarized-dark-il: #2aa198; ...@@ -202,6 +202,8 @@ $solarized-dark-il: #2aa198;
} }
} }
@include conflict-colors('solarized-dark');
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: $solarized-dark-hll-bg !important; background-color: $solarized-dark-hll-bg !important;
......
...@@ -210,6 +210,8 @@ $solarized-light-il: #2aa198; ...@@ -210,6 +210,8 @@ $solarized-light-il: #2aa198;
} }
} }
@include conflict-colors('solarized-light');
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: $solarized-light-hll-bg !important; background-color: $solarized-light-hll-bg !important;
......
.code.white { .code.white {
@import '../white_base'; @import '../white_base';
@include conflict-colors('white');
} }
...@@ -81,6 +81,17 @@ $white-gc-bg: #eaf2f5; ...@@ -81,6 +81,17 @@ $white-gc-bg: #eaf2f5;
.line-numbers, .line-numbers,
.diff-line-num { .diff-line-num {
background-color: $gray-light; 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, .diff-line-num,
...@@ -115,7 +126,7 @@ pre.code, ...@@ -115,7 +126,7 @@ pre.code,
.diff-grid-left:hover, .diff-grid-left:hover,
.diff-grid-right: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); @include line-number-hover($white-over-bg);
} }
} }
......
@import 'mixins_and_variables_and_functions'; @import 'mixins_and_variables_and_functions';
// Disabled to use the color map for creating color schemes @import '../highlight/conflict_colors';
// 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
@mixin color-scheme($color) { @mixin color-scheme($color) {
.header.line_content, .header.line_content,
.diff-line-num { .diff-line-num {
&.origin { &.origin {
background-color: map-get($colors, #{$color}-header-origin-neutral); background-color: map-get($conflict-colors, #{$color}-header-origin-neutral);
border-color: map-get($colors, #{$color}-header-origin-neutral); border-color: map-get($conflict-colors, #{$color}-header-origin-neutral);
button { button {
background-color: map-get($colors, #{$color}-button-origin-neutral); background-color: map-get($conflict-colors, #{$color}-button-origin-neutral);
border-color: darken(map-get($colors, #{$color}-button-origin-neutral), 15); border-color: darken(map-get($conflict-colors, #{$color}-button-origin-neutral), 15);
} }
&.selected { &.selected {
background-color: map-get($colors, #{$color}-header-origin-chosen); background-color: map-get($conflict-colors, #{$color}-header-origin-chosen);
border-color: map-get($colors, #{$color}-header-origin-chosen); border-color: map-get($conflict-colors, #{$color}-header-origin-chosen);
button { button {
background-color: map-get($colors, #{$color}-button-origin-chosen); background-color: map-get($conflict-colors, #{$color}-button-origin-chosen);
border-color: darken(map-get($colors, #{$color}-button-origin-chosen), 15); border-color: darken(map-get($conflict-colors, #{$color}-button-origin-chosen), 15);
} }
} }
&.unselected { &.unselected {
background-color: map-get($colors, #{$color}-header-not-chosen); background-color: map-get($conflict-colors, #{$color}-header-not-chosen);
border-color: map-get($colors, #{$color}-header-not-chosen); border-color: map-get($conflict-colors, #{$color}-header-not-chosen);
button { button {
background-color: lighten(map-get($colors, #{$color}-button-origin-neutral), 15); background-color: lighten(map-get($conflict-colors, #{$color}-button-origin-neutral), 15);
border-color: map-get($colors, #{$color}-button-origin-neutral); border-color: map-get($conflict-colors, #{$color}-button-origin-neutral);
} }
} }
} }
&.head { &.head {
background-color: map-get($colors, #{$color}-header-head-neutral); background-color: map-get($conflict-colors, #{$color}-header-head-neutral);
border-color: map-get($colors, #{$color}-header-head-neutral); border-color: map-get($conflict-colors, #{$color}-header-head-neutral);
button { button {
background-color: map-get($colors, #{$color}-button-head-neutral); background-color: map-get($conflict-colors, #{$color}-button-head-neutral);
border-color: darken(map-get($colors, #{$color}-button-head-neutral), 15); border-color: darken(map-get($conflict-colors, #{$color}-button-head-neutral), 15);
} }
&.selected { &.selected {
background-color: map-get($colors, #{$color}-header-head-chosen); background-color: map-get($conflict-colors, #{$color}-header-head-chosen);
border-color: map-get($colors, #{$color}-header-head-chosen); border-color: map-get($conflict-colors, #{$color}-header-head-chosen);
button { button {
background-color: map-get($colors, #{$color}-button-head-chosen); background-color: map-get($conflict-colors, #{$color}-button-head-chosen);
border-color: darken(map-get($colors, #{$color}-button-head-chosen), 15); border-color: darken(map-get($conflict-colors, #{$color}-button-head-chosen), 15);
} }
} }
&.unselected { &.unselected {
background-color: map-get($colors, #{$color}-header-not-chosen); background-color: map-get($conflict-colors, #{$color}-header-not-chosen);
border-color: map-get($colors, #{$color}-header-not-chosen); border-color: map-get($conflict-colors, #{$color}-header-not-chosen);
button { button {
background-color: lighten(map-get($colors, #{$color}-button-head-neutral), 15); background-color: lighten(map-get($conflict-colors, #{$color}-button-head-neutral), 15);
border-color: map-get($colors, #{$color}-button-head-neutral); border-color: map-get($conflict-colors, #{$color}-button-head-neutral);
} }
} }
} }
...@@ -185,26 +67,26 @@ $colors: ( ...@@ -185,26 +67,26 @@ $colors: (
.line_content { .line_content {
&.origin { &.origin {
background-color: map-get($colors, #{$color}-line-origin-neutral); background-color: map-get($conflict-colors, #{$color}-line-origin-neutral);
&.selected { &.selected {
background-color: map-get($colors, #{$color}-line-origin-chosen); background-color: map-get($conflict-colors, #{$color}-line-origin-chosen);
} }
&.unselected { &.unselected {
background-color: map-get($colors, #{$color}-line-not-chosen); background-color: map-get($conflict-colors, #{$color}-line-not-chosen);
} }
} }
&.head { &.head {
background-color: map-get($colors, #{$color}-line-head-neutral); background-color: map-get($conflict-colors, #{$color}-line-head-neutral);
&.selected { &.selected {
background-color: map-get($colors, #{$color}-line-head-chosen); background-color: map-get($conflict-colors, #{$color}-line-head-chosen);
} }
&.unselected { &.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 ...@@ -9,9 +9,13 @@ module Gitlab
CONTEXT_LINES = 3 CONTEXT_LINES = 3
CONFLICT_MARKER_OUR = 'conflict_marker_our'
CONFLICT_MARKER_THEIR = 'conflict_marker_their'
CONFLICT_MARKER_SEPARATOR = 'conflict_marker'
CONFLICT_TYPES = { CONFLICT_TYPES = {
"old" => "conflict_marker_their", "old" => "conflict_their",
"new" => "conflict_marker_our" "new" => "conflict_our"
}.freeze }.freeze
attr_reader :merge_request attr_reader :merge_request
...@@ -59,18 +63,25 @@ module Gitlab ...@@ -59,18 +63,25 @@ module Gitlab
if section[:conflict] if section[:conflict]
lines = [] 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| section[:lines].each do |line|
if line.type != initial_type if line.type != current_type # insert a separator between our changes and theirs
lines << create_separator_line(line) lines << create_separator_line(line, CONFLICT_MARKER_SEPARATOR)
initial_type = line.type current_type = line.type
end end
line.type = CONFLICT_TYPES[line.type] 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 lines << line
end end
lines << create_separator_line(lines.last) lines << create_separator_line(lines.last, CONFLICT_MARKER_THEIR)
lines lines
else else
...@@ -156,8 +167,8 @@ module Gitlab ...@@ -156,8 +167,8 @@ module Gitlab
Gitlab::Diff::Line.new('', 'match', line.index, line.old_pos, line.new_pos) Gitlab::Diff::Line.new('', 'match', line.index, line.old_pos, line.new_pos)
end end
def create_separator_line(line) def create_separator_line(line, type)
Gitlab::Diff::Line.new('', 'conflict_marker', line.index, nil, nil) Gitlab::Diff::Line.new('', type, line.index, nil, nil)
end end
# Any line beginning with a letter, an underscore, or a dollar can be used in a # Any line beginning with a letter, an underscore, or a dollar can be used in a
......
...@@ -8,9 +8,9 @@ module Gitlab ...@@ -8,9 +8,9 @@ module Gitlab
# #
SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze
attr_reader :line_code, :old_pos, :new_pos attr_reader :line_code
attr_writer :rich_text 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) def initialize(text, type, index, old_pos, new_pos, parent_file: nil, line_code: nil, rich_text: nil)
@text, @type, @index = text, type, index @text, @type, @index = text, type, index
......
...@@ -1119,6 +1119,42 @@ describe('DiffsStoreUtils', () => { ...@@ -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', () => { describe('parallelizeDiffLines', () => {
it('converts inline diff lines to parallel diff lines', () => { it('converts inline diff lines to parallel diff lines', () => {
const file = getDiffFileMock(); const file = getDiffFileMock();
...@@ -1128,6 +1164,34 @@ describe('DiffsStoreUtils', () => { ...@@ -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', () => { it('converts inline diff lines', () => {
const file = getDiffFileMock(); const file = getDiffFileMock();
const files = utils.parallelizeDiffLines(file.highlighted_diff_lines, true); const files = utils.parallelizeDiffLines(file.highlighted_diff_lines, true);
......
...@@ -97,19 +97,27 @@ RSpec.describe Gitlab::Conflict::File do ...@@ -97,19 +97,27 @@ RSpec.describe Gitlab::Conflict::File do
let(:diff_line_types) { conflict_file.diff_lines_for_serializer.map(&:type) } let(:diff_line_types) { conflict_file.diff_lines_for_serializer.map(&:type) }
it 'assigns conflict types to the diff lines' do it 'assigns conflict types to the diff lines' do
expect(diff_line_types[4]).to eq('conflict_marker') expect(diff_line_types[4]).to eq('conflict_marker_our')
expect(diff_line_types[5..10]).to eq(['conflict_marker_our'] * 6) expect(diff_line_types[5..10]).to eq(['conflict_our'] * 6)
expect(diff_line_types[11]).to eq('conflict_marker') 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[12..17]).to eq(['conflict_their'] * 6)
expect(diff_line_types[18]).to eq('conflict_marker') expect(diff_line_types[18]).to eq('conflict_marker_their')
expect(diff_line_types[19..24]).to eq([nil] * 6) expect(diff_line_types[19..24]).to eq([nil] * 6)
expect(diff_line_types[25]).to eq('conflict_marker') expect(diff_line_types[25]).to eq('conflict_marker_our')
expect(diff_line_types[26..27]).to eq(['conflict_marker_our'] * 2) expect(diff_line_types[26..27]).to eq(['conflict_our'] * 2)
expect(diff_line_types[28]).to eq('conflict_marker') 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[29..30]).to eq(['conflict_their'] * 2)
expect(diff_line_types[31]).to eq('conflict_marker') 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 end
it 'does not add a match line to the end of the section' do it 'does not add a match line to the end of the section' do
...@@ -124,13 +132,13 @@ RSpec.describe Gitlab::Conflict::File do ...@@ -124,13 +132,13 @@ RSpec.describe Gitlab::Conflict::File do
expect(diff_line_types).to eq([ expect(diff_line_types).to eq([
'match', 'match',
nil, nil, nil, nil, nil, nil,
"conflict_marker",
"conflict_marker_our", "conflict_marker_our",
"conflict_our",
"conflict_marker", "conflict_marker",
"conflict_their",
"conflict_their",
"conflict_their",
"conflict_marker_their", "conflict_marker_their",
"conflict_marker_their",
"conflict_marker_their",
"conflict_marker",
nil, nil, nil, nil, nil, nil,
"match" "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