fork of go-gitdiff with jj support
1package gitdiff
2
3import (
4 "bytes"
5 "compress/zlib"
6 "fmt"
7 "io"
8 "strconv"
9)
10
11type formatter struct {
12 w io.Writer
13 err error
14}
15
16func newFormatter(w io.Writer) *formatter {
17 return &formatter{w: w}
18}
19
20func (fm *formatter) Write(p []byte) (int, error) {
21 if fm.err != nil {
22 return len(p), nil
23 }
24 if _, err := fm.w.Write(p); err != nil {
25 fm.err = err
26 }
27 return len(p), nil
28}
29
30func (fm *formatter) WriteString(s string) (int, error) {
31 fm.Write([]byte(s))
32 return len(s), nil
33}
34
35func (fm *formatter) WriteByte(c byte) error {
36 fm.Write([]byte{c})
37 return nil
38}
39
40func (fm *formatter) WriteQuotedName(s string) {
41 qpos := 0
42 for i := 0; i < len(s); i++ {
43 ch := s[i]
44 if q, quoted := quoteByte(ch); quoted {
45 if qpos == 0 {
46 fm.WriteByte('"')
47 }
48 fm.WriteString(s[qpos:i])
49 fm.Write(q)
50 qpos = i + 1
51 }
52 }
53 fm.WriteString(s[qpos:])
54 if qpos > 0 {
55 fm.WriteByte('"')
56 }
57}
58
59var quoteEscapeTable = map[byte]byte{
60 '\a': 'a',
61 '\b': 'b',
62 '\t': 't',
63 '\n': 'n',
64 '\v': 'v',
65 '\f': 'f',
66 '\r': 'r',
67 '"': '"',
68 '\\': '\\',
69}
70
71func quoteByte(b byte) ([]byte, bool) {
72 if q, ok := quoteEscapeTable[b]; ok {
73 return []byte{'\\', q}, true
74 }
75 if b < 0x20 || b >= 0x7F {
76 return []byte{
77 '\\',
78 '0' + (b>>6)&0o3,
79 '0' + (b>>3)&0o7,
80 '0' + (b>>0)&0o7,
81 }, true
82 }
83 return nil, false
84}
85
86func (fm *formatter) FormatFile(f *File) {
87 fm.WriteString("diff --git ")
88
89 var aName, bName string
90 switch {
91 case f.OldName == "":
92 aName = f.NewName
93 bName = f.NewName
94
95 case f.NewName == "":
96 aName = f.OldName
97 bName = f.OldName
98
99 default:
100 aName = f.OldName
101 bName = f.NewName
102 }
103
104 fm.WriteQuotedName("a/" + aName)
105 fm.WriteByte(' ')
106 fm.WriteQuotedName("b/" + bName)
107 fm.WriteByte('\n')
108
109 if f.OldMode != 0 {
110 if f.IsDelete {
111 fmt.Fprintf(fm, "deleted file mode %o\n", f.OldMode)
112 } else if f.NewMode != 0 {
113 fmt.Fprintf(fm, "old mode %o\n", f.OldMode)
114 }
115 }
116
117 if f.NewMode != 0 {
118 if f.IsNew {
119 fmt.Fprintf(fm, "new file mode %o\n", f.NewMode)
120 } else if f.OldMode != 0 {
121 fmt.Fprintf(fm, "new mode %o\n", f.NewMode)
122 }
123 }
124
125 if f.Score > 0 {
126 if f.IsCopy || f.IsRename {
127 fmt.Fprintf(fm, "similarity index %d%%\n", f.Score)
128 } else {
129 fmt.Fprintf(fm, "dissimilarity index %d%%\n", f.Score)
130 }
131 }
132
133 if f.IsCopy {
134 if f.OldName != "" {
135 fm.WriteString("copy from ")
136 fm.WriteQuotedName(f.OldName)
137 fm.WriteByte('\n')
138 }
139 if f.NewName != "" {
140 fm.WriteString("copy to ")
141 fm.WriteQuotedName(f.NewName)
142 fm.WriteByte('\n')
143 }
144 }
145
146 if f.IsRename {
147 if f.OldName != "" {
148 fm.WriteString("rename from ")
149 fm.WriteQuotedName(f.OldName)
150 fm.WriteByte('\n')
151 }
152 if f.NewName != "" {
153 fm.WriteString("rename to ")
154 fm.WriteQuotedName(f.NewName)
155 fm.WriteByte('\n')
156 }
157 }
158
159 if f.OldOIDPrefix != "" && f.NewOIDPrefix != "" {
160 fmt.Fprintf(fm, "index %s..%s", f.OldOIDPrefix, f.NewOIDPrefix)
161
162 // Mode is only included on the index line when it is not changing
163 if f.OldMode != 0 && ((f.NewMode == 0 && !f.IsDelete) || f.OldMode == f.NewMode) {
164 fmt.Fprintf(fm, " %o", f.OldMode)
165 }
166
167 fm.WriteByte('\n')
168 }
169
170 if f.IsBinary {
171 if f.BinaryFragment == nil {
172 fm.WriteString("Binary files ")
173 fm.WriteQuotedName("a/" + aName)
174 fm.WriteString(" and ")
175 fm.WriteQuotedName("b/" + bName)
176 fm.WriteString(" differ\n")
177 } else {
178 fm.WriteString("GIT binary patch\n")
179 fm.FormatBinaryFragment(f.BinaryFragment)
180 if f.ReverseBinaryFragment != nil {
181 fm.FormatBinaryFragment(f.ReverseBinaryFragment)
182 }
183 }
184 }
185
186 // The "---" and "+++" lines only appear for text patches with fragments
187 if len(f.TextFragments) > 0 {
188 fm.WriteString("--- ")
189 if f.OldName == "" {
190 fm.WriteString("/dev/null")
191 } else {
192 fm.WriteQuotedName("a/" + f.OldName)
193 }
194 fm.WriteByte('\n')
195
196 fm.WriteString("+++ ")
197 if f.NewName == "" {
198 fm.WriteString("/dev/null")
199 } else {
200 fm.WriteQuotedName("b/" + f.NewName)
201 }
202 fm.WriteByte('\n')
203
204 for _, frag := range f.TextFragments {
205 fm.FormatTextFragment(frag)
206 }
207 }
208}
209
210func (fm *formatter) FormatTextFragment(f *TextFragment) {
211 fm.FormatTextFragmentHeader(f)
212 fm.WriteByte('\n')
213
214 for _, line := range f.Lines {
215 fm.WriteString(line.Op.String())
216 fm.WriteString(line.Line)
217 if line.NoEOL() {
218 fm.WriteString("\n\\ No newline at end of file\n")
219 }
220 }
221}
222
223func (fm *formatter) FormatTextFragmentHeader(f *TextFragment) {
224 fmt.Fprintf(fm, "@@ -%d,%d +%d,%d @@", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines)
225 if f.Comment != "" {
226 fm.WriteByte(' ')
227 fm.WriteString(f.Comment)
228 }
229}
230
231func (fm *formatter) FormatBinaryFragment(f *BinaryFragment) {
232 const (
233 maxBytesPerLine = 52
234 )
235
236 switch f.Method {
237 case BinaryPatchDelta:
238 fm.WriteString("delta ")
239 case BinaryPatchLiteral:
240 fm.WriteString("literal ")
241 }
242 fm.Write(strconv.AppendInt(nil, f.Size, 10))
243 fm.WriteByte('\n')
244
245 data := deflateBinaryChunk(f.Data)
246 n := (len(data) / maxBytesPerLine) * maxBytesPerLine
247
248 buf := make([]byte, base85Len(maxBytesPerLine))
249 for i := 0; i < n; i += maxBytesPerLine {
250 base85Encode(buf, data[i:i+maxBytesPerLine])
251 fm.WriteByte('z')
252 fm.Write(buf)
253 fm.WriteByte('\n')
254 }
255 if remainder := len(data) - n; remainder > 0 {
256 buf = buf[0:base85Len(remainder)]
257
258 sizeChar := byte(remainder)
259 if remainder <= 26 {
260 sizeChar = 'A' + sizeChar - 1
261 } else {
262 sizeChar = 'a' + sizeChar - 27
263 }
264
265 base85Encode(buf, data[n:])
266 fm.WriteByte(sizeChar)
267 fm.Write(buf)
268 fm.WriteByte('\n')
269 }
270 fm.WriteByte('\n')
271}
272
273func deflateBinaryChunk(data []byte) []byte {
274 var b bytes.Buffer
275
276 zw := zlib.NewWriter(&b)
277 _, _ = zw.Write(data)
278 _ = zw.Close()
279
280 return b.Bytes()
281}