git_diff.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. package models
  2. import (
  3. "bytes"
  4. "fmt"
  5. "gitote/gitote/pkg/setting"
  6. "gitote/gitote/pkg/template/highlight"
  7. "gitote/gitote/pkg/tool"
  8. "html"
  9. "html/template"
  10. "io"
  11. "github.com/sergi/go-diff/diffmatchpatch"
  12. "gitlab.com/gitote/git-module"
  13. "golang.org/x/net/html/charset"
  14. "golang.org/x/text/transform"
  15. )
  16. type DiffSection struct {
  17. *git.DiffSection
  18. }
  19. var (
  20. addedCodePrefix = []byte("<span class=\"added-code\">")
  21. removedCodePrefix = []byte("<span class=\"removed-code\">")
  22. codeTagSuffix = []byte("</span>")
  23. )
  24. func diffToHTML(diffs []diffmatchpatch.Diff, lineType git.DiffLineType) template.HTML {
  25. buf := bytes.NewBuffer(nil)
  26. // Reproduce signs which are cutted for inline diff before.
  27. switch lineType {
  28. case git.DIFF_LINE_ADD:
  29. buf.WriteByte('+')
  30. case git.DIFF_LINE_DEL:
  31. buf.WriteByte('-')
  32. }
  33. for i := range diffs {
  34. switch {
  35. case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == git.DIFF_LINE_ADD:
  36. buf.Write(addedCodePrefix)
  37. buf.WriteString(html.EscapeString(diffs[i].Text))
  38. buf.Write(codeTagSuffix)
  39. case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == git.DIFF_LINE_DEL:
  40. buf.Write(removedCodePrefix)
  41. buf.WriteString(html.EscapeString(diffs[i].Text))
  42. buf.Write(codeTagSuffix)
  43. case diffs[i].Type == diffmatchpatch.DiffEqual:
  44. buf.WriteString(html.EscapeString(diffs[i].Text))
  45. }
  46. }
  47. return template.HTML(buf.Bytes())
  48. }
  49. var diffMatchPatch = diffmatchpatch.New()
  50. func init() {
  51. diffMatchPatch.DiffEditCost = 100
  52. }
  53. // ComputedInlineDiffFor computes inline diff for the given line.
  54. func (diffSection *DiffSection) ComputedInlineDiffFor(diffLine *git.DiffLine) template.HTML {
  55. if setting.Git.DisableDiffHighlight {
  56. return template.HTML(html.EscapeString(diffLine.Content[1:]))
  57. }
  58. var (
  59. compareDiffLine *git.DiffLine
  60. diff1 string
  61. diff2 string
  62. )
  63. // try to find equivalent diff line. ignore, otherwise
  64. switch diffLine.Type {
  65. case git.DIFF_LINE_ADD:
  66. compareDiffLine = diffSection.Line(git.DIFF_LINE_DEL, diffLine.RightIdx)
  67. if compareDiffLine == nil {
  68. return template.HTML(html.EscapeString(diffLine.Content))
  69. }
  70. diff1 = compareDiffLine.Content
  71. diff2 = diffLine.Content
  72. case git.DIFF_LINE_DEL:
  73. compareDiffLine = diffSection.Line(git.DIFF_LINE_ADD, diffLine.LeftIdx)
  74. if compareDiffLine == nil {
  75. return template.HTML(html.EscapeString(diffLine.Content))
  76. }
  77. diff1 = diffLine.Content
  78. diff2 = compareDiffLine.Content
  79. default:
  80. return template.HTML(html.EscapeString(diffLine.Content))
  81. }
  82. diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true)
  83. diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
  84. return diffToHTML(diffRecord, diffLine.Type)
  85. }
  86. type DiffFile struct {
  87. *git.DiffFile
  88. Sections []*DiffSection
  89. }
  90. func (diffFile *DiffFile) HighlightClass() string {
  91. return highlight.FileNameToHighlightClass(diffFile.Name)
  92. }
  93. type Diff struct {
  94. *git.Diff
  95. Files []*DiffFile
  96. }
  97. func NewDiff(gitDiff *git.Diff) *Diff {
  98. diff := &Diff{
  99. Diff: gitDiff,
  100. Files: make([]*DiffFile, gitDiff.NumFiles()),
  101. }
  102. // FIXME: detect encoding while parsing.
  103. var buf bytes.Buffer
  104. for i := range gitDiff.Files {
  105. buf.Reset()
  106. diff.Files[i] = &DiffFile{
  107. DiffFile: gitDiff.Files[i],
  108. Sections: make([]*DiffSection, gitDiff.Files[i].NumSections()),
  109. }
  110. for j := range gitDiff.Files[i].Sections {
  111. diff.Files[i].Sections[j] = &DiffSection{
  112. DiffSection: gitDiff.Files[i].Sections[j],
  113. }
  114. for k := range diff.Files[i].Sections[j].Lines {
  115. buf.WriteString(diff.Files[i].Sections[j].Lines[k].Content)
  116. buf.WriteString("\n")
  117. }
  118. }
  119. charsetLabel, err := tool.DetectEncoding(buf.Bytes())
  120. if charsetLabel != "UTF-8" && err == nil {
  121. encoding, _ := charset.Lookup(charsetLabel)
  122. if encoding != nil {
  123. d := encoding.NewDecoder()
  124. for j := range diff.Files[i].Sections {
  125. for k := range diff.Files[i].Sections[j].Lines {
  126. if c, _, err := transform.String(d, diff.Files[i].Sections[j].Lines[k].Content); err == nil {
  127. diff.Files[i].Sections[j].Lines[k].Content = c
  128. }
  129. }
  130. }
  131. }
  132. }
  133. }
  134. return diff
  135. }
  136. func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*Diff, error) {
  137. done := make(chan error)
  138. var gitDiff *git.Diff
  139. go func() {
  140. gitDiff = git.ParsePatch(done, maxLines, maxLineCharacteres, maxFiles, reader)
  141. }()
  142. if err := <-done; err != nil {
  143. return nil, fmt.Errorf("ParsePatch: %v", err)
  144. }
  145. return NewDiff(gitDiff), nil
  146. }
  147. func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
  148. gitDiff, err := git.GetDiffRange(repoPath, beforeCommitID, afterCommitID, maxLines, maxLineCharacteres, maxFiles)
  149. if err != nil {
  150. return nil, fmt.Errorf("GetDiffRange: %v", err)
  151. }
  152. return NewDiff(gitDiff), nil
  153. }
  154. func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
  155. gitDiff, err := git.GetDiffCommit(repoPath, commitID, maxLines, maxLineCharacteres, maxFiles)
  156. if err != nil {
  157. return nil, fmt.Errorf("GetDiffCommit: %v", err)
  158. }
  159. return NewDiff(gitDiff), nil
  160. }