aboutsummaryrefslogtreecommitdiff
path: root/internal/db/git_diff.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/db/git_diff.go')
-rw-r--r--internal/db/git_diff.go194
1 files changed, 194 insertions, 0 deletions
diff --git a/internal/db/git_diff.go b/internal/db/git_diff.go
new file mode 100644
index 00000000..040c472b
--- /dev/null
+++ b/internal/db/git_diff.go
@@ -0,0 +1,194 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package db
+
+import (
+ "bytes"
+ "fmt"
+ "html"
+ "html/template"
+ "io"
+
+ "github.com/sergi/go-diff/diffmatchpatch"
+ "golang.org/x/net/html/charset"
+ "golang.org/x/text/transform"
+
+ "github.com/gogs/git-module"
+
+ "gogs.io/gogs/internal/setting"
+ "gogs.io/gogs/internal/template/highlight"
+ "gogs.io/gogs/internal/tool"
+)
+
+type DiffSection struct {
+ *git.DiffSection
+}
+
+var (
+ addedCodePrefix = []byte("<span class=\"added-code\">")
+ removedCodePrefix = []byte("<span class=\"removed-code\">")
+ codeTagSuffix = []byte("</span>")
+)
+
+func diffToHTML(diffs []diffmatchpatch.Diff, lineType git.DiffLineType) template.HTML {
+ buf := bytes.NewBuffer(nil)
+
+ // Reproduce signs which are cutted for inline diff before.
+ switch lineType {
+ case git.DIFF_LINE_ADD:
+ buf.WriteByte('+')
+ case git.DIFF_LINE_DEL:
+ buf.WriteByte('-')
+ }
+
+ for i := range diffs {
+ switch {
+ case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == git.DIFF_LINE_ADD:
+ buf.Write(addedCodePrefix)
+ buf.WriteString(html.EscapeString(diffs[i].Text))
+ buf.Write(codeTagSuffix)
+ case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == git.DIFF_LINE_DEL:
+ buf.Write(removedCodePrefix)
+ buf.WriteString(html.EscapeString(diffs[i].Text))
+ buf.Write(codeTagSuffix)
+ case diffs[i].Type == diffmatchpatch.DiffEqual:
+ buf.WriteString(html.EscapeString(diffs[i].Text))
+ }
+ }
+
+ return template.HTML(buf.Bytes())
+}
+
+var diffMatchPatch = diffmatchpatch.New()
+
+func init() {
+ diffMatchPatch.DiffEditCost = 100
+}
+
+// ComputedInlineDiffFor computes inline diff for the given line.
+func (diffSection *DiffSection) ComputedInlineDiffFor(diffLine *git.DiffLine) template.HTML {
+ if setting.Git.DisableDiffHighlight {
+ return template.HTML(html.EscapeString(diffLine.Content[1:]))
+ }
+ var (
+ compareDiffLine *git.DiffLine
+ diff1 string
+ diff2 string
+ )
+
+ // try to find equivalent diff line. ignore, otherwise
+ switch diffLine.Type {
+ case git.DIFF_LINE_ADD:
+ compareDiffLine = diffSection.Line(git.DIFF_LINE_DEL, diffLine.RightIdx)
+ if compareDiffLine == nil {
+ return template.HTML(html.EscapeString(diffLine.Content))
+ }
+ diff1 = compareDiffLine.Content
+ diff2 = diffLine.Content
+ case git.DIFF_LINE_DEL:
+ compareDiffLine = diffSection.Line(git.DIFF_LINE_ADD, diffLine.LeftIdx)
+ if compareDiffLine == nil {
+ return template.HTML(html.EscapeString(diffLine.Content))
+ }
+ diff1 = diffLine.Content
+ diff2 = compareDiffLine.Content
+ default:
+ return template.HTML(html.EscapeString(diffLine.Content))
+ }
+
+ diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true)
+ diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
+
+ return diffToHTML(diffRecord, diffLine.Type)
+}
+
+type DiffFile struct {
+ *git.DiffFile
+ Sections []*DiffSection
+}
+
+func (diffFile *DiffFile) HighlightClass() string {
+ return highlight.FileNameToHighlightClass(diffFile.Name)
+}
+
+type Diff struct {
+ *git.Diff
+ Files []*DiffFile
+}
+
+func NewDiff(gitDiff *git.Diff) *Diff {
+ diff := &Diff{
+ Diff: gitDiff,
+ Files: make([]*DiffFile, gitDiff.NumFiles()),
+ }
+
+ // FIXME: detect encoding while parsing.
+ var buf bytes.Buffer
+ for i := range gitDiff.Files {
+ buf.Reset()
+
+ diff.Files[i] = &DiffFile{
+ DiffFile: gitDiff.Files[i],
+ Sections: make([]*DiffSection, gitDiff.Files[i].NumSections()),
+ }
+
+ for j := range gitDiff.Files[i].Sections {
+ diff.Files[i].Sections[j] = &DiffSection{
+ DiffSection: gitDiff.Files[i].Sections[j],
+ }
+
+ for k := range diff.Files[i].Sections[j].Lines {
+ buf.WriteString(diff.Files[i].Sections[j].Lines[k].Content)
+ buf.WriteString("\n")
+ }
+ }
+
+ charsetLabel, err := tool.DetectEncoding(buf.Bytes())
+ if charsetLabel != "UTF-8" && err == nil {
+ encoding, _ := charset.Lookup(charsetLabel)
+ if encoding != nil {
+ d := encoding.NewDecoder()
+ for j := range diff.Files[i].Sections {
+ for k := range diff.Files[i].Sections[j].Lines {
+ if c, _, err := transform.String(d, diff.Files[i].Sections[j].Lines[k].Content); err == nil {
+ diff.Files[i].Sections[j].Lines[k].Content = c
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return diff
+}
+
+func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*Diff, error) {
+ done := make(chan error)
+ var gitDiff *git.Diff
+ go func() {
+ gitDiff = git.ParsePatch(done, maxLines, maxLineCharacteres, maxFiles, reader)
+ }()
+
+ if err := <-done; err != nil {
+ return nil, fmt.Errorf("ParsePatch: %v", err)
+ }
+ return NewDiff(gitDiff), nil
+}
+
+func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
+ gitDiff, err := git.GetDiffRange(repoPath, beforeCommitID, afterCommitID, maxLines, maxLineCharacteres, maxFiles)
+ if err != nil {
+ return nil, fmt.Errorf("GetDiffRange: %v", err)
+ }
+ return NewDiff(gitDiff), nil
+}
+
+func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
+ gitDiff, err := git.GetDiffCommit(repoPath, commitID, maxLines, maxLineCharacteres, maxFiles)
+ if err != nil {
+ return nil, fmt.Errorf("GetDiffCommit: %v", err)
+ }
+ return NewDiff(gitDiff), nil
+}