codekingpro/portable-devtools
115k
1const jsDiff = require('diff')
2
3const shouldPrintPatch = require('./should-print-patch.js')
4
5const colors = {
6 // red
7 removed: { open: '\x1B[31m', close: '\x1B[39m' },
8 // green
9 added: { open: '\x1B[32m', close: '\x1B[39m' },
10 // blue
11 header: { open: '\x1B[34m', close: '\x1B[39m' },
12 // cyan
13 section: { open: '\x1B[36m', close: '\x1B[39m' },
14}
15
16const color = (colorStr, colorId) => {
17 const { open, close } = colors[colorId]
18 // avoid highlighting the "\n" (would highlight till the end of the line)
19 return colorStr.replace(/[^\n\r]+/g, open + '$&' + close)
20}
21
22const formatDiff = async ({ files, opts = {}, refs, versions }) => {
23 let res = ''
24 const srcPrefix = opts.diffNoPrefix ? '' : opts.diffSrcPrefix || 'a/'
25 const dstPrefix = opts.diffNoPrefix ? '' : opts.diffDstPrefix || 'b/'
26
27 for (const filename of files.values()) {
28 const names = {
29 a: `${srcPrefix}${filename}`,
30 b: `${dstPrefix}${filename}`,
31 }
32
33 let fileMode = ''
34 const filenames = {
35 a: refs.get(`a/${filename}`),
36 b: refs.get(`b/${filename}`),
37 }
38 const contents = {
39 a: filenames.a && filenames.a.content,
40 b: filenames.b && filenames.b.content,
41 }
42 const modes = {
43 a: filenames.a && filenames.a.mode,
44 b: filenames.b && filenames.b.mode,
45 }
46
47 if (contents.a === contents.b && modes.a === modes.b) {
48 continue
49 }
50
51 if (opts.diffNameOnly) {
52 res += `${filename}\n`
53 continue
54 }
55
56 let patch = ''
57 let headerLength = 0
58 const header = str => {
59 headerLength++
60 patch += `${str}\n`
61 }
62
63 // manually build a git diff-compatible header
64 header(`diff --git ${names.a} ${names.b}`)
65 if (modes.a === modes.b) {
66 fileMode = filenames.a.mode
67 } else {
68 if (modes.a && !modes.b) {
69 header(`deleted file mode ${modes.a}`)
70 } else if (!modes.a && modes.b) {
71 header(`new file mode ${modes.b}`)
72 } else {
73 header(`old mode ${modes.a}`)
74 header(`new mode ${modes.b}`)
75 }
76 }
77 /* eslint-disable-next-line max-len */
78 header(`index ${opts.tagVersionPrefix || 'v'}${versions.a}..${opts.tagVersionPrefix || 'v'}${versions.b} ${fileMode}`)
79
80 if (await shouldPrintPatch(filename)) {
81 patch += jsDiff.createTwoFilesPatch(
82 names.a,
83 names.b,
84 contents.a || '',
85 contents.b || '',
86 '',
87 '',
88 {
89 context: opts.diffUnified === 0 ? 0 : opts.diffUnified || 3,
90 ignoreWhitespace: opts.diffIgnoreAllSpace,
91 }
92 ).replace(
93 '===================================================================\n',
94 ''
95 ).replace(/\t\n/g, '\n') // strip trailing tabs
96 headerLength += 2
97 } else {
98 header(`--- ${names.a}`)
99 header(`+++ ${names.b}`)
100 }
101
102 if (opts.color) {
103 // this RegExp will include all the `\n` chars into the lines, easier to join
104 const lines = patch.split(/^/m)
105 res += color(lines.slice(0, headerLength).join(''), 'header')
106 res += lines.slice(headerLength).join('')
107 .replace(/^-.*/gm, color('$&', 'removed'))
108 .replace(/^\+.*/gm, color('$&', 'added'))
109 .replace(/^@@.+@@/gm, color('$&', 'section'))
110 } else {
111 res += patch
112 }
113 }
114
115 return res.trim()
116}
117
118module.exports = formatDiff
119 