fork of go-gitdiff with jj support
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add incomplete git header parsing functions

I believe the structure is correct, but there are a lot of details to
fill in - translating the C string parsing logic into Go is not always
straightforward.

+144 -2
+17
gitdiff/gitdiff.go
··· 1 1 package gitdiff 2 2 3 + import ( 4 + "os" 5 + ) 6 + 3 7 // File describes changes to a single file. It can be either a text file or a 4 8 // binary file. 5 9 type File struct { 10 + OldName string 11 + NewName string 12 + 13 + IsNew bool 14 + IsDelete bool 15 + IsCopy bool 16 + IsRename bool 17 + 18 + OldMode os.FileMode 19 + NewMode os.FileMode 20 + 21 + Score int 22 + 6 23 Fragments []*Fragment 7 24 } 8 25
+127 -2
gitdiff/parser.go
··· 4 4 "bufio" 5 5 "fmt" 6 6 "io" 7 + "os" 7 8 "regexp" 8 9 "strconv" 9 10 "strings" ··· 45 46 const ( 46 47 fragmentHeaderPrefix = "@@ -" 47 48 48 - fileHeaderPrefix = "diff --git" 49 + fileHeaderPrefix = "diff --git " 49 50 oldFilePrefix = "--- " 50 51 newFilePrefix = "+++ " 51 52 ) ··· 127 128 } 128 129 129 130 func (p *parser) ParseGitFileHeader(f *File, header string) error { 130 - panic("unimplemented") 131 + // TODO(bkeyes): parse header line for filename 132 + // necessary to get the filename for mode changes or add/rm empty files 133 + 134 + for { 135 + line, err := p.PeekLine() 136 + if err != nil { 137 + return err 138 + } 139 + 140 + more, err := parseGitHeaderLine(f, line) 141 + if err != nil { 142 + return p.Errorf("header: %v", err) 143 + } 144 + if !more { 145 + break 146 + } 147 + p.Line() 148 + } 149 + 150 + return nil 131 151 } 132 152 133 153 func (p *parser) ParseTraditionalFileHeader(f *File, oldFile, newFile string) error { ··· 203 223 204 224 return nil 205 225 } 226 + 227 + func parseGitHeaderLine(f *File, line string) (more bool, err error) { 228 + match := func(s string) bool { 229 + if strings.HasPrefix(line, s) { 230 + // TODO(bkeyes): strip final line separator too 231 + line = line[len(s):] 232 + return true 233 + } 234 + return false 235 + } 236 + 237 + switch { 238 + case match(fragmentHeaderPrefix): 239 + // start of a fragment indicates the end of the header 240 + return false, nil 241 + 242 + case match(oldFilePrefix): 243 + 244 + case match(newFilePrefix): 245 + 246 + case match("old mode "): 247 + if f.OldMode, err = parseModeLine(line); err != nil { 248 + return false, err 249 + } 250 + 251 + case match("new mode "): 252 + if f.NewMode, err = parseModeLine(line); err != nil { 253 + return false, err 254 + } 255 + 256 + case match("deleted file mode "): 257 + // TODO(bkeyes): maybe set old name from default? 258 + f.IsDelete = true 259 + if f.OldMode, err = parseModeLine(line); err != nil { 260 + return false, err 261 + } 262 + 263 + case match("new file mode "): 264 + f.IsNew = true 265 + if f.NewMode, err = parseModeLine(line); err != nil { 266 + return false, err 267 + } 268 + 269 + case match("copy from "): 270 + f.IsCopy = true 271 + // TODO(bkeyes): set old name 272 + 273 + case match("copy to "): 274 + f.IsCopy = true 275 + // TODO(bkeyes): set new name 276 + 277 + case match("rename old "): 278 + f.IsRename = true 279 + // TODO(bkeyes): set old name 280 + 281 + case match("rename new "): 282 + f.IsRename = true 283 + // TODO(bkeyes): set new name 284 + 285 + case match("rename from "): 286 + f.IsRename = true 287 + // TODO(bkeyes): set old name 288 + 289 + case match("rename to "): 290 + f.IsRename = true 291 + // TODO(bkeyes): set new name 292 + 293 + case match("similarity index "): 294 + f.Score = parseScoreLine(line) 295 + 296 + case match("dissimilarity index "): 297 + f.Score = parseScoreLine(line) 298 + 299 + case match("index "): 300 + 301 + default: 302 + // unknown line also indicates the end of the header 303 + return false, nil 304 + } 305 + 306 + return true, nil 307 + } 308 + 309 + func parseModeLine(s string) (os.FileMode, error) { 310 + s = strings.TrimSuffix(s, "\n") 311 + 312 + mode, err := strconv.ParseInt(s, 8, 32) 313 + if err != nil { 314 + nerr := err.(*strconv.NumError) 315 + return os.FileMode(0), fmt.Errorf("invalid mode line: %v", nerr.Err) 316 + } 317 + 318 + return os.FileMode(mode), nil 319 + } 320 + 321 + func parseScoreLine(s string) int { 322 + s = strings.TrimSuffix(s, "\n") 323 + 324 + // gitdiff_similarity / gitdiff_dissimilarity ignore invalid scores 325 + score, _ := strconv.ParseInt(s, 10, 32) 326 + if score <= 100 { 327 + return int(score) 328 + } 329 + return 0 330 + }