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/exec" | ||||
| 	"strings" | ||||
| 	"html/template" | ||||
| 	"html" | ||||
|  | ||||
| 	"github.com/Unknwon/com" | ||||
| 	"golang.org/x/net/html/charset" | ||||
| 	"golang.org/x/text/transform" | ||||
| 	"github.com/sergi/go-diff/diffmatchpatch" | ||||
|  | ||||
| 	"github.com/gogits/git-module" | ||||
|  | ||||
| @@ -25,16 +28,19 @@ import ( | ||||
| 	"github.com/gogits/gogs/modules/process" | ||||
| ) | ||||
|  | ||||
| // Diff line types. | ||||
| type DiffLineType uint8 | ||||
|  | ||||
| const ( | ||||
| 	DIFF_LINE_PLAIN = iota + 1 | ||||
| 	DIFF_LINE_PLAIN DiffLineType = iota + 1 | ||||
| 	DIFF_LINE_ADD | ||||
| 	DIFF_LINE_DEL | ||||
| 	DIFF_LINE_SECTION | ||||
| ) | ||||
|  | ||||
| type DiffFileType uint8 | ||||
|  | ||||
| const ( | ||||
| 	DIFF_FILE_ADD = iota + 1 | ||||
| 	DIFF_FILE_ADD DiffFileType = iota + 1 | ||||
| 	DIFF_FILE_CHANGE | ||||
| 	DIFF_FILE_DEL | ||||
| 	DIFF_FILE_RENAME | ||||
| @@ -43,12 +49,13 @@ const ( | ||||
| type DiffLine struct { | ||||
| 	LeftIdx  int | ||||
| 	RightIdx int | ||||
| 	Type     int | ||||
| 	Type     DiffLineType | ||||
| 	Content  string | ||||
| 	ParsedContent template.HTML | ||||
| } | ||||
|  | ||||
| func (d DiffLine) GetType() int { | ||||
| 	return d.Type | ||||
| func (d *DiffLine) GetType() int { | ||||
| 	return int(d.Type) | ||||
| } | ||||
|  | ||||
| type DiffSection struct { | ||||
| @@ -56,12 +63,89 @@ type DiffSection struct { | ||||
| 	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 { | ||||
| 	Name               string | ||||
| 	OldName            string | ||||
| 	Index              int | ||||
| 	Addition, Deletion int | ||||
| 	Type               int | ||||
| 	Type               DiffFileType | ||||
| 	IsCreated          bool | ||||
| 	IsDeleted          bool | ||||
| 	IsBin              bool | ||||
| @@ -69,6 +153,10 @@ type DiffFile struct { | ||||
| 	Sections           []*DiffSection | ||||
| } | ||||
|  | ||||
| func (diffFile *DiffFile) GetType() int { | ||||
| 	return int(diffFile.Type) | ||||
| } | ||||
|  | ||||
| type Diff struct { | ||||
| 	TotalAddition, TotalDeletion int | ||||
| 	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 { | ||||
|   width: 100%!important; | ||||
| } | ||||
| .removed-code { | ||||
|   background-color: #ff9999; | ||||
| } | ||||
| .added-code { | ||||
|   background-color: #99ff99; | ||||
| } | ||||
| .organization { | ||||
|   padding-top: 15px; | ||||
|   padding-bottom: 80px; | ||||
|   | ||||
| @@ -1217,3 +1217,11 @@ | ||||
| 		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["Username"] = userName | ||||
| 	ctx.Data["Reponame"] = repoName | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
| 						{{end}} | ||||
| 					</div> | ||||
| 					<!-- 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> | ||||
| 				</li> | ||||
| 			{{end}} | ||||
| @@ -71,18 +71,18 @@ | ||||
| 									{{if $.IsSplitStyle}} | ||||
| 										{{range $j, $section := .Sections}} | ||||
| 											{{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"> | ||||
| 														<span rel="{{if $line.LeftIdx}}diff-{{Sha1 $file.Name}}L{{$line.LeftIdx}}{{end}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span> | ||||
| 													</td> | ||||
| 													<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 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> | ||||
| 													</td> | ||||
| 													<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> | ||||
| 												</tr> | ||||
| 											{{end}} | ||||
| @@ -90,8 +90,8 @@ | ||||
| 									{{else}} | ||||
| 										{{range $j, $section := .Sections}} | ||||
| 											{{range $k, $line := .Lines}} | ||||
| 												<tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}"> | ||||
| 													{{if eq .Type 4}} | ||||
| 												<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}"> | ||||
| 													{{if eq .GetType 4}} | ||||
| 													<td colspan="2" class="lines-num"> | ||||
| 														{{/* {{if gt $j 0}}<span class="fold octicon octicon-fold"></span>{{end}} */}} | ||||
| 													</td> | ||||
| @@ -104,7 +104,7 @@ | ||||
| 													</td> | ||||
| 													{{end}} | ||||
| 													<td class="lines-code"> | ||||
| 														<pre>{{$line.Content}}</pre> | ||||
| 														<pre>{{$line.ParsedContent}}</pre> | ||||
| 													</td> | ||||
| 												</tr> | ||||
| 											{{end}} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Unknwon
					Unknwon