All files / app/assets/javascripts/ide/lib create_diff.js

100% Statements 38/38
95.45% Branches 21/22
100% Functions 13/13
100% Lines 38/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88        4x 3x   3x 2x     1x     4x   10x 23x 23x 23x     23x 1x   22x     23x         10x 18x   3x   3x 1x     2x   1x     1x         10x 17x     2x 1x     1x 2x       10x     4x 10x   10x 19x 10x   10x 19x 9x     10x              
import { commitActionTypes } from '~/ide/constants';
import { commitActionForFile } from '~/ide/stores/utils';
import createFileDiff from './create_file_diff';
 
const getDeletedParents = (entries, file) => {
  const parent = file.parentPath && entries[file.parentPath];
 
  if (parent && parent.deleted) {
    return [parent, ...getDeletedParents(entries, parent)];
  }
 
  return [];
};
 
const filesWithChanges = ({ stagedFiles = [], changedFiles = [], entries = {} }) => {
  // We need changed files to overwrite staged, so put them at the end.
  const changes = stagedFiles.concat(changedFiles).reduce((acc, file) => {
    const key = file.path;
    const action = commitActionForFile(file);
    const prev = acc[key];
 
    // If a file was deleted, which was previously added, then we should do nothing.
    if (action === commitActionTypes.delete && prev && prev.action === commitActionTypes.create) {
      delete acc[key];
    } else {
      acc[key] = { action, file };
    }
 
    return acc;
  }, {});
 
  // We need to clean "move" actions, because we can only support 100% similarity moves at the moment.
  // This is because the previous file's content might not be loaded.
  Object.values(changes)
    .filter((change) => change.action === commitActionTypes.move)
    .forEach((change) => {
      const prev = changes[change.file.prevPath];
 
      if (!prev) {
        return;
      }
 
      if (change.file.content === prev.file.content) {
        // If content is the same, continue with the move but don't do the prevPath's delete.
        delete changes[change.file.prevPath];
      } else {
        // Otherwise, treat the move as a delete / create.
        Object.assign(change, { action: commitActionTypes.create });
      }
    });
 
  // Next, we need to add deleted directories by looking at the parents
  Object.values(changes)
    .filter((change) => change.action === commitActionTypes.delete && change.file.parentPath)
    .forEach(({ file }) => {
      // Do nothing if we've already visited this directory.
      if (changes[file.parentPath]) {
        return;
      }
 
      getDeletedParents(entries, file).forEach((parent) => {
        changes[parent.path] = { action: commitActionTypes.delete, file: parent };
      });
    });
 
  return Object.values(changes);
};
 
const createDiff = (state) => {
  const changes = filesWithChanges(state);
 
  const toDelete = changes
    .filter((x) => x.action === commitActionTypes.delete)
    .map((x) => x.file.path);
 
  const patch = changes
    .filter((x) => x.action !== commitActionTypes.delete)
    .map(({ file, action }) => createFileDiff(file, action))
    .join('');
 
  return {
    patch,
    toDelete,
  };
};
 
export default createDiff;