git_diff.go 5.6 KB

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