mirror of https://github.com/gogs/gogs.git
196 lines
5.0 KiB
Go
196 lines
5.0 KiB
Go
// 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 gitutil
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html"
|
|
"html/template"
|
|
"io"
|
|
"sync"
|
|
|
|
"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/conf"
|
|
"gogs.io/gogs/internal/template/highlight"
|
|
"gogs.io/gogs/internal/tool"
|
|
)
|
|
|
|
// DiffSection is a wrapper to git.DiffSection with helper methods.
|
|
type DiffSection struct {
|
|
*git.DiffSection
|
|
|
|
initOnce sync.Once
|
|
dmp *diffmatchpatch.DiffMatchPatch
|
|
}
|
|
|
|
// ComputedInlineDiffFor computes inline diff for the given line.
|
|
func (s *DiffSection) ComputedInlineDiffFor(line *git.DiffLine) template.HTML {
|
|
fallback := template.HTML(html.EscapeString(line.Content))
|
|
if conf.Git.DisableDiffHighlight {
|
|
return fallback
|
|
}
|
|
|
|
// Find equivalent diff line, ignore when not found.
|
|
var diff1, diff2 string
|
|
switch line.Type {
|
|
case git.DiffLineAdd:
|
|
compareLine := s.Line(git.DiffLineDelete, line.RightLine)
|
|
if compareLine == nil {
|
|
return fallback
|
|
}
|
|
|
|
diff1 = compareLine.Content
|
|
diff2 = line.Content
|
|
|
|
case git.DiffLineDelete:
|
|
compareLine := s.Line(git.DiffLineAdd, line.LeftLine)
|
|
if compareLine == nil {
|
|
return fallback
|
|
}
|
|
|
|
diff1 = line.Content
|
|
diff2 = compareLine.Content
|
|
|
|
default:
|
|
return fallback
|
|
}
|
|
|
|
s.initOnce.Do(func() {
|
|
s.dmp = diffmatchpatch.New()
|
|
s.dmp.DiffEditCost = 100
|
|
})
|
|
|
|
diffs := s.dmp.DiffMain(diff1[1:], diff2[1:], true)
|
|
diffs = s.dmp.DiffCleanupEfficiency(diffs)
|
|
|
|
return diffsToHTML(diffs, line.Type)
|
|
}
|
|
|
|
func diffsToHTML(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.DiffLineAdd:
|
|
buf.WriteByte('+')
|
|
case git.DiffLineDelete:
|
|
buf.WriteByte('-')
|
|
}
|
|
|
|
const (
|
|
addedCodePrefix = `<span class="added-code">`
|
|
removedCodePrefix = `<span class="removed-code">`
|
|
codeTagSuffix = `</span>`
|
|
)
|
|
|
|
for i := range diffs {
|
|
switch {
|
|
case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == git.DiffLineAdd:
|
|
buf.WriteString(addedCodePrefix)
|
|
buf.WriteString(html.EscapeString(diffs[i].Text))
|
|
buf.WriteString(codeTagSuffix)
|
|
case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == git.DiffLineDelete:
|
|
buf.WriteString(removedCodePrefix)
|
|
buf.WriteString(html.EscapeString(diffs[i].Text))
|
|
buf.WriteString(codeTagSuffix)
|
|
case diffs[i].Type == diffmatchpatch.DiffEqual:
|
|
buf.WriteString(html.EscapeString(diffs[i].Text))
|
|
}
|
|
}
|
|
|
|
return template.HTML(buf.Bytes())
|
|
}
|
|
|
|
// DiffFile is a wrapper to git.DiffFile with helper methods.
|
|
type DiffFile struct {
|
|
*git.DiffFile
|
|
Sections []*DiffSection
|
|
}
|
|
|
|
// HighlightClass returns the detected highlight class for the file.
|
|
func (diffFile *DiffFile) HighlightClass() string {
|
|
return highlight.FileNameToHighlightClass(diffFile.Name)
|
|
}
|
|
|
|
// Diff is a wrapper to git.Diff with helper methods.
|
|
type Diff struct {
|
|
*git.Diff
|
|
Files []*DiffFile
|
|
}
|
|
|
|
// NewDiff returns a new wrapper of given git.Diff.
|
|
func NewDiff(oldDiff *git.Diff) *Diff {
|
|
newDiff := &Diff{
|
|
Diff: oldDiff,
|
|
Files: make([]*DiffFile, oldDiff.NumFiles()),
|
|
}
|
|
|
|
// FIXME: detect encoding while parsing.
|
|
var buf bytes.Buffer
|
|
for i := range oldDiff.Files {
|
|
buf.Reset()
|
|
|
|
newDiff.Files[i] = &DiffFile{
|
|
DiffFile: oldDiff.Files[i],
|
|
Sections: make([]*DiffSection, oldDiff.Files[i].NumSections()),
|
|
}
|
|
|
|
for j := range oldDiff.Files[i].Sections {
|
|
newDiff.Files[i].Sections[j] = &DiffSection{
|
|
DiffSection: oldDiff.Files[i].Sections[j],
|
|
}
|
|
|
|
for k := range newDiff.Files[i].Sections[j].Lines {
|
|
buf.WriteString(newDiff.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 newDiff.Files[i].Sections {
|
|
for k := range newDiff.Files[i].Sections[j].Lines {
|
|
if c, _, err := transform.String(d, newDiff.Files[i].Sections[j].Lines[k].Content); err == nil {
|
|
newDiff.Files[i].Sections[j].Lines[k].Content = c
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return newDiff
|
|
}
|
|
|
|
// ParseDiff parses the diff from given io.Reader.
|
|
func ParseDiff(r io.Reader, maxFiles, maxFileLines, maxLineChars int) (*Diff, error) {
|
|
done := make(chan git.SteamParseDiffResult)
|
|
go git.StreamParseDiff(r, done, maxFiles, maxFileLines, maxLineChars)
|
|
|
|
result := <-done
|
|
if result.Err != nil {
|
|
return nil, fmt.Errorf("stream parse diff: %v", result.Err)
|
|
}
|
|
return NewDiff(result.Diff), nil
|
|
}
|
|
|
|
// RepoDiff parses the diff on given revisions of given repository.
|
|
func RepoDiff(repo *git.Repository, rev string, maxFiles, maxFileLines, maxLineChars int, opts ...git.DiffOptions) (*Diff, error) {
|
|
diff, err := repo.Diff(rev, maxFiles, maxFileLines, maxLineChars, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get diff: %v", err)
|
|
}
|
|
return NewDiff(diff), nil
|
|
}
|