mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
5.2 KiB
194 lines
5.2 KiB
// 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/base" |
|
"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 := base.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 |
|
}
|
|
|