[WIP] implement some functionality for commit language graph

bfcbfe59d9a93e837abcbfa2f22f330c1c2dba55

Tucker McKnight <tmcknight@instructure.com> | Sun Aug 02 2026

[WIP] implement some functionality for commit language graph

Adds "added" and "removed" fields to a commit, which has the number
of lines added and deleted in that commit hunk. This is different
from the length of the beforeText and afterText fields, since those
have newlines added to them to balance them out in the side-by-side
view. So the beforeText and afterText always have the same number
of lines in them. added and removed says what the original diff
sizes were.

This is still a WIP and I need to figure out how to have shared
language colors between this and the repo home graph, as well
as implement the "other" length. And also do something for the
average size of a commit -- a moving standard deviation?
src/dataTypes.ts:18
Before
17
18
19


20
21
    lineNumber: number,
    beforeText: string,
    afterText: string,
⁣
⁣
  }>
}
After
17
18
19
20
21
22
23
    lineNumber: number,
    beforeText: string,
    afterText: string,
    added: number,
    removed: number,
  }>
}
src/helpers.ts:51
Before
50
51
52

53
54
55
56
57
58


59
        if (str.startsWith(' ')) { return str.replace(" ", "") }
      }).join("\n")

⁣
      const changeObject = (lastHunkBefore.length > 1000 || lastHunkAfter.length > 1000)
        ? []
        : Diff.diffWordsWithSpace(lastHunkBefore, lastHunkAfter)
      let beforeText = ""
      let afterText = ""

⁣
⁣
      changeObject.forEach((obj) => {
        const numNewLines = obj.value.split('').filter(char => char === '\n').length
After
50
51
52
53
54
55
56
57
58
59
60
61
62
        if (str.startsWith(' ')) { return str.replace(" ", "") }
      }).join("\n")

      // TODO: don't split on \n again here, do this earlier? More efficiently?
      const changeObject = (lastHunkBefore.split('\n').length > 1000 || lastHunkAfter.split('\n').length > 1000)
        ? []
        : Diff.diffWordsWithSpace(lastHunkBefore, lastHunkAfter)
      let beforeText = ""
      let afterText = ""
      let added = 0
      let removed = 0

      changeObject.forEach((obj) => {
        const numNewLines = obj.value.split('').filter(char => char === '\n').length
src/helpers.ts:63
Before
62
63
64


65
66
67

68
69
        if (!obj.added && !obj.removed) {
          beforeText = beforeText + escape(obj.value)
          afterText = afterText + escape(obj.value)
⁣
⁣
        }
        if (obj.added) {
          afterText = afterText + "<mark>" + escape(obj.value) + "</mark>"
⁣
          if (numNewLines > 0) {
            let insertAt = beforeText.lastIndexOf("\n")
            insertAt = insertAt === -1 ? 0 : insertAt
After
62
63
64
65
66
67
68
69
70
71
72
        if (!obj.added && !obj.removed) {
          beforeText = beforeText + escape(obj.value)
          afterText = afterText + escape(obj.value)
          added += obj.value.split('\n').length
          removed += obj.value.split('\n').length
        }
        if (obj.added) {
          afterText = afterText + "<mark>" + escape(obj.value) + "</mark>"
          added += obj.value.split('\n').length
          if (numNewLines > 0) {
            let insertAt = beforeText.lastIndexOf("\n")
            insertAt = insertAt === -1 ? 0 : insertAt
src/helpers.ts:75
Before
74
75
76

77
78
        }
        if (obj.removed) {
          beforeText = beforeText + "<mark>" + escape(obj.value) + "</mark>"
⁣
          if (numNewLines > 0) {
            let insertAt = afterText.lastIndexOf("\n")
            insertAt = insertAt === -1 ? 0 : insertAt
After
74
75
76
77
78
79
        }
        if (obj.removed) {
          beforeText = beforeText + "<mark>" + escape(obj.value) + "</mark>"
          removed += obj.value.split('\n').length
          if (numNewLines > 0) {
            let insertAt = afterText.lastIndexOf("\n")
            insertAt = insertAt === -1 ? 0 : insertAt
src/helpers.ts:89
Before
88
89
90


91
92
        lineNumber: parseInt(lines[previousHunk].match(lineNumberRegex)[1]),
        beforeText,
        afterText,
⁣
⁣
      })
      previousFilename = ''
      previousHunk = index
After
88
89
90
91
92
93
94
        lineNumber: parseInt(lines[previousHunk].match(lineNumberRegex)[1]),
        beforeText,
        afterText,
        added,
        removed,
      })
      previousFilename = ''
      previousHunk = index