Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
197ac5eb
Commit
197ac5eb
authored
Sep 29, 2016
by
Alfredo Sumaran
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Ability to resolve conflicts for files with `text-editor` as conflict type
parent
a8ac9089
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
213 additions
and
159 deletions
+213
-159
app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
...cripts/merge_conflicts/components/diff_file_editor.js.es6
+24
-19
app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
...s/javascripts/merge_conflicts/merge_conflict_store.js.es6
+126
-114
app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml
...ects/merge_requests/conflicts/_diff_file_editor.html.haml
+2
-4
app/views/projects/merge_requests/conflicts/_file_actions.html.haml
...projects/merge_requests/conflicts/_file_actions.html.haml
+1
-1
app/views/projects/merge_requests/conflicts/_inline_view.html.haml
.../projects/merge_requests/conflicts/_inline_view.html.haml
+22
-19
app/views/projects/merge_requests/conflicts/_parallel_view.html.haml
...rojects/merge_requests/conflicts/_parallel_view.html.haml
+1
-1
spec/features/merge_requests/conflicts_spec.rb
spec/features/merge_requests/conflicts_spec.rb
+37
-1
No files found.
app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
View file @
197ac5eb
...
...
@@ -7,10 +7,10 @@
template: '#diff-file-editor',
data() {
return {
originalContent: '',
saved: false,
loading: false,
fileLoaded: false
fileLoaded: false,
originalContent: '',
}
},
computed: {
...
...
@@ -23,43 +23,48 @@
}
},
watch: {
loadFile(val) {
const self = this;
['file.showEditor'](val) {
this.resetEditorContent();
if (!val || this.fileLoaded || this.loading) {
return
return
;
}
this.loadEditor();
}
},
ready() {
if (this.file.loadEditor) {
this.loadEditor();
}
},
methods: {
loadEditor() {
this.loading = true;
$.get(this.file.content_path)
.done((file) => {
let content = self.$el.querySelector('pre');
let content = this.$el.querySelector('pre');
let fileContent = document.createTextNode(file.content);
content.textContent = fileContent.textContent;
self
.originalContent = file.content;
self
.fileLoaded = true;
self
.editor = ace.edit(content);
self
.editor.$blockScrolling = Infinity; // Turn off annoying warning
self
.editor.on('change', () => {
self
.saveDiffResolution();
this
.originalContent = file.content;
this
.fileLoaded = true;
this
.editor = ace.edit(content);
this
.editor.$blockScrolling = Infinity; // Turn off annoying warning
this
.editor.on('change', () => {
this
.saveDiffResolution();
});
self
.saveDiffResolution();
this
.saveDiffResolution();
})
.fail(() => {
console.log('error');
})
.always(() => {
self
.loading = false;
this
.loading = false;
});
}
},
methods: {
},
saveDiffResolution() {
this.saved = true;
...
...
app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
View file @
197ac5eb
...
...
@@ -13,6 +13,10 @@
INLINE: 'inline',
PARALLEL: 'parallel'
};
const CONFLICT_TYPES = {
TEXT: 'text',
TEXT_EDITOR: 'text-editor'
};
global.mergeConflicts.mergeConflictsStore = {
state: {
...
...
@@ -26,8 +30,6 @@
setConflictsData(data) {
this.decorateFiles(data.files);
this.setInlineLines(data.files);
this.setParallelLines(data.files);
this.state.conflictsData = {
files: data.files,
...
...
@@ -45,90 +47,90 @@
file.resolutionData = {};
file.promptDiscardConfirmation = false;
file.resolveMode = DEFAULT_RESOLVE_MODE;
});
},
setInlineLines(files) {
files.forEach((file) => {
file.filePath = this.getFilePath(file);
file.iconClass = `fa-${file.blob_icon}`;
file.blobPath = file.blob_path;
file.filePath = this.getFilePath(file);
file.inlineLines = [];
file.sections.forEach((section) =>
{
let currentLineType = 'new'
;
const { conflict, lines, id } = section
;
if (file.type === CONFLICT_TYPES.TEXT)
{
file.showEditor = false
;
file.loadEditor = false
;
if (conflict) {
file.inlineLines.push(this.getHeadHeaderLine(id));
}
this.setInlineLine(file);
this.setParallelLine(file);
} else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
file.showEditor = true;
file.loadEditor = true;
}
});
},
lines.forEach((line) =>
{
const { type } = line
;
setInlineLine(file)
{
file.inlineLines = []
;
if ((type === 'new' || type === 'old') && currentLineType !== type) {
currentLineType = type;
file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
}
file.sections.forEach((section) => {
let currentLineType = 'new';
const { conflict, lines, id } = section;
if (conflict) {
file.inlineLines.push(this.getHeadHeaderLine(id));
}
this.decorateLineForInlineView(line, id, conflict);
file.inlineLines.push(line);
})
lines.forEach((line) => {
const { type } = line;
if (conflict) {
file.inlineLines.push(this.getOriginHeaderLine(id));
if ((type === 'new' || type === 'old') && currentLineType !== type) {
currentLineType = type;
file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
}
});
this.decorateLineForInlineView(line, id, conflict);
file.inlineLines.push(line);
})
if (conflict) {
file.inlineLines.push(this.getOriginHeaderLine(id));
}
});
},
setParallelLines(files) {
files.forEach((file) => {
file.filePath = this.getFilePath(file);
file.iconClass = `fa-${file.blob_icon}`;
file.blobPath = file.blob_path;
file.parallelLines = [];
const linesObj = { left: [], right: [] };
file.sections.forEach((section) => {
const { conflict, lines, id } = section;
setParallelLine(file) {
file.parallelLines = [];
const linesObj = { left: [], right: [] };
if (conflict) {
linesObj.left.push(this.getOriginHeaderLine(id));
linesObj.right.push(this.getHeadHeaderLine(id));
}
file.sections.forEach((section) => {
const { conflict, lines, id } = section;
lines.forEach((line) => {
const { type } = line;
if (conflict) {
linesObj.left.push(this.getOriginHeaderLine(id));
linesObj.right.push(this.getHeadHeaderLine(id));
}
if (conflict) {
if (type === 'old') {
linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
}
else if (type === 'new') {
linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
}
}
else {
const lineType = type || 'context';
lines.forEach((line) => {
const { type } = line;
linesObj.left.push (this.getLineForParallelView(line, id, lineType));
linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
if (conflict) {
if (type === 'old') {
linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
} else if (type === 'new') {
linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
}
});
} else {
const lineType = type || 'context';
this.checkLineLengths(linesObj);
linesObj.left.push (this.getLineForParallelView(line, id, lineType));
linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
}
});
for (let i = 0, len = linesObj.left.length; i < len; i++) {
file.parallelLines.push([
linesObj.right[i],
linesObj.left[i]
]);
}
return file;
this.checkLineLengths(linesObj);
});
for (let i = 0, len = linesObj.left.length; i < len; i++) {
file.parallelLines.push([
linesObj.right[i],
linesObj.left[i]
]);
}
},
setLoadingState(state) {
...
...
@@ -140,13 +142,12 @@
},
setFailedRequest(message) {
console.log('setFailedRequest');
this.state.hasError = true;
this.state.conflictsData.errorMessage = message;
},
getConflictsCount() {
if (!this.state.conflictsData.files) {
if (!this.state.conflictsData.files
.length
) {
return 0;
}
...
...
@@ -154,11 +155,15 @@
let count = 0;
files.forEach((file) => {
file.sections.forEach((section) => {
if (section.conflict) {
count++;
}
});
if (file.type === CONFLICT_TYPES.TEXT) {
file.sections.forEach((section) => {
if (section.conflict) {
count++;
}
});
} else {
count++;
}
});
return count;
...
...
@@ -172,7 +177,7 @@
},
setViewType(viewType) {
this.state.diffView
= viewType;
this.state.diffView = viewType;
this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;
$.cookie('diff_view', viewType, {
...
...
@@ -253,8 +258,7 @@
for (let i = 0; i < diff; i++) {
right.push({ lineType: 'emptyLine', richText: '' });
}
}
else {
} else {
const diff = right.length - left.length;
for (let i = 0; i < diff; i++) {
left.push({ lineType: 'emptyLine', richText: '' });
...
...
@@ -268,8 +272,12 @@
},
setFileResolveMode(file, mode) {
// Restore Interactive mode when switching to Edit mode
if (mode === EDIT_RESOLVE_MODE) {
if (mode === INTERACTIVE_RESOLVE_MODE) {
file.showEditor = false;
} else if (mode === EDIT_RESOLVE_MODE) {
// Restore Interactive mode when switching to Edit mode
file.showEditor = true;
file.loadEditor = true;
file.resolutionData = {};
this.restoreFileLinesState(file);
...
...
@@ -287,9 +295,9 @@
});
file.parallelLines.forEach((lines) => {
const left
= lines[0];
const right
= lines[1];
const isLeftMatch
= left.hasConflict || left.isHeader;
const left = lines[0];
const right = lines[1];
const isLeftMatch = left.hasConflict || left.isHeader;
const isRightMatch = right.hasConflict || right.isHeader;
if (isLeftMatch || isRightMatch) {
...
...
@@ -313,14 +321,17 @@
let numberConflicts = 0;
let resolvedConflicts = Object.keys(file.resolutionData).length
for (let j = 0, k = file.sections.length; j < k; j++) {
if (file.sections[j].conflict) {
numberConflicts++;
// We only check if
if (file.type === CONFLICT_TYPES.TEXT) {
for (let j = 0, k = file.sections.length; j < k; j++) {
if (file.sections[j].conflict) {
numberConflicts++;
}
}
}
if (resolvedConflicts !== numberConflicts) {
unresolved++;
if (resolvedConflicts !== numberConflicts) {
unresolved++;
}
}
} else if (file.resolveMode === EDIT_RESOLVE_MODE) {
// Unlikely to happen since switching to Edit mode saves content automatically.
...
...
@@ -358,10 +369,15 @@
new_path: file.new_path
};
// Submit only one data for type of editing
if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
addFile.sections = file.resolutionData;
} else if (file.resolveMode === EDIT_RESOLVE_MODE) {
if (file.type === CONFLICT_TYPES.TEXT) {
// Submit only one data for type of editing
if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
addFile.sections = file.resolutionData;
} else if (file.resolveMode === EDIT_RESOLVE_MODE) {
addFile.content = file.content;
}
} else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
addFile.content = file.content;
}
...
...
@@ -374,39 +390,35 @@
handleSelected(file, sectionId, selection) {
Vue.set(file.resolutionData, sectionId, selection);
this.state.conflictsData.files.forEach((file) => {
file.inlineLines.forEach((line) => {
if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
this.markLine(line, selection);
}
});
file.inlineLines.forEach((line) => {
if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
this.markLine(line, selection);
}
});
file.parallelLines.forEach((lines) => {
const left
= lines[0];
const right
= lines[1];
const hasSameId
= right.id === sectionId || left.id === sectionId;
const isLeftMatch
= left.hasConflict || left.isHeader;
const isRightMatch = right.hasConflict || right.isHeader;
file.parallelLines.forEach((lines) => {
const left
= lines[0];
const right
= lines[1];
const hasSameId
= right.id === sectionId || left.id === sectionId;
const isLeftMatch
= left.hasConflict || left.isHeader;
const isRightMatch = right.hasConflict || right.isHeader;
if (hasSameId && (isLeftMatch || isRightMatch)) {
this.markLine(left, selection);
this.markLine(right, selection);
}
})
if (hasSameId && (isLeftMatch || isRightMatch)) {
this.markLine(left, selection);
this.markLine(right, selection);
}
});
},
markLine(line, selection) {
if (selection === 'head' && line.isHead) {
line.isSelected
= true;
line.isSelected = true;
line.isUnselected = false;
}
else if (selection === 'origin' && line.isOrigin) {
line.isSelected = true;
} else if (selection === 'origin' && line.isOrigin) {
line.isSelected = true;
line.isUnselected = false;
}
else {
line.isSelected = false;
} else {
line.isSelected = false;
line.isUnselected = true;
}
},
...
...
app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml
View file @
197ac5eb
-
if_condition
=
local_assigns
.
fetch
(
:if_condition
,
''
)
.diff-editor-wrap
{
"v-show"
=>
if_condition
}
.diff-editor-wrap
{
"v-show"
=>
"file.showEditor"
}
.discard-changes-alert-wrap
{
"v-if"
=>
"file.promptDiscardConfirmation"
}
.discard-changes-alert
Are you sure to discard your changes?
.discard-actions
%button
.btn.btn-sm.btn-close
{
"@click"
=>
"acceptDiscardConfirmation(file)"
}
Discard changes
%button
.btn.btn-sm
{
"@click"
=>
"cancelDiscardConfirmation(file)"
}
Cancel
%diff-file-editor
{
":file"
=>
"file"
,
":load-file"
=>
if_condition
}
%diff-file-editor
{
":file"
=>
"file"
}
app/views/projects/merge_requests/conflicts/_file_actions.html.haml
View file @
197ac5eb
.file-actions
.btn-group
.btn-group
{
"v-if"
=>
"file.type === 'text'"
}
%button
.btn
{
":class"
=>
"{ 'active': file.resolveMode == 'interactive' }"
,
'@click'
=>
"onClickResolveModeButton(file, 'interactive')"
,
type:
'button'
}
...
...
app/views/projects/merge_requests/conflicts/_inline_view.html.haml
View file @
197ac5eb
...
...
@@ -4,23 +4,26 @@
%i
.fa.fa-fw
{
":class"
=>
"file.iconClass"
}
%strong
{{file.filePath}}
=
render
partial:
'projects/merge_requests/conflicts/file_actions'
.diff-content.diff-wrap-lines
.diff-wrap-lines.code.file-content.js-syntax-highlight
{
'v-show'
=>
"file.resolveMode === 'interactive'"
}
%table
%tr
.line_holder.diff-inline
{
"v-for"
=>
"line in file.inlineLines"
}
%template
{
"v-if"
=>
"!line.isHeader"
}
%td
.diff-line-num.new_line
{
":class"
=>
class_bindings
}
%a
{{line.new_line}}
%td
.diff-line-num.old_line
{
":class"
=>
class_bindings
}
%a
{{line.old_line}}
%td
.line_content
{
":class"
=>
class_bindings
}
{{{line.richText}}}
%template
{
"v-if"
=>
"file.type === 'text'"
}
.diff-content.diff-wrap-lines
.diff-wrap-lines.code.file-content.js-syntax-highlight
{
'v-show'
=>
"file.resolveMode === 'interactive'"
}
%table
%tr
.line_holder.diff-inline
{
"v-for"
=>
"line in file.inlineLines"
}
%template
{
"v-if"
=>
"!line.isHeader"
}
%td
.diff-line-num.new_line
{
":class"
=>
class_bindings
}
%a
{{line.new_line}}
%td
.diff-line-num.old_line
{
":class"
=>
class_bindings
}
%a
{{line.old_line}}
%td
.line_content
{
":class"
=>
class_bindings
}
{{{line.richText}}}
%template
{
"v-if"
=>
"line.isHeader"
}
%td
.diff-line-num.header
{
":class"
=>
class_bindings
}
%td
.diff-line-num.header
{
":class"
=>
class_bindings
}
%td
.line_content.header
{
":class"
=>
class_bindings
}
%strong
{{{line.richText}}}
%button
.btn
{
"@click"
=>
"handleSelected(file, line.id, line.section)"
}
{{line.buttonTitle}}
=
render
partial:
'projects/merge_requests/conflicts/diff_file_editor'
,
locals:
{
if_condition:
"file.resolveMode === 'edit' && !isParallel"
}
%template
{
"v-if"
=>
"line.isHeader"
}
%td
.diff-line-num.header
{
":class"
=>
class_bindings
}
%td
.diff-line-num.header
{
":class"
=>
class_bindings
}
%td
.line_content.header
{
":class"
=>
class_bindings
}
%strong
{{{line.richText}}}
%button
.btn
{
"@click"
=>
"handleSelected(file, line.id, line.section)"
}
{{line.buttonTitle}}
=
render
partial:
'projects/merge_requests/conflicts/diff_file_editor'
%template
{
"v-else"
=>
true
}
=
render
partial:
'projects/merge_requests/conflicts/diff_file_editor'
app/views/projects/merge_requests/conflicts/_parallel_view.html.haml
View file @
197ac5eb
...
...
@@ -22,4 +22,4 @@
{{line.lineNumber}}
%td
.line_content.parallel
{
":class"
=>
class_bindings
}
{{{line.richText}}}
=
render
partial:
'projects/merge_requests/conflicts/diff_file_editor'
,
locals:
{
if_condition:
"file.
resolveMode === 'edit'
&& isParallel"
}
=
render
partial:
'projects/merge_requests/conflicts/diff_file_editor'
,
locals:
{
if_condition:
"file.
loadFile
&& isParallel"
}
spec/features/merge_requests/conflicts_spec.rb
View file @
197ac5eb
...
...
@@ -37,7 +37,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
end
end
context
'when in inline mode'
do
context
'when in inline mode'
do
it
'resolves files manually'
do
within
find
(
'.files-wrapper .diff-file.inline-view'
,
text:
'files/ruby/popen.rb'
)
do
click_button
'Edit inline'
...
...
@@ -66,6 +66,42 @@ feature 'Merge request conflict resolution', js: true, feature: true do
end
end
context
'when a merge request can be resolved in the UI'
do
let
(
:merge_request
)
{
create_merge_request
(
'conflict-contains-conflict-markers'
)
}
before
do
project
.
team
<<
[
user
,
:developer
]
login_as
(
user
)
visit
namespace_project_merge_request_path
(
project
.
namespace
,
project
,
merge_request
)
end
context
'a conflict contain markers'
do
before
{
click_link
(
'conflicts'
,
href:
/\/conflicts\Z/
)
}
it
'resolves files manually'
do
within
find
(
'.files-wrapper .diff-file.inline-view'
,
text:
'files/markdown/ruby-style-guide.md'
)
do
wait_for_ajax
execute_script
(
'ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("Gregor Samsa woke from troubled dreams");'
)
end
click_button
'Commit conflict resolution'
wait_for_ajax
expect
(
page
).
to
have_content
(
'All merge conflicts were resolved'
)
merge_request
.
reload_diff
click_on
'Changes'
wait_for_ajax
find
(
'.nothing-here-block'
,
visible:
true
).
click
wait_for_ajax
expect
(
page
).
to
have_content
(
'Gregor Samsa woke from troubled dreams'
)
end
end
end
UNRESOLVABLE_CONFLICTS
=
{
'conflict-too-large'
=>
'when the conflicts contain a large file'
,
'conflict-binary-file'
=>
'when the conflicts contain a binary file'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment