summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/merge_conflict_data_provider.js.es6
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/merge_conflict_data_provider.js.es6')
-rw-r--r--app/assets/javascripts/merge_conflict_data_provider.js.es6341
1 files changed, 341 insertions, 0 deletions
diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6
new file mode 100644
index 00000000000..cd92df8ddc5
--- /dev/null
+++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6
@@ -0,0 +1,341 @@
+const HEAD_HEADER_TEXT = 'HEAD//our changes';
+const ORIGIN_HEADER_TEXT = 'origin//their changes';
+const HEAD_BUTTON_TITLE = 'Use ours';
+const ORIGIN_BUTTON_TITLE = 'Use theirs';
+
+
+class MergeConflictDataProvider {
+
+ getInitialData() {
+ const diffViewType = $.cookie('diff_view');
+
+ return {
+ isLoading : true,
+ hasError : false,
+ isParallel : diffViewType === 'parallel',
+ diffViewType : diffViewType,
+ isSubmitting : false,
+ conflictsData : {},
+ resolutionData : {}
+ }
+ }
+
+
+ decorateData(vueInstance, data) {
+ this.vueInstance = vueInstance;
+
+ if (data.type === 'error') {
+ vueInstance.hasError = true;
+ data.errorMessage = data.message;
+ }
+ else {
+ data.shortCommitSha = data.commit_sha.slice(0, 7);
+ data.commitMessage = data.commit_message;
+
+ this.setParallelLines(data);
+ this.setInlineLines(data);
+ this.updateResolutionsData(data);
+ }
+
+ vueInstance.conflictsData = data;
+ vueInstance.isSubmitting = false;
+
+ const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict';
+ vueInstance.conflictsData.conflictsText = conflictsText;
+ }
+
+
+ updateResolutionsData(data) {
+ const vi = this.vueInstance;
+
+ data.files.forEach( (file) => {
+ file.sections.forEach( (section) => {
+ if (section.conflict) {
+ vi.$set(`resolutionData['${section.id}']`, false);
+ }
+ });
+ });
+ }
+
+
+ setParallelLines(data) {
+ data.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;
+
+ if (conflict) {
+ linesObj.left.push(this.getOriginHeaderLine(id));
+ linesObj.right.push(this.getHeadHeaderLine(id));
+ }
+
+ lines.forEach( (line) => {
+ const { type } = line;
+
+ 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';
+
+ linesObj.left.push (this.getLineForParallelView(line, id, lineType));
+ linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
+ }
+ });
+
+ this.checkLineLengths(linesObj);
+ });
+
+ for (let i = 0, len = linesObj.left.length; i < len; i++) {
+ file.parallelLines.push([
+ linesObj.right[i],
+ linesObj.left[i]
+ ]);
+ }
+
+ });
+ }
+
+
+ checkLineLengths(linesObj) {
+ let { left, right } = linesObj;
+
+ if (left.length !== right.length) {
+ if (left.length > right.length) {
+ const diff = left.length - right.length;
+ for (let i = 0; i < diff; i++) {
+ right.push({ lineType: 'emptyLine', richText: '' });
+ }
+ }
+ else {
+ const diff = right.length - left.length;
+ for (let i = 0; i < diff; i++) {
+ left.push({ lineType: 'emptyLine', richText: '' });
+ }
+ }
+ }
+ }
+
+
+ setInlineLines(data) {
+ data.files.forEach( (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 (conflict) {
+ file.inlineLines.push(this.getHeadHeaderLine(id));
+ }
+
+ lines.forEach( (line) => {
+ const { type } = line;
+
+ 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));
+ }
+ });
+ });
+ }
+
+
+ handleSelected(sectionId, selection) {
+ const vi = this.vueInstance;
+
+ vi.resolutionData[sectionId] = selection;
+ vi.conflictsData.files.forEach( (file) => {
+ 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;
+
+ if (hasSameId && (isLeftMatch || isRightMatch)) {
+ this.markLine(left, selection);
+ this.markLine(right, selection);
+ }
+ })
+ });
+ }
+
+
+ updateViewType(newType) {
+ const vi = this.vueInstance;
+
+ if (newType === vi.diffView || !(newType === 'parallel' || newType === 'inline')) {
+ return;
+ }
+
+ vi.diffView = newType;
+ vi.isParallel = newType === 'parallel';
+ $.cookie('diff_view', newType); // TODO: Make sure that cookie path added.
+ $('.content-wrapper .container-fluid').toggleClass('container-limited');
+ }
+
+
+ markLine(line, selection) {
+ if (selection === 'head' && line.isHead) {
+ line.isSelected = true;
+ line.isUnselected = false;
+ }
+ else if (selection === 'origin' && line.isOrigin) {
+ line.isSelected = true;
+ line.isUnselected = false;
+ }
+ else {
+ line.isSelected = false;
+ line.isUnselected = true;
+ }
+ }
+
+
+ getConflictsCount() {
+ return Object.keys(this.vueInstance.resolutionData).length;
+ }
+
+
+ getResolvedCount() {
+ let count = 0;
+ const data = this.vueInstance.resolutionData;
+
+ for (const id in data) {
+ const resolution = data[id];
+ if (resolution) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+
+ isReadyToCommit() {
+ const { conflictsData, isSubmitting } = this.vueInstance
+ const allResolved = this.getConflictsCount() === this.getResolvedCount();
+ const hasCommitMessage = $.trim(conflictsData.commitMessage).length;
+
+ return !isSubmitting && hasCommitMessage && allResolved;
+ }
+
+
+ getCommitButtonText() {
+ const initial = 'Commit conflict resolution';
+ const inProgress = 'Committing...';
+ const vue = this.vueInstance;
+
+ return vue ? vue.isSubmitting ? inProgress : initial : initial;
+ }
+
+
+ decorateLineForInlineView(line, id, conflict) {
+ const { type } = line;
+ line.id = id;
+ line.hasConflict = conflict;
+ line.isHead = type === 'new';
+ line.isOrigin = type === 'old';
+ line.hasMatch = type === 'match';
+ line.richText = line.rich_text;
+ line.isSelected = false;
+ line.isUnselected = false;
+ }
+
+ getLineForParallelView(line, id, lineType, isHead) {
+ const { old_line, new_line, rich_text } = line;
+ const hasConflict = lineType === 'conflict';
+
+ return {
+ id,
+ lineType,
+ hasConflict,
+ isHead : hasConflict && isHead,
+ isOrigin : hasConflict && !isHead,
+ hasMatch : lineType === 'match',
+ lineNumber : isHead ? new_line : old_line,
+ section : isHead ? 'head' : 'origin',
+ richText : rich_text,
+ isSelected : false,
+ isUnselected : false
+ }
+ }
+
+
+ getHeadHeaderLine(id) {
+ return {
+ id : id,
+ richText : HEAD_HEADER_TEXT,
+ buttonTitle : HEAD_BUTTON_TITLE,
+ type : 'new',
+ section : 'head',
+ isHeader : true,
+ isHead : true,
+ isSelected : false,
+ isUnselected: false
+ }
+ }
+
+
+ getOriginHeaderLine(id) {
+ return {
+ id : id,
+ richText : ORIGIN_HEADER_TEXT,
+ buttonTitle : ORIGIN_BUTTON_TITLE,
+ type : 'old',
+ section : 'origin',
+ isHeader : true,
+ isOrigin : true,
+ isSelected : false,
+ isUnselected: false
+ }
+ }
+
+
+ handleFailedRequest(vueInstance, data) {
+ vueInstance.hasError = true;
+ vueInstance.conflictsData.errorMessage = 'Something went wrong!';
+ }
+
+
+ getCommitData() {
+ return {
+ commit_message: this.vueInstance.conflictsData.commitMessage,
+ sections: this.vueInstance.resolutionData
+ }
+ }
+
+
+ getFilePath(file) {
+ const { old_path, new_path } = file;
+ return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
+ }
+
+}