Merge pull request #2335 from andreynering/highlight-diff

Highlight diff
This commit is contained in:
Unknwon 2016-01-09 13:39:18 +08:00
commit bcf6aed452
6 changed files with 192 additions and 14 deletions

View File

@ -13,10 +13,13 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"html/template"
"html"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"golang.org/x/net/html/charset" "golang.org/x/net/html/charset"
"golang.org/x/text/transform" "golang.org/x/text/transform"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/gogits/git-module" "github.com/gogits/git-module"
@ -25,16 +28,19 @@ import (
"github.com/gogits/gogs/modules/process" "github.com/gogits/gogs/modules/process"
) )
// Diff line types. type DiffLineType uint8
const ( const (
DIFF_LINE_PLAIN = iota + 1 DIFF_LINE_PLAIN DiffLineType = iota + 1
DIFF_LINE_ADD DIFF_LINE_ADD
DIFF_LINE_DEL DIFF_LINE_DEL
DIFF_LINE_SECTION DIFF_LINE_SECTION
) )
type DiffFileType uint8
const ( const (
DIFF_FILE_ADD = iota + 1 DIFF_FILE_ADD DiffFileType = iota + 1
DIFF_FILE_CHANGE DIFF_FILE_CHANGE
DIFF_FILE_DEL DIFF_FILE_DEL
DIFF_FILE_RENAME DIFF_FILE_RENAME
@ -43,12 +49,13 @@ const (
type DiffLine struct { type DiffLine struct {
LeftIdx int LeftIdx int
RightIdx int RightIdx int
Type int Type DiffLineType
Content string Content string
ParsedContent template.HTML
} }
func (d DiffLine) GetType() int { func (d *DiffLine) GetType() int {
return d.Type return int(d.Type)
} }
type DiffSection struct { type DiffSection struct {
@ -56,12 +63,89 @@ type DiffSection struct {
Lines []*DiffLine Lines []*DiffLine
} }
func diffToHtml(diffRecord []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
result := ""
for _, s := range diffRecord {
if s.Type == diffmatchpatch.DiffInsert && lineType == DIFF_LINE_ADD {
result = result + "<span class=\"added-code\">"+html.EscapeString(s.Text)+"</span>"
} else if s.Type == diffmatchpatch.DiffDelete && lineType == DIFF_LINE_DEL {
result = result + "<span class=\"removed-code\">"+html.EscapeString(s.Text)+"</span>"
} else if s.Type == diffmatchpatch.DiffEqual {
result = result + html.EscapeString(s.Text)
}
}
return template.HTML(result)
}
// get an specific line by type (add or del) and file line number
func (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine {
difference := 0
for _, diffLine := range diffSection.Lines {
if diffLine.Type == DIFF_LINE_PLAIN {
// get the difference of line numbers between ADD and DEL versions
difference = diffLine.RightIdx - diffLine.LeftIdx
continue
}
if lineType == DIFF_LINE_DEL {
if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx - difference {
return diffLine
}
} else if lineType == DIFF_LINE_ADD {
if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx + difference {
return diffLine
}
}
}
return nil
}
// computes diff of each diff line and set the HTML on diffLine.ParsedContent
func (diffSection *DiffSection) ComputeLinesDiff() {
for _, diffLine := range diffSection.Lines {
var compareDiffLine *DiffLine
var diff1, diff2 string
// default content: as is
diffLine.ParsedContent = template.HTML(html.EscapeString(diffLine.Content[1:]))
// just compute diff for adds and removes
if diffLine.Type != DIFF_LINE_ADD && diffLine.Type != DIFF_LINE_DEL {
continue
}
// try to find equivalent diff line. ignore, otherwise
if diffLine.Type == DIFF_LINE_ADD {
compareDiffLine = diffSection.GetLine(DIFF_LINE_DEL, diffLine.RightIdx)
if compareDiffLine == nil {
continue
}
diff1 = compareDiffLine.Content
diff2 = diffLine.Content
} else {
compareDiffLine = diffSection.GetLine(DIFF_LINE_ADD, diffLine.LeftIdx)
if compareDiffLine == nil {
continue
}
diff1 = diffLine.Content
diff2 = compareDiffLine.Content
}
dmp := diffmatchpatch.New()
diffRecord := dmp.DiffMain(diff1[1:], diff2[1:], true)
diffRecord = dmp.DiffCleanupSemantic(diffRecord)
diffLine.ParsedContent = diffToHtml(diffRecord, diffLine.Type)
}
}
type DiffFile struct { type DiffFile struct {
Name string Name string
OldName string OldName string
Index int Index int
Addition, Deletion int Addition, Deletion int
Type int Type DiffFileType
IsCreated bool IsCreated bool
IsDeleted bool IsDeleted bool
IsBin bool IsBin bool
@ -69,6 +153,10 @@ type DiffFile struct {
Sections []*DiffSection Sections []*DiffSection
} }
func (diffFile *DiffFile) GetType() int {
return int(diffFile.Type)
}
type Diff struct { type Diff struct {
TotalAddition, TotalDeletion int TotalAddition, TotalDeletion int
Files []*DiffFile Files []*DiffFile

70
models/git_diff_test.go Normal file
View File

@ -0,0 +1,70 @@
package models
import (
dmp "github.com/sergi/go-diff/diffmatchpatch"
"html/template"
"testing"
)
func assertEqual(t *testing.T, s1 string, s2 template.HTML) {
if s1 != string(s2) {
t.Errorf("%s should be equal %s", s2, s1)
}
}
func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) {
if d1 != d2 {
t.Errorf("%v should be equal %v", d1, d2)
}
}
func TestDiffToHtml(t *testing.T) {
assertEqual(t, "foo <span class=\"added-code\">bar</span> biz", diffToHtml([]dmp.Diff{
dmp.Diff{dmp.DiffEqual, "foo "},
dmp.Diff{dmp.DiffInsert, "bar"},
dmp.Diff{dmp.DiffDelete, " baz"},
dmp.Diff{dmp.DiffEqual, " biz"},
}, DIFF_LINE_ADD))
assertEqual(t, "foo <span class=\"removed-code\">bar</span> biz", diffToHtml([]dmp.Diff{
dmp.Diff{dmp.DiffEqual, "foo "},
dmp.Diff{dmp.DiffDelete, "bar"},
dmp.Diff{dmp.DiffInsert, " baz"},
dmp.Diff{dmp.DiffEqual, " biz"},
}, DIFF_LINE_DEL))
}
// test if GetLine is return the correct lines
func TestGetLine(t *testing.T) {
ds := DiffSection{Lines: []*DiffLine{
&DiffLine{LeftIdx: 28, RightIdx: 28, Type: DIFF_LINE_PLAIN},
&DiffLine{LeftIdx: 29, RightIdx: 29, Type: DIFF_LINE_PLAIN},
&DiffLine{LeftIdx: 30, RightIdx: 30, Type: DIFF_LINE_PLAIN},
&DiffLine{LeftIdx: 31, RightIdx: 0, Type: DIFF_LINE_DEL},
&DiffLine{LeftIdx: 0, RightIdx: 31, Type: DIFF_LINE_ADD},
&DiffLine{LeftIdx: 0, RightIdx: 32, Type: DIFF_LINE_ADD},
&DiffLine{LeftIdx: 32, RightIdx: 33, Type: DIFF_LINE_PLAIN},
&DiffLine{LeftIdx: 33, RightIdx: 0, Type: DIFF_LINE_DEL},
&DiffLine{LeftIdx: 34, RightIdx: 0, Type: DIFF_LINE_DEL},
&DiffLine{LeftIdx: 35, RightIdx: 0, Type: DIFF_LINE_DEL},
&DiffLine{LeftIdx: 36, RightIdx: 0, Type: DIFF_LINE_DEL},
&DiffLine{LeftIdx: 0, RightIdx: 34, Type: DIFF_LINE_ADD},
&DiffLine{LeftIdx: 0, RightIdx: 35, Type: DIFF_LINE_ADD},
&DiffLine{LeftIdx: 0, RightIdx: 36, Type: DIFF_LINE_ADD},
&DiffLine{LeftIdx: 0, RightIdx: 37, Type: DIFF_LINE_ADD},
&DiffLine{LeftIdx: 37, RightIdx: 38, Type: DIFF_LINE_PLAIN},
&DiffLine{LeftIdx: 38, RightIdx: 39, Type: DIFF_LINE_PLAIN},
}}
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 31), ds.Lines[4])
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 31), ds.Lines[3])
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 33), ds.Lines[11])
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 34), ds.Lines[12])
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 35), ds.Lines[13])
assertLineEqual(t, ds.GetLine(DIFF_LINE_ADD, 36), ds.Lines[14])
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 34), ds.Lines[7])
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 35), ds.Lines[8])
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 36), ds.Lines[9])
assertLineEqual(t, ds.GetLine(DIFF_LINE_DEL, 37), ds.Lines[10])
}

View File

@ -2764,6 +2764,12 @@ footer .container .links > *:first-child {
#delete-repo-modal .ui.message { #delete-repo-modal .ui.message {
width: 100%!important; width: 100%!important;
} }
.removed-code {
background-color: #ff9999;
}
.added-code {
background-color: #99ff99;
}
.organization { .organization {
padding-top: 15px; padding-top: 15px;
padding-bottom: 80px; padding-bottom: 80px;

View File

@ -1217,3 +1217,11 @@
width: 100%!important; width: 100%!important;
} }
} }
.removed-code {
background-color: #ff9999;
}
.added-code {
background-color: #99ff99;
}

View File

@ -168,6 +168,12 @@ func Diff(ctx *middleware.Context) {
} }
} }
for _, diffFile := range diff.Files {
for _, diffSection := range diffFile.Sections {
diffSection.ComputeLinesDiff()
}
}
ctx.Data["IsSplitStyle"] = ctx.Query("style") == "split" ctx.Data["IsSplitStyle"] = ctx.Query("style") == "split"
ctx.Data["Username"] = userName ctx.Data["Username"] = userName
ctx.Data["Reponame"] = repoName ctx.Data["Reponame"] = repoName

View File

@ -26,7 +26,7 @@
{{end}} {{end}}
</div> </div>
<!-- todo finish all file status, now modify, add, delete and rename --> <!-- todo finish all file status, now modify, add, delete and rename -->
<span class="status {{DiffTypeToStr .Type}} poping up" data-content="{{DiffTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center">&nbsp;</span> <span class="status {{DiffTypeToStr .GetType}} poping up" data-content="{{DiffTypeToStr .GetType}}" data-variation="inverted tiny" data-position="right center">&nbsp;</span>
<a class="file" href="#diff-{{.Index}}">{{.Name}}</a> <a class="file" href="#diff-{{.Index}}">{{.Name}}</a>
</li> </li>
{{end}} {{end}}
@ -71,18 +71,18 @@
{{if $.IsSplitStyle}} {{if $.IsSplitStyle}}
{{range $j, $section := .Sections}} {{range $j, $section := .Sections}}
{{range $k, $line := .Lines}} {{range $k, $line := .Lines}}
<tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> <tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}">
<td class="lines-num lines-num-old"> <td class="lines-num lines-num-old">
<span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span> <span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span>
</td> </td>
<td class="lines-code halfwidth"> <td class="lines-code halfwidth">
<pre class="wrap">{{if $line.LeftIdx}}{{$line.Content}}{{end}}</pre> <pre class="wrap">{{if $line.LeftIdx}}{{$line.ParsedContent}}{{end}}</pre>
</td> </td>
<td class="lines-num lines-num-new"> <td class="lines-num lines-num-new">
<span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span> <span rel="{{if $line.RightIdx}}diff-{{Sha1 $file.Name}}R{{$line.RightIdx}}{{end}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span>
</td> </td>
<td class="lines-code halfwidth"> <td class="lines-code halfwidth">
<pre class="wrap">{{if $line.RightIdx}}{{$line.Content}}{{end}}</pre> <pre class="wrap">{{if $line.RightIdx}}{{$line.ParsedContent}}{{end}}</pre>
</td> </td>
</tr> </tr>
{{end}} {{end}}
@ -90,8 +90,8 @@
{{else}} {{else}}
{{range $j, $section := .Sections}} {{range $j, $section := .Sections}}
{{range $k, $line := .Lines}} {{range $k, $line := .Lines}}
<tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> <tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}">
{{if eq .Type 4}} {{if eq .GetType 4}}
<td colspan="2" class="lines-num"> <td colspan="2" class="lines-num">
{{/* {{if gt $j 0}}<span class="fold octicon octicon-fold"></span>{{end}} */}} {{/* {{if gt $j 0}}<span class="fold octicon octicon-fold"></span>{{end}} */}}
</td> </td>
@ -104,7 +104,7 @@
</td> </td>
{{end}} {{end}}
<td class="lines-code"> <td class="lines-code">
<pre>{{$line.Content}}</pre> <pre>{{$line.ParsedContent}}</pre>
</td> </td>
</tr> </tr>
{{end}} {{end}}