diff.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. package hercules
  2. import (
  3. "bytes"
  4. "errors"
  5. "github.com/sergi/go-diff/diffmatchpatch"
  6. "gopkg.in/src-d/go-git.v4"
  7. "gopkg.in/src-d/go-git.v4/plumbing"
  8. "gopkg.in/src-d/go-git.v4/plumbing/object"
  9. "gopkg.in/src-d/go-git.v4/utils/merkletrie"
  10. )
  11. // FileDiff calculates the difference of files which were modified.
  12. type FileDiff struct {
  13. }
  14. type FileDiffData struct {
  15. OldLinesOfCode int
  16. NewLinesOfCode int
  17. Diffs []diffmatchpatch.Diff
  18. }
  19. func (diff *FileDiff) Name() string {
  20. return "FileDiff"
  21. }
  22. func (diff *FileDiff) Provides() []string {
  23. arr := [...]string{"file_diff"}
  24. return arr[:]
  25. }
  26. func (diff *FileDiff) Requires() []string {
  27. arr := [...]string{"changes", "blob_cache"}
  28. return arr[:]
  29. }
  30. func (diff *FileDiff) Construct(facts map[string]interface{}) {}
  31. func (diff *FileDiff) Initialize(repository *git.Repository) {}
  32. func (diff *FileDiff) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  33. result := map[string]FileDiffData{}
  34. cache := deps["blob_cache"].(map[plumbing.Hash]*object.Blob)
  35. tree_diff := deps["changes"].(object.Changes)
  36. for _, change := range tree_diff {
  37. action, err := change.Action()
  38. if err != nil {
  39. return nil, err
  40. }
  41. switch action {
  42. case merkletrie.Modify:
  43. blob_from := cache[change.From.TreeEntry.Hash]
  44. blob_to := cache[change.To.TreeEntry.Hash]
  45. // we are not validating UTF-8 here because for example
  46. // git/git 4f7770c87ce3c302e1639a7737a6d2531fe4b160 fetch-pack.c is invalid UTF-8
  47. str_from, err := blobToString(blob_from)
  48. if err != nil {
  49. return nil, err
  50. }
  51. str_to, err := blobToString(blob_to)
  52. if err != nil {
  53. return nil, err
  54. }
  55. dmp := diffmatchpatch.New()
  56. src, dst, _ := dmp.DiffLinesToRunes(str_from, str_to)
  57. diffs := dmp.DiffMainRunes(src, dst, false)
  58. result[change.To.Name] = FileDiffData{
  59. OldLinesOfCode: len(src),
  60. NewLinesOfCode: len(dst),
  61. Diffs: diffs,
  62. }
  63. default:
  64. continue
  65. }
  66. }
  67. return map[string]interface{}{"file_diff": result}, nil
  68. }
  69. func (diff *FileDiff) Finalize() interface{} {
  70. return nil
  71. }
  72. func blobToString(file *object.Blob) (string, error) {
  73. if file == nil {
  74. return "", errors.New("Blob not cached.")
  75. }
  76. reader, err := file.Reader()
  77. if err != nil {
  78. return "", err
  79. }
  80. defer checkClose(reader)
  81. buf := new(bytes.Buffer)
  82. buf.ReadFrom(reader)
  83. return buf.String(), nil
  84. }
  85. func init() {
  86. Registry.Register(&FileDiff{})
  87. }