git@cosmocr.at/gitweb.git @ HEAD · summary refs log
HEAD / internal / render / diff.go /
internal/render/diff.go · 1.8 KiB · raw
view: SourceMarkdown ☑ force
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package render

import (
	"html/template"
	"strings"

	"github.com/go-git/go-git/v5/plumbing/format/diff"
	"github.com/go-git/go-git/v5/plumbing/object"
)

// Diff renders a go-git commit patch as a class-tagged HTML table: a header row
// per file then added/removed/context lines, each tagged with a CSS class
// (.diff-add/.diff-del/.diff-ctx) defined in style.css. No inline styles, so
// the strict CSP is preserved.
func Diff(p *object.Patch) template.HTML {
	var b strings.Builder
	b.WriteString(`<table class="diff">` + "\n")
	for _, fp := range p.FilePatches() {
		from, to := fp.Files()
		b.WriteString(`<tr class="file-head"><td colspan="2">`)
		b.WriteString(template.HTMLEscapeString(fileHeader(from, to, fp.IsBinary())))
		b.WriteString("</td></tr>\n")
		if fp.IsBinary() {
			continue
		}
		for _, chunk := range fp.Chunks() {
			class, prefix := chunkStyle(chunk.Type())
			lines := strings.Split(strings.TrimSuffix(chunk.Content(), "\n"), "\n")
			for _, line := range lines {
				b.WriteString(`<tr class="` + class + `"><td>` + prefix + `</td><td>`)
				b.WriteString(template.HTMLEscapeString(line))
				b.WriteString("</td></tr>\n")
			}
		}
	}
	b.WriteString("</table>\n")
	return template.HTML(b.String())
}

func chunkStyle(op diff.Operation) (class, prefix string) {
	switch op {
	case diff.Add:
		return "diff-add", "+"
	case diff.Delete:
		return "diff-del", "-"
	default:
		return "diff-ctx", " "
	}
}

func fileHeader(from, to diff.File, binary bool) string {
	var name string
	switch {
	case from == nil && to != nil:
		name = "added " + to.Path()
	case from != nil && to == nil:
		name = "deleted " + from.Path()
	case from != nil && to != nil && from.Path() != to.Path():
		name = from.Path() + " → " + to.Path()
	case to != nil:
		name = to.Path()
	case from != nil:
		name = from.Path()
	}
	if binary {
		name += " (binary)"
	}
	return name
}