diff --git a/git/diff.go b/git/diff.go
new file mode 100644
index 0000000..f7e5b0e
--- /dev/null
+++ b/git/diff.go
@@ -0,0 +1,99 @@
+package git
+
+import (
+ "fmt"
+ "log"
+ "strings"
+
+ "github.com/bluekeyes/go-gitdiff/gitdiff"
+ "github.com/go-git/go-git/v5/plumbing/object"
+)
+
+type TextFragment struct {
+ Header string
+ Lines []gitdiff.Line
+}
+
+type Diff struct {
+ Name struct {
+ Old string
+ New string
+ }
+ TextFragments []TextFragment
+}
+
+// A nicer git diff representation.
+type NiceDiff struct {
+ Commit struct {
+ Message string
+ Author object.Signature
+ This string
+ Parent string
+ }
+ Stat struct {
+ FilesChanged int
+ Insertions int
+ Deletions int
+ }
+ Diff []Diff
+}
+
+func (g *GitRepo) Diff() (*NiceDiff, error) {
+ c, err := g.r.CommitObject(g.h)
+ if err != nil {
+ return nil, fmt.Errorf("commit object: %w", err)
+ }
+
+ var parent *object.Commit
+ if len(c.ParentHashes) > 0 {
+ parent, err = c.Parent(0)
+ if err != nil {
+ return nil, fmt.Errorf("getting parent: %w", err)
+ }
+ } else {
+ parent = c
+ }
+
+ patch, err := parent.Patch(c)
+ if err != nil {
+ return nil, fmt.Errorf("patch: %w", err)
+ }
+
+ diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
+ if err != nil {
+ log.Println(err)
+ }
+
+ nd := NiceDiff{}
+ nd.Commit.This = c.Hash.String()
+ nd.Commit.Parent = parent.Hash.String()
+ nd.Commit.Author = c.Author
+ nd.Commit.Message = c.Message
+ ndiff := Diff{}
+
+ for _, d := range diffs {
+ ndiff.Name.New = d.NewName
+ ndiff.Name.Old = d.OldName
+
+ for _, tf := range d.TextFragments {
+ ndiff.TextFragments = append(ndiff.TextFragments, TextFragment{
+ Header: tf.Header(),
+ Lines: tf.Lines,
+ })
+ for _, l := range tf.Lines {
+ switch l.Op {
+ case gitdiff.OpAdd:
+ nd.Stat.Insertions += 1
+ case gitdiff.OpDelete:
+ nd.Stat.Deletions += 1
+ }
+ }
+ }
+
+ nd.Diff = append(nd.Diff, ndiff)
+ }
+
+ nd.Stat.FilesChanged = len(diffs)
+
+ return &nd, nil
+}
diff --git a/go.mod b/go.mod
index 233095d..479bdda 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
+ github.com/bluekeyes/go-gitdiff v0.7.0 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
@@ -22,6 +23,7 @@ require (
github.com/pjbgf/sha1cd v0.2.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
+ github.com/sourcegraph/go-diff v0.6.1 // indirect
github.com/xanzy/ssh-agent v0.3.2 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/net v0.2.0 // indirect
diff --git a/go.sum b/go.sum
index f42bcab..768ba72 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/bluekeyes/go-gitdiff v0.7.0 h1:w4SrRFcufU0/tEpWx3VurDBAnWfpxsmwS7yWr14meQk=
+github.com/bluekeyes/go-gitdiff v0.7.0/go.mod h1:QpfYYO1E0fTVHVZAZKiRjtSGY9823iCdvGXBcEzHGbM=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
@@ -29,6 +31,7 @@ github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlK
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
github.com/go-git/go-git/v5 v5.5.0 h1:StO/ASRvk1Pp74tr7XQ0pQwKlCFignzzTF/NLKdQzUE=
github.com/go-git/go-git/v5 v5.5.0/go.mod h1:g456XI30HAdt7GQtIf8JR6GDAdULGaR4KtfFtQa0uTg=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
@@ -56,9 +59,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
+github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ=
+github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -115,6 +122,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/routes/handler.go b/routes/handler.go
index 5a1cff4..2625b43 100644
--- a/routes/handler.go
+++ b/routes/handler.go
@@ -12,5 +12,6 @@ func Handlers(c *config.Config) *flow.Mux {
mux.HandleFunc("/:name/tree/:ref/...", d.RepoTree, "GET")
mux.HandleFunc("/:name/blob/:ref/...", d.FileContent, "GET")
mux.HandleFunc("/:name/log/:ref", d.Log, "GET")
+ mux.HandleFunc("/:name/commit/:ref", d.Diff, "GET")
return mux
}
diff --git a/routes/routes.go b/routes/routes.go
index f1eef06..f7a36c6 100644
--- a/routes/routes.go
+++ b/routes/routes.go
@@ -127,3 +127,39 @@ func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
return
}
}
+
+func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
+ name := flow.Param(r.Context(), "name")
+ ref := flow.Param(r.Context(), "ref")
+
+ path := filepath.Join(d.c.Git.ScanPath, name+".git")
+ gr, err := git.Open(path, ref)
+ if err != nil {
+ Write404(w, *d.c)
+ return
+ }
+
+ diff, err := gr.Diff()
+ if err != nil {
+ Write500(w, *d.c)
+ log.Println(err)
+ return
+ }
+
+ tpath := filepath.Join(d.c.Template.Dir, "*")
+ t := template.Must(template.ParseGlob(tpath))
+
+ data := make(map[string]interface{})
+
+ data["commit"] = diff.Commit
+ data["stat"] = diff.Stat
+ data["diff"] = diff.Diff
+ data["meta"] = d.c.Meta
+ data["name"] = name
+ data["ref"] = ref
+
+ if err := t.ExecuteTemplate(w, "commit", data); err != nil {
+ log.Println(err)
+ return
+ }
+}
diff --git a/templates/commit.html b/templates/commit.html
new file mode 100644
index 0000000..e6af551
--- /dev/null
+++ b/templates/commit.html
@@ -0,0 +1,52 @@
+{{ define "commit" }}
+
+{{ template "head" . }}
+
+
+ {{ .meta.Title }}
+ {{ .meta.Description }}
+
+
+ {{ template "nav" . }}
+
+
+ author: {{ .commit.Author.Name }} <{{ .commit.Author.Email}}> on {{ .commit.Author.When }}
+ commit:
+ {{ .commit.This }}
+
+
+ parent:
+ {{ .commit.Parent }}
+
+
+ {{ .stat.FilesChanged }} files changed,
+ {{ .stat.Insertions }} insertions(+),
+ {{ .stat.Deletions }} deletions(-)
+
+
+
+ {{ range .diff }}
+ {{ if .Name.Old }}
+ {{ .Name.Old }} → {{ .Name.New }}
+ {{ else }}
+ {{.Name.New }}
+ {{- end -}}
+
+ {{- range .TextFragments -}}
+ {{- .Header -}}
+ {{- range .Lines -}}
+ {{- if eq .Op.String "+" -}}
+ {{ .String }}
+ {{- end -}}
+ {{- if eq .Op.String "-" -}}
+ {{ .String }}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+
+ {{ end }}
+
+
+
+
+{{ end }}