| 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 gitutilimport (	"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}
 |