git_diff.go 5.1 KB

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