Author | Jakob Wakeling <[email protected]> |
Date | 2023-07-24 09:56:07 |
Commit | 64718e1cdc44c70854777e66c08401558ff0999c |
Parent | 583a0660cb480978c9a329575d7ee8f78504f647 |
Refine repository log page
Diffstat
M | main.go | | | 22 | ++++++++++++---------- |
M | res/admin/index.html | | | 2 | +- |
M | res/repo/log.html | | | 48 | +++++++++++++++++++++++++++--------------------- |
M | src/admin.go | | | 12 | ++++++------ |
M | src/goit.go | | | 2 | ++ |
M | src/http.go | | | 36 | ++++++++++++++++++------------------ |
M | src/repo.go | | | 87 | ++++++++++++++++++++++--------------------------------------------------------- |
A | src/repo/log.go | | | 101 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/repo/repo.go | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
M | src/user.go | | | 4 | ++-- |
10 files changed, 233 insertions, 120 deletions
diff --git a/main.go b/main.go index 4783b59..9e714e5 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "github.com/Jamozed/Goit/res" goit "github.com/Jamozed/Goit/src" + "github.com/Jamozed/Goit/src/repo" "github.com/gorilla/mux" ) @@ -29,6 +30,7 @@ func main() { h.Path("/user/logout").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogout) h.Path("/user/sessions").Methods("GET", "POST").HandlerFunc(goit.HandleUserSessions) h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(goit.HandleRepoCreate) + h.Path("/repo/delete").Methods("DELETE").HandlerFunc(repo.HandleDelete) h.Path("/admin").Methods("GET").HandlerFunc(goit.HandleAdminIndex) h.Path("/admin/users").Methods("GET").HandlerFunc(goit.HandleAdminUsers) h.Path("/admin/user/create").Methods("GET", "POST").HandlerFunc(goit.HandleAdminUserCreate) @@ -36,17 +38,17 @@ func main() { h.Path("/admin/repos").Methods("GET").HandlerFunc(goit.HandleAdminRepos) h.Path("/admin/repo/edit").Methods("GET", "POST").HandlerFunc(goit.HandleAdminRepoEdit) - h.Path("/{repo:.+(?:\\.git)$}").Methods(http.MethodGet).HandlerFunc(redirectDotGit) - h.Path("/{repo}").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoLog) - h.Path("/{repo}/log").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoLog) - h.Path("/{repo}/tree").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoTree) - h.Path("/{repo}/refs").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoRefs) - h.Path("/{repo}/info/refs").Methods(http.MethodGet).HandlerFunc(goit.HandleInfoRefs) - h.Path("/{repo}/git-upload-pack").Methods(http.MethodPost).HandlerFunc(goit.HandleUploadPack) - h.Path("/{repo}/git-receive-pack").Methods(http.MethodPost).HandlerFunc(goit.HandleReceivePack) + h.Path("/{repo:.+(?:\\.git)$}").Methods("GET").HandlerFunc(redirectDotGit) + h.Path("/{repo}").Methods("GET").HandlerFunc(repo.HandleLog) + h.Path("/{repo}/log").Methods("GET").HandlerFunc(repo.HandleLog) + h.Path("/{repo}/tree").Methods("GET").HandlerFunc(goit.HandleRepoTree) + h.Path("/{repo}/refs").Methods("GET").HandlerFunc(goit.HandleRepoRefs) + h.Path("/{repo}/info/refs").Methods("GET").HandlerFunc(goit.HandleInfoRefs) + h.Path("/{repo}/git-upload-pack").Methods("POST").HandlerFunc(goit.HandleUploadPack) + h.Path("/{repo}/git-receive-pack").Methods("POST").HandlerFunc(goit.HandleReceivePack) - h.Path("/static/style.css").Methods(http.MethodGet).HandlerFunc(handleStyle) - h.Path("/static/favicon.png").Methods(http.MethodGet).HandlerFunc(handleFavicon) + h.Path("/static/style.css").Methods("GET").HandlerFunc(handleStyle) + h.Path("/static/favicon.png").Methods("GET").HandlerFunc(handleFavicon) h.PathPrefix("/").HandlerFunc(goit.HttpNotFound) diff --git a/res/admin/index.html b/res/admin/index.html index 257aed7..c2f95de 100644 --- a/res/admin/index.html +++ b/res/admin/index.html @@ -10,7 +10,7 @@ </tr> <tr><td> <a href="/admin/users">Users</a> - | <a href="/admin/repos">Repos</a> + | <a href="/admin/repos">Repositories</a> </td></tr> </table> </body> diff --git a/res/repo/log.html b/res/repo/log.html index 155a31b..6dad3e7 100644 --- a/res/repo/log.html +++ b/res/repo/log.html @@ -3,27 +3,33 @@ <body> <header>{{template "repo/header" .}}</header><hr> <main> - {{if .Commits}} - <table> - <thead> - <tr> - <td><b>Date</b></td> - <td><b>Message</b></td> - <td><b>Author</b></td> - </tr> - </thead> - <tbody> - {{range .Commits}} - <tr> - <td>{{.Date}}</a></td> - <td>{{.Message}}</td> - <td>{{.Author}}</td> - </tr> + <table> + <thead> + <tr> + <td><b>Date</b></td> + <td><b>Message</b></td> + <td><b>Author</b></td> + <td><b>Files</b></td> + <td><b>+</b></td> + <td><b>-</b></td> + </tr> + </thead> + <tbody> + {{if .Commits}} + {{range .Commits}} + <tr> + <td>{{.Date}}</a></td> + <td><a href="/{{$.Name}}/commit/{{.Hash}}">{{.Message}}</a></td> + <td>{{.Author}}</td> + <td align="right">{{.Files}}</td> + <td align="right" style="color: #008800">{{.Additions}}</td> + <td align="right" style="color: #AA0000">{{.Deletions}}</td> + </tr> + {{end}} + {{else}} + <tr><td colspan="6">No commits</td></tr> {{end}} - </tbody> - </table> - {{else}} - <span>No commits</span> - {{end}} + </tbody> + </table> </main> </body> diff --git a/src/admin.go b/src/admin.go index 9b98ca7..3af6822 100644 --- a/src/admin.go +++ b/src/admin.go @@ -21,7 +21,7 @@ func HandleAdminIndex(w http.ResponseWriter, r *http.Request) { return } - if err := tmpl.ExecuteTemplate(w, "admin/index", struct{ Title string }{"Admin"}); err != nil { + if err := Tmpl.ExecuteTemplate(w, "admin/index", struct{ Title string }{"Admin"}); err != nil { log.Println("[/admin/index]", err.Error()) } } @@ -66,7 +66,7 @@ func HandleAdminUsers(w http.ResponseWriter, r *http.Request) { return } - if err := tmpl.ExecuteTemplate(w, "admin/users", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "admin/users", data); err != nil { log.Println("[/admin/users]", err.Error()) } } @@ -111,7 +111,7 @@ func HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) { } } - if err := tmpl.ExecuteTemplate(w, "admin/user/create", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "admin/user/create", data); err != nil { log.Println("[/admin/user/create]", err.Error()) } } @@ -189,7 +189,7 @@ func HandleAdminUserEdit(w http.ResponseWriter, r *http.Request) { } } - if err := tmpl.ExecuteTemplate(w, "admin/user/edit", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "admin/user/edit", data); err != nil { log.Println("[/admin/user/edit]", err.Error()) } } @@ -244,7 +244,7 @@ func HandleAdminRepos(w http.ResponseWriter, r *http.Request) { return } - if err := tmpl.ExecuteTemplate(w, "admin/repos", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "admin/repos", data); err != nil { log.Println("[/admin/repos]", err.Error()) } } @@ -314,7 +314,7 @@ func HandleAdminRepoEdit(w http.ResponseWriter, r *http.Request) { } } - if err := tmpl.ExecuteTemplate(w, "admin/repo/edit", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "admin/repo/edit", data); err != nil { log.Println("[/admin/repo/edit]", err.Error()) } } diff --git a/src/goit.go b/src/goit.go index b71c9a1..f9ff03d 100644 --- a/src/goit.go +++ b/src/goit.go @@ -23,6 +23,7 @@ type Config struct { HttpPort string `json:"http_port"` GitPath string `json:"git_path"` IpSessions bool `json:"ip_sessions"` + UsesHttps bool `json:"uses_https"` } var Conf = Config{ @@ -31,6 +32,7 @@ var Conf = Config{ HttpPort: "8080", GitPath: "git", IpSessions: true, + UsesHttps: false, } var db *sql.DB diff --git a/src/http.go b/src/http.go index 4fc6e78..76722f5 100644 --- a/src/http.go +++ b/src/http.go @@ -12,35 +12,35 @@ import ( "github.com/Jamozed/Goit/res" ) -var tmpl = template.Must(template.New("error").Parse(res.Error)) +var Tmpl = template.Must(template.New("error").Parse(res.Error)) func init() { - template.Must(tmpl.New("index").Parse(res.Index)) - template.Must(tmpl.New("base/head").Parse(res.BaseHead)) - - template.Must(tmpl.New("admin/index").Parse(res.AdminIndex)) - template.Must(tmpl.New("admin/users").Parse(res.AdminUsers)) - template.Must(tmpl.New("admin/user/create").Parse(res.AdminUserCreate)) - template.Must(tmpl.New("admin/user/edit").Parse(res.AdminUserEdit)) - template.Must(tmpl.New("admin/repos").Parse(res.AdminRepos)) - template.Must(tmpl.New("admin/repo/edit").Parse(res.AdminRepoEdit)) - - template.Must(tmpl.New("user/header").Parse(res.UserHeader)) - template.Must(tmpl.New("user/login").Parse(res.UserLogin)) - template.Must(tmpl.New("user/sessions").Parse(res.UserSessions)) - - template.Must(tmpl.New("repo/header").Parse(res.RepoHeader)) - template.Must(tmpl.New("repo/create").Parse(res.RepoCreate)) - - template.Must(tmpl.New("repo/log").Parse(res.RepoLog)) - template.Must(tmpl.New("repo/tree").Parse(res.RepoTree)) - template.Must(tmpl.New("repo/refs").Parse(res.RepoRefs)) + template.Must(Tmpl.New("index").Parse(res.Index)) + template.Must(Tmpl.New("base/head").Parse(res.BaseHead)) + + template.Must(Tmpl.New("admin/index").Parse(res.AdminIndex)) + template.Must(Tmpl.New("admin/users").Parse(res.AdminUsers)) + template.Must(Tmpl.New("admin/user/create").Parse(res.AdminUserCreate)) + template.Must(Tmpl.New("admin/user/edit").Parse(res.AdminUserEdit)) + template.Must(Tmpl.New("admin/repos").Parse(res.AdminRepos)) + template.Must(Tmpl.New("admin/repo/edit").Parse(res.AdminRepoEdit)) + + template.Must(Tmpl.New("user/header").Parse(res.UserHeader)) + template.Must(Tmpl.New("user/login").Parse(res.UserLogin)) + template.Must(Tmpl.New("user/sessions").Parse(res.UserSessions)) + + template.Must(Tmpl.New("repo/header").Parse(res.RepoHeader)) + template.Must(Tmpl.New("repo/create").Parse(res.RepoCreate)) + + template.Must(Tmpl.New("repo/log").Parse(res.RepoLog)) + template.Must(Tmpl.New("repo/tree").Parse(res.RepoTree)) + template.Must(Tmpl.New("repo/refs").Parse(res.RepoRefs)) } func HttpError(w http.ResponseWriter, code int) { w.WriteHeader(code) s := fmt.Sprint(code) + " " + http.StatusText(code) - tmpl.ExecuteTemplate(w, "error", struct{ Status string }{s}) + Tmpl.ExecuteTemplate(w, "error", struct{ Status string }{s}) } func HttpNotFound(w http.ResponseWriter, r *http.Request) { diff --git a/src/repo.go b/src/repo.go index 67b5a03..28c6169 100644 --- a/src/repo.go +++ b/src/repo.go @@ -12,12 +12,10 @@ import ( "os" "path/filepath" "strings" - "time" "github.com/Jamozed/Goit/src/util" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" "github.com/gorilla/mux" ) @@ -79,7 +77,7 @@ func HandleIndex(w http.ResponseWriter, r *http.Request) { return } - if err := tmpl.ExecuteTemplate(w, "index", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "index", data); err != nil { log.Println("[/]", err.Error()) } } @@ -123,60 +121,11 @@ func HandleRepoCreate(w http.ResponseWriter, r *http.Request) { } } - if err := tmpl.ExecuteTemplate(w, "repo/create", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "repo/create", data); err != nil { log.Println("[/repo/create]", err.Error()) } } -func HandleRepoLog(w http.ResponseWriter, r *http.Request) { - reponame := mux.Vars(r)["repo"] - - repo, err := GetRepoByName(reponame) - if err != nil { - HttpError(w, http.StatusInternalServerError) - return - } else if repo == nil { - HttpError(w, http.StatusNotFound) - return - } - - type row struct{ Date, Message, Author string } - commits := []row{} - - if gr, err := git.PlainOpen(RepoPath(reponame)); err != nil { - log.Println("[Repo:Log]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if ref, err := gr.Head(); err != nil { - if !errors.Is(err, plumbing.ErrReferenceNotFound) { - log.Println("[Repo:Log]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - } else if iter, err := gr.Log(&git.LogOptions{From: ref.Hash()}); err != nil { - log.Println("[Repo:Log]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if err := iter.ForEach(func(c *object.Commit) error { - commits = append(commits, row{c.Author.When.UTC().Format(time.DateTime), strings.SplitN(c.Message, "\n", 2)[0], c.Author.Name}) - return nil - }); err != nil { - log.Println("[Repo:Log]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - - if err := tmpl.ExecuteTemplate(w, "repo/log", struct { - Title, Name, Description, Url string - Readme, Licence string - Commits []row - }{ - "Log", reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", "", "", commits, - }); err != nil { - log.Println("[Repo:Log]", err.Error()) - } -} - func HandleRepoTree(w http.ResponseWriter, r *http.Request) { HttpError(w, http.StatusNoContent) } @@ -226,13 +175,14 @@ func HandleRepoRefs(w http.ResponseWriter, r *http.Request) { return } - if err := tmpl.ExecuteTemplate(w, "repo/refs", struct { + if err := Tmpl.ExecuteTemplate(w, "repo/refs", struct { Title, Name, Description, Url string Readme, Licence string Branches []bra Tags []tag }{ - "Refs", reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", "", "", bras, tags, + "Refs", reponame, repo.Description, util.If(Conf.UsesHttps, "https://", "http://") + r.Host + r.URL.Path, "", + "", bras, tags, }); err != nil { log.Println("[Repo:Refs]", err.Error()) } @@ -246,9 +196,9 @@ func GetRepo(id int64) (*Repo, error) { ).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.IsPrivate); err != nil { if !errors.Is(err, sql.ErrNoRows) { return nil, err - } else { - return nil, nil } + + return nil, nil } else { return r, nil } @@ -257,13 +207,14 @@ func GetRepo(id int64) (*Repo, error) { func GetRepoByName(name string) (*Repo, error) { r := &Repo{} - err := db.QueryRow( + if err := db.QueryRow( "SELECT id, owner_id, name, description, default_branch, is_private FROM repos WHERE name = ?", name, - ).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.IsPrivate) - if errors.Is(err, sql.ErrNoRows) { + ).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.IsPrivate); err != nil { + if !errors.Is(err, sql.ErrNoRows) { + return nil, err + } + return nil, nil - } else if err != nil { - return nil, err } return r, nil @@ -314,3 +265,15 @@ func RepoSize(name string) (uint64, error) { return uint64(size), err } + +func DelRepo(name string) error { + if err := os.RemoveAll(RepoPath(name)); err != nil { + return err + } + + if _, err := db.Exec("DELETE FROM repos WHERE name = ?", name); err != nil { + return err + } + + return nil +} diff --git a/src/repo/log.go b/src/repo/log.go new file mode 100644 index 0000000..d2a98a1 --- /dev/null +++ b/src/repo/log.go @@ -0,0 +1,101 @@ +package repo + +import ( + "fmt" + "log" + "net/http" + "strconv" + "strings" + "time" + + goit "github.com/Jamozed/Goit/src" + "github.com/Jamozed/Goit/src/util" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/gorilla/mux" +) + +func HandleLog(w http.ResponseWriter, r *http.Request) { + _, uid := goit.AuthCookie(w, r, true) + + repo, err := goit.GetRepoByName(mux.Vars(r)["repo"]) + if err != nil { + goit.HttpError(w, http.StatusInternalServerError) + return + } else if repo == nil || (repo.IsPrivate && repo.OwnerId != uid) { + goit.HttpError(w, http.StatusNotFound) + return + } + + var offset uint64 = 0 + if o := r.URL.Query().Get("o"); o != "" { + if i, err := strconv.ParseUint(o, 10, 64); err != nil { + goit.HttpError(w, http.StatusBadRequest) + return + } else { + offset = i + } + } + + offset += 1 + + type row struct{ Hash, Date, Message, Author, Files, Additions, Deletions string } + data := struct { + Title, Name, Description, Url string + Readme, Licence string + Commits []row + }{ + Title: repo.Name + " - Log", Name: repo.Name, + Description: repo.Description, Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + r.URL.Path, + } + + gr, err := git.PlainOpen(goit.RepoPath(repo.Name)) + if err != nil { + log.Println("[/repo/log]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + ref, err := gr.Head() + if err != nil { + log.Println("[/repo/log]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + iter, err := gr.Log(&git.LogOptions{From: ref.Hash()}) + if err != nil { + log.Println("[/repo/log]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + if err := iter.ForEach(func(c *object.Commit) error { + var files, additions, deletions int + + if stats, err := c.Stats(); err != nil { + log.Println("[/repo/log]", err.Error()) + } else { + files = len(stats) + for _, s := range stats { + additions += s.Addition + deletions += s.Deletion + } + } + + data.Commits = append(data.Commits, row{ + c.Hash.String(), c.Author.When.UTC().Format(time.DateTime), strings.SplitN(c.Message, "\n", 2)[0], + c.Author.Name, fmt.Sprint(files), "+" + fmt.Sprint(additions), "-" + fmt.Sprint(deletions), + }) + + return nil + }); err != nil { + log.Println("[/repo/log]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + if err := goit.Tmpl.ExecuteTemplate(w, "repo/log", data); err != nil { + log.Println("[/repo/log]", err.Error()) + } +} diff --git a/src/repo/repo.go b/src/repo/repo.go new file mode 100644 index 0000000..3299296 --- /dev/null +++ b/src/repo/repo.go @@ -0,0 +1,39 @@ +package repo + +import ( + "log" + "net/http" + "strconv" + + goit "github.com/Jamozed/Goit/src" +) + +func HandleDelete(w http.ResponseWriter, r *http.Request) { + auth, admin, uid := goit.AuthCookieAdmin(w, r, true) + + rid, err := strconv.ParseInt(r.URL.Query().Get("repo"), 10, 64) + if err != nil { + goit.HttpError(w, http.StatusNotFound) + return + } + + repo, err := goit.GetRepo(rid) + if err != nil { + log.Println("[/repo/delete]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + if !auth || (uid != repo.OwnerId && !admin) { + goit.HttpError(w, http.StatusUnauthorized) + return + } + + if err := goit.DelRepo(repo.Name); err != nil { + log.Println("[/repo/delete]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusFound) +} diff --git a/src/user.go b/src/user.go index fc967c8..4d0ee24 100644 --- a/src/user.go +++ b/src/user.go @@ -73,7 +73,7 @@ func HandleUserLogin(w http.ResponseWriter, r *http.Request) { } } - if err := tmpl.ExecuteTemplate(w, "user/login", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "user/login", data); err != nil { log.Println("[/user/login]", err.Error()) } } @@ -107,7 +107,7 @@ func HandleUserSessions(w http.ResponseWriter, r *http.Request) { }) } - if err := tmpl.ExecuteTemplate(w, "user/sessions", data); err != nil { + if err := Tmpl.ExecuteTemplate(w, "user/sessions", data); err != nil { log.Println("[/user/login]", err.Error()) } }