123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- // From https://github.com/sergeyt/parse-diff
- module.exports = function (input) {
- if (!input) { return [] }
- if (input.match(/^\s+$/)) { return [] }
- const lines = input.split('\n')
- if (lines.length === 0) { return [] }
- const files = []
- let file = null
- let lnDel = 0
- let lnAdd = 0
- let current = null
- const start = function (line) {
- file = {
- chunks: [],
- deletions: 0,
- additions: 0
- }
- files.push(file)
- if (!file.to && !file.from) {
- const fileNames = parseFile(line)
- if (fileNames) {
- file.from = fileNames[0]
- file.to = fileNames[1]
- }
- }
- }
- const restart = function () {
- if (!file || file.chunks.length) { return start() }
- }
- const newFile = function () {
- restart()
- file.new = true
- file.from = '/dev/null'
- }
- const deletedFile = function () {
- restart()
- file.deleted = true
- file.to = '/dev/null'
- }
- const index = function (line) {
- restart()
- file.index = line.split(' ').slice(1)
- }
- const fromFile = function (line) {
- restart()
- file.from = parseFileFallback(line)
- }
- const toFile = function (line) {
- restart()
- file.to = parseFileFallback(line)
- }
- const binary = function (line) {
- file.binary = true
- }
- const chunk = function (line, match) {
- let newStart, oldStart
- lnDel = (oldStart = +match[1])
- const oldLines = +(match[2] || 0)
- lnAdd = (newStart = +match[3])
- const newLines = +(match[4] || 0)
- current = {
- content: line,
- changes: [],
- oldStart,
- oldLines,
- newStart,
- newLines
- }
- file.chunks.push(current)
- }
- const del = function (line) {
- if (!current) return
- current.changes.push({ type: 'del', del: true, ln: lnDel++, content: line })
- file.deletions++
- }
- const add = function (line) {
- if (!current) return
- current.changes.push({ type: 'add', add: true, ln: lnAdd++, content: line })
- file.additions++
- }
- const normal = function (line) {
- if (!current) return
- current.changes.push({
- type: 'normal',
- normal: true,
- ln1: lnDel++,
- ln2: lnAdd++,
- content: line
- })
- }
- const eof = function (line) {
- const recentChange = current.changes[current.changes.length - 1]
- return current.changes.push({
- type: recentChange.type,
- [recentChange.type]: true,
- ln1: recentChange.ln1,
- ln2: recentChange.ln2,
- ln: recentChange.ln,
- content: line
- })
- }
- const schema = [
- // todo beter regexp to avoid detect normal line starting with diff
- [/^\s+/, normal],
- [/^diff\s/, start],
- [/^new file mode \d+$/, newFile],
- [/^deleted file mode \d+$/, deletedFile],
- [/^Binary files/, binary],
- [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index],
- [/^---\s/, fromFile],
- [/^\+\+\+\s/, toFile],
- [/^@@\s+-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk],
- [/^-/, del],
- [/^\+/, add],
- [/^\\ No newline at end of file$/, eof]
- ]
- const parse = function (line) {
- for (const p of schema) {
- const m = line.match(p[0])
- if (m) {
- p[1](line, m)
- return true
- }
- }
- return false
- }
- for (const line of lines) {
- parse(line)
- }
- return files
- }
- function parseFile (s) {
- if (!s) return
- const result = /\sa\/(.*)\sb\/(.*)/.exec(s)
- return [result[1], result[2]]
- }
- // fallback function to overwrite file.from and file.to if executed
- function parseFileFallback (s) {
- s = ltrim(s, '-')
- s = ltrim(s, '+')
- s = s.trim()
- // ignore possible time stamp
- const t = (/\t.*|\d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d(.\d+)?\s(\+|-)\d\d\d\d/).exec(s)
- if (t) { s = s.substring(0, t.index).trim() }
- // ignore git prefixes a/ or b/
- if (s.match((/^(a|b)\//))) { return s.substr(2) } else { return s }
- }
- function ltrim (s, chars) {
- s = makeString(s)
- if (!chars && trimLeft) { return trimLeft.call(s) }
- chars = defaultToWhiteSpace(chars)
- return s.replace(new RegExp(`^${chars}+`), '')
- }
- const makeString = s => s === null ? '' : s + ''
- const { trimLeft } = String.prototype
- function defaultToWhiteSpace (chars) {
- if (chars === null) { return '\\s' }
- if (chars.source) { return chars.source }
- return `[${escapeRegExp(chars)}]`
- }
- const escapeRegExp = s => makeString(s).replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1')
|