diff options
author | ᴜɴᴋɴᴡᴏɴ <u@gogs.io> | 2020-03-08 19:09:31 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-08 19:09:31 +0800 |
commit | 6437d0180b97a26319b50c2e22927dac7c94fcdd (patch) | |
tree | 3d0d097e7f498e4b970065096e7500876d365a8b /internal/gitutil/diff.go | |
parent | c65b5b9f84dee21dc362311b299694e8e00f6ac6 (diff) |
git: migrate to github.com/gogs/git-module@v1.0.0 (#5958)
* WIP
* Finish `internal/db/git_diff.go`
* FInish internal/db/mirror.go
* Finish internal/db/pull.go
* Finish internal/db/release.go
* Finish internal/db/repo.go
* Finish internal/db/repo_branch.go
* Finish internal/db/repo_editor.go
* Finish internal/db/update.go
* Save my work
* Add license header
* Compile!
* Merge master
* Finish internal/cmd/hook.go
* Finish internal/conf/static.go
* Finish internal/context/repo.go
* Finish internal/db/action.go
* Finish internal/db/git_diff.go
* Fix submodule URL inferring
* Finish internal/db/mirror.go
* Updat to beta.4
* css: update fonts
* Finish internal/db/pull.go
* Finish internal/db/release.go
* Finish internal/db/repo_branch.go
* Finish internal/db/wiki.go
* gitutil: enhance infer submodule UR
* Finish internal/route/api/v1/repo/commits.go
* mirror: only collect branch commits after sync
* mirror: fix tag support
* Finish internal/db/repo.go
* Finish internal/db/repo_editor.go
* Finish internal/db/update.go
* Finish internal/gitutil/pull_request.go
* Make it compile
* Finish internal/route/repo/setting.go
* Finish internal/route/repo/branch.go
* Finish internal/route/api/v1/repo/file.go
* Finish internal/route/repo/download.go
* Finish internal/route/repo/editor.go
* Use helper
* Finish internal/route/repo/issue.go
* Finish internal/route/repo/pull.go
* Finish internal/route/repo/release.go
* Finish internal/route/repo/repo.go
* Finish internal/route/repo/wiki.go
* Finish internal/route/repo/commit.go
* Finish internal/route/repo/view.go
* Finish internal/gitutil/tag.go
* go.sum
Diffstat (limited to 'internal/gitutil/diff.go')
-rw-r--r-- | internal/gitutil/diff.go | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/internal/gitutil/diff.go b/internal/gitutil/diff.go new file mode 100644 index 00000000..4d759742 --- /dev/null +++ b/internal/gitutil/diff.go @@ -0,0 +1,196 @@ +// 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('-') + } + 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 +} |