gitshow

Log | Files | Refs

commit 65e269983829831f6869baf213f3675ca2bb6dc5
parent ee7071298a8dc215d7113b95656387e176ca2484
Author: Thomas Vigouroux <thomas.vigouroux@univ-grenoble-alpes.fr>
Date:   Wed, 10 Apr 2024 21:16:40 +0200

feat: add commit-level handlings

Diffstat:
Acommit.go | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfile.go | 20++++++--------------
Mgitshow.go | 2++
Mstatic/style.css | 42++++++++++++++++++++++++------------------
Ctemplates/file.html -> templates/commit.html | 0
Mtemplates/file.html | 2+-
Mtemplates/log.html | 3++-
Mtemplates/tree.html | 6+++---
Mtemplates/wrap.html | 2+-
Mtree.go | 40++++++++++++++++++++++------------------
10 files changed, 192 insertions(+), 56 deletions(-)

diff --git a/commit.go b/commit.go @@ -0,0 +1,131 @@ +package main + +import ( + "github.com/gorilla/mux" + "github.com/libgit2/git2go/v31" + "log" + "net/http" + // "path/filepath" + // "strings" +) + +type DeltaAction int + +const ( + DeltaActionCreated DeltaAction = iota + DeltaActionChanged + DeltaActionDeleted + DeltaActionMoved +) + +type DeltaData struct { + NewFile string + OldFile string + Action DeltaAction +} + +type CommitPageData struct { + Name string + Deltas []DeltaData +} + +func getCommit(w http.ResponseWriter, r *http.Request) { + logger := log.Default() + vars := mux.Vars(r) + reponame := vars["repository"] + + repo, err := checkRepo(reponame) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + defer repo.Free() + + obj, err := repo.RevparseSingle(vars["hash"]) + if err != nil { + logger.Printf("Object not found: %s", vars["hash"]) + w.WriteHeader(http.StatusNotFound) + return + } + defer obj.Free() + + commit, err := obj.AsCommit() + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + defer commit.Free() + + if commit.ParentCount() != 1 { + w.WriteHeader(http.StatusInternalServerError) + logger.Print("TODO: initial commit and merge commit") + return + } + + ctree, err := commit.Tree() + if err != nil { + logger.Fatal(err) + } + defer ctree.Free() + + parent := commit.Parent(0) + defer parent.Free() + + ptree, err := parent.Tree() + if err != nil { + logger.Fatal(err) + } + defer ptree.Free() + + parentid, err := parent.ShortId() + if err != nil { + logger.Fatal(err) + } + + commitid, err := parent.ShortId() + if err != nil { + logger.Fatal(err) + } + + diff, err := repo.DiffTreeToTree(ptree, ctree, &git.DiffOptions{ + Flags: git.DiffIgnoreWhitespaceChange, + MaxSize: 2000, + ContextLines: 5, + OldPrefix: parentid, + NewPrefix: commitid, + }) + if err != nil { + logger.Fatal(err) + } + defer diff.Free() + + deltadatas := make([]DeltaData, 0) + + diff.ForEach(func(delta git.DiffDelta, progress float64) (git.DiffForEachHunkCallback, error) { + deltadata := DeltaData { + NewFile: delta.NewFile.Path, + OldFile: delta.OldFile.Path, + } + + if delta.OldFile.Oid.IsZero() { + deltadata.Action = DeltaActionCreated + } else if delta.NewFile.Oid.IsZero() { + deltadata.Action = DeltaActionDeleted + } else if delta.NewFile.Path == delta.OldFile.Path { + deltadata.Action = DeltaActionChanged + } else { + deltadata.Action = DeltaActionMoved + } + + deltadatas = append(deltadatas, deltadata) + + logger.Print("Delta", deltadata) + return func(hunk git.DiffHunk) (git.DiffForEachLineCallback, error) { + logger.Print("Hunk", hunk.Header) + return func(line git.DiffLine) error { + logger.Print("Line", line) + return nil + }, nil + }, nil + }, git.DiffDetailHunks) +} diff --git a/file.go b/file.go @@ -19,7 +19,7 @@ import ( type FilePageData struct { Name string Content template.HTML - Branch *git.Branch + Ref *git.Oid } func getFile(w http.ResponseWriter, r *http.Request) { @@ -33,24 +33,16 @@ func getFile(w http.ResponseWriter, r *http.Request) { return } - var branch *git.Branch - branch, err = repo.LookupBranch(vars["ref"], git.BranchAll) + ref, err := repo.RevparseSingle(vars["ref"]) if err != nil { w.WriteHeader(http.StatusNotFound) return } - logger.Printf("Called %s: ref to %s", vars["ref"], branch.Shorthand()) - - // Now get the corresponding commit - ref, err := branch.Resolve() - if err != nil { - logger.Fatalf("Malformed repository") - } - - commit, err := repo.LookupCommit(ref.Target()) + commit, err := ref.AsCommit() if err != nil { - logger.Fatalf("Could not get commit") + w.WriteHeader(http.StatusNotFound) + return } tree, err := commit.Tree() @@ -112,7 +104,7 @@ func getFile(w http.ResponseWriter, r *http.Request) { data := FilePageData { Name: reponame, Content: template.HTML(sbuilder.String()), - Branch: branch, + Ref: ref.Id(), } if ishtmx { diff --git a/gitshow.go b/gitshow.go @@ -64,6 +64,8 @@ func main() { reporouter.PathPrefix("/file/{ref:[a-z-A-Z0-9]+}/").HandlerFunc(getFile).Methods("GET").Name("file") + reporouter.HandleFunc("/commit/{hash:[a-f0-9]+}", getCommit).Methods("GET") + srv := &http.Server{ Handler: r, Addr: "127.0.0.1:8080", diff --git a/static/style.css b/static/style.css @@ -11,14 +11,13 @@ --chroma-const: var(--color-lightgreen); } -/* Tree page */ -.tree-file { +#reponame { cursor: pointer; } -.tree-file:hover { - transition: 0.1s; - color: var(--color-purple); +/* Tree page */ +.tree-file { + cursor: pointer; } .tree-kind { @@ -28,32 +27,44 @@ /* Commit page */ .commit-hash { + cursor: pointer; color: gray; padding-right: 20px; } /* Inspired from HTMX tab example */ #tab-list { - padding-right: 80%; - display: flex; border-bottom: solid 3px lightgray; } #tab-list button { - flex: 1; border: none; - margin: 0 10px; padding: 5px 10px; cursor: pointer; } -#tab-list button:hover { +#tab-list button.selected { + background: lightgray; +} + +/* Various things that hover */ + +.tree-file:hover, +.commit-hash:hover, +#tab-list button:hover, +.chroma .lnlinks:hover, +#reponame:hover +{ + transition: 0.2s; color: var(--color-purple); - transition: 0.1s; } -#tab-list button.selected { - background: lightgray; +.tree-file, +.commit-hash, +#tab-list button, +.chroma .lnlinks, +#reponame { + transition: 0.2s; } /* Chroma related styles for file highlighting */ @@ -63,11 +74,6 @@ color: gray; } -.chroma .lnlinks:hover { - color: var(--color-purple); - transition: 0.1s; -} - /* This allows to easily see the link that has a link to it */ .chroma span.line:has(span.ln:target) { background-color: var(--color-lightpurple); diff --git a/templates/file.html b/templates/commit.html diff --git a/templates/file.html b/templates/file.html @@ -2,7 +2,7 @@ <div id="tab-list" role="tablist" hx-push-url="true"> <button hx-get="/repos/{{.Name}}">about</button> <button hx-get="/repos/{{.Name}}/log">log</button> - <button hx-get="/repos/{{.Name}}/tree/{{.Branch.Shorthand}}">tree</button> + <button hx-get="/repos/{{.Name}}/tree/{{.Ref.String}}">tree</button> </div> {{.Content}} {{end}} diff --git a/templates/log.html b/templates/log.html @@ -4,10 +4,11 @@ <button hx-get="/repos/{{.Name}}/log" class="selected">log</button> <button hx-get="/repos/{{.Name}}/tree">tree</button> </div> +{{$repoName := .Name}} <table> {{range .Commits}} <tr> - <td class="commit-hash">{{.ShortId}}</td> + <td class="commit-hash" hx-push-url=true hx-get="/repos/{{$repoName}}/commit/{{.Id.String}}">{{.ShortId}}</td> <td>{{.Message}}</td> </tr> {{end}} diff --git a/templates/tree.html b/templates/tree.html @@ -2,15 +2,15 @@ <div id="tab-list" role="tablist" hx-push-url="true"> <button hx-get="/repos/{{.Name}}">about</button> <button hx-get="/repos/{{.Name}}/log">log</button> - <button hx-get="/repos/{{.Name}}/tree/{{.Branch.Shorthand}}" class="selected">tree</button> + <button hx-get="/repos/{{.Name}}/tree/{{.Ref.String}}" class="selected">tree</button> </div> {{$repoName:=.Name}} -{{$branchName:=.Branch.Shorthand}} +{{$branchName:=.Ref.String}} <table> {{range .Entries}} <tr> <td class="tree-kind">{{if isFile .}}F{{else}}D{{end}}</div> - <td><div{{if isFile .}} hx-push-url="true" class="tree-file" hx-get="/repos/{{$repoName}}/file/{{$branchName}}/{{.Name}}"{{end}}>{{.Name}}</div></td> + <td{{if isFile .}} hx-push-url="true" class="tree-file" hx-get="/repos/{{$repoName}}/file/{{$branchName}}/{{.Name}}"{{end}}>{{.Name}}</td> </tr> {{end}} </table> diff --git a/templates/wrap.html b/templates/wrap.html @@ -6,7 +6,7 @@ </head> <body> - <h1>{{.Name}}</h1> + <h1 id="reponame" hx-push-url="true" hx-get="/repos/{{.Name}}" hx-swap="innerHTML" hx-target="#tabs">{{.Name}}</h1> <div id="tabs" hx-target="#tabs" hx-swap="innerHTML"> {{end}} diff --git a/tree.go b/tree.go @@ -13,7 +13,7 @@ import ( type TreePageData struct { Name string Entries []*git.TreeEntry - Branch *git.Branch + Ref *git.Oid } func getTree(w http.ResponseWriter, r *http.Request) { @@ -27,7 +27,7 @@ func getTree(w http.ResponseWriter, r *http.Request) { return } - var branch *git.Branch + var commit *git.Commit refname, present := vars["ref"] if !present { @@ -38,33 +38,37 @@ func getTree(w http.ResponseWriter, r *http.Request) { return } - branch = head.Branch() + branch := head.Branch() + // Now get the corresponding commit + ref, err := branch.Resolve() + if err != nil { + logger.Fatalf("Malformed repository") + } + + commit, err = repo.LookupCommit(ref.Target()) + if err != nil { + logger.Fatalf("Could not get commit") + } } else { - branch, err = repo.LookupBranch(refname, git.BranchAll) + obj, err := repo.RevparseSingle(refname) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + commit, err = obj.AsCommit() if err != nil { w.WriteHeader(http.StatusNotFound) return } } - logger.Printf("Called %s: ref to %s", vars["ref"], branch.Shorthand()) - - // Now get the corresponding commit - ref, err := branch.Resolve() - if err != nil { - logger.Fatalf("Malformed repository") - } - - commit, err := repo.LookupCommit(ref.Target()) - if err != nil { - logger.Fatalf("Could not get commit") - } + logger.Printf("Called %s: ref to %s", refname, commit.Id().String()) tree, err := commit.Tree() if err != nil { logger.Fatalf("Could not get tree") } - url, err := reporouter.Get("tree").URL("repository", reponame, "ref", branch.Shorthand()) + url, err := reporouter.Get("tree").URL("repository", reponame, "ref", commit.Id().String()) if err != nil { logger.Fatalf("Could not generate URL") } @@ -100,7 +104,7 @@ func getTree(w http.ResponseWriter, r *http.Request) { data := TreePageData{ Name: reponame, Entries: entries, - Branch: branch, + Ref: commit.Id(), } if ishtmx {