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 }}