BigW Consortium Gitlab

merge_conflict_data_provider.js.es6 8.65 KB
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}`;
  }

}