|
|
|
// 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 models
|
|
|
|
|
|
|
|
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/gogits/git-module"
|
|
|
|
|
|
|
|
"github.com/gogits/gogs/pkg/tool"
|
|
|
|
"github.com/gogits/gogs/pkg/setting"
|
|
|
|
"github.com/gogits/gogs/pkg/template/highlight"
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|