summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/ide/lib/create_diff.js
blob: 3e915afdbcb2d25d8c741cabf4c6300499460c8b (plain)
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
import { commitActionForFile } from '~/ide/stores/utils';
import { commitActionTypes } from '~/ide/constants';
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;