@@ -2,6 +2,9 @@ package gitdiff
22
33import (
44 "bufio"
5+ "bytes"
6+ "compress/flate"
7+ "encoding/ascii85"
58 "fmt"
69 "io"
710 "strconv"
@@ -324,10 +327,25 @@ func (p *parser) ParseBinaryFragments(f *File) (n int, err error) {
324327
325328 f .IsBinary = true
326329 if hasData {
327- panic ("TODO(bkeyes): unimplemented" )
330+ forward , err := p .ParseBinaryFragment ()
331+ if err != nil {
332+ return n , err
333+ }
334+ if forward == nil {
335+ return n , p .Errorf (0 , "missing data for binary patch" )
336+ }
337+ f .BinaryFragment = forward
338+ n ++
339+
340+ // valid for reverse to not exist, but it must be valid if present
341+ reverse , err := p .ParseBinaryFragment ()
342+ if err != nil {
343+ return n , err
344+ }
345+ f .ReverseBinaryFragment = reverse
328346 }
329347
330- return 0 , nil
348+ return n , nil
331349}
332350
333351func (p * parser ) ParseBinaryMarker () (isBinary bool , hasData bool , err error ) {
@@ -346,6 +364,109 @@ func (p *parser) ParseBinaryMarker() (isBinary bool, hasData bool, err error) {
346364 return true , hasData , nil
347365}
348366
367+ func (p * parser ) ParseBinaryFragment () (* BinaryFragment , error ) {
368+ // TODO(bkeyes): split this function into small parts
369+ // TODO(bkeyes): add summary of data format so this is less mysterious
370+ const (
371+ shortestValidLine = "A00000\n "
372+ maxBytesPerLine = 52
373+ )
374+
375+ parts := strings .SplitN (p .Line (0 ), " " , 2 )
376+ if len (parts ) < 2 {
377+ return nil , nil
378+ }
379+
380+ frag := & BinaryFragment {}
381+ switch parts [0 ] {
382+ case "delta" :
383+ frag .Method = BinaryPatchDelta
384+ case "literal" :
385+ frag .Method = BinaryPatchLiteral
386+ default :
387+ return nil , nil
388+ }
389+
390+ totalBytes , err := strconv .ParseInt (parts [1 ], 10 , 64 )
391+ if err != nil {
392+ nerr := err .(* strconv.NumError )
393+ return nil , p .Errorf (0 , "binary patch: invalid data length: %v" , nerr .Err )
394+ }
395+
396+ var data bytes.Buffer
397+ buf := make ([]byte , maxBytesPerLine )
398+
399+ for {
400+ if err := p .Next (); err != nil {
401+ if err == io .EOF {
402+ break
403+ }
404+ return nil , err
405+ }
406+ line := p .Line (0 )
407+
408+ if line == "\n " {
409+ // blank line indicates the end of the fragment
410+ break
411+ }
412+
413+ // base85 encoding means each line is a multiple of 5 + first char and newline
414+ if len (line ) < len (shortestValidLine ) || (len (line )- 2 )% 5 != 0 {
415+ return nil , p .Errorf (0 , "binary patch: corrupt data line" )
416+ }
417+
418+ byteCount := int (line [0 ])
419+ switch {
420+ case 'A' <= byteCount && byteCount <= 'Z' :
421+ byteCount = byteCount - 'A' + 1
422+ case 'a' <= byteCount && byteCount <= 'z' :
423+ byteCount = byteCount - 'a' + 27
424+ default :
425+ return nil , p .Errorf (0 , "binary patch: invalid length byte: %q" , line [0 ])
426+ }
427+
428+ // base85 encodes every 4 bytes into 5 characters, with up to 3 bytes of end padding
429+ maxByteCount := (len (line ) - 2 ) / 5 * 4
430+ if byteCount >= maxByteCount || byteCount < maxByteCount - 3 {
431+ return nil , p .Errorf (0 , "binary patch: incorrect byte count: %d" , byteCount )
432+ }
433+
434+ ndst , _ , err := ascii85 .Decode (buf , []byte (line [1 :]), byteCount < maxBytesPerLine )
435+ if err != nil {
436+ return nil , p .Errorf (0 , "binary patch: %v" , err )
437+ }
438+ if ndst != byteCount {
439+ return nil , p .Errorf (0 , "binary patch: expected %d bytes, but decoded %d" , byteCount , ndst )
440+ }
441+ data .Write (buf [:ndst ])
442+ }
443+
444+ if err := inflateBinaryChunk (frag , & data , totalBytes ); err != nil {
445+ return nil , p .Errorf (0 , "binary patch: %v" , err )
446+ }
447+
448+ // consume the empty line that ended the fragment
449+ if err := p .Next (); err != nil && err != io .EOF {
450+ return nil , err
451+ }
452+ return frag , nil
453+ }
454+
455+ func inflateBinaryChunk (frag * BinaryFragment , r io.Reader , length int64 ) error {
456+ data := make ([]byte , length )
457+
458+ inflater := flate .NewReader (r )
459+ if _ , err := io .ReadFull (inflater , frag .Data ); err != nil {
460+ return err
461+ }
462+ if err := inflater .Close (); err != nil {
463+ return err
464+ }
465+
466+ frag .Data = data
467+ return nil
468+ }
469+
349470func parseRange (s string ) (start int64 , end int64 , err error ) {
350471 parts := strings .SplitN (s , "," , 2 )
351472
0 commit comments