mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Merge pull request #2335 from andreynering/highlight-diff
Highlight diff
This commit is contained in:
		| @@ -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
									
								
							
							
						
						
									
										70
									
								
								models/git_diff_test.go
									
									
									
									
									
										Normal 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]) | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -1217,3 +1217,11 @@ | |||||||
| 		width: 100%!important; | 		width: 100%!important; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .removed-code { | ||||||
|  |   background-color: #ff9999; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .added-code { | ||||||
|  |   background-color: #99ff99; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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"> </span> | 					<span class="status {{DiffTypeToStr .GetType}} poping up" data-content="{{DiffTypeToStr .GetType}}" data-variation="inverted tiny" data-position="right center"> </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}} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Unknwon
					Unknwon