123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- // 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
- }
|