git_diff.go 5.2 KB

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