fork of go-gitdiff with jj support
at v0.8.1 5.6 kB view raw
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}