fierj
PublicTiny personal git forge
package main
import (
"fmt"
"html/template"
"io"
"log/slog"
"net/http"
"strings"
)
func PatchesList(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
state := r.URL.Query().Get("state")
if state == "" {
state = "open"
}
ref := git.DefaultBranch()
rp := repoPath(cfg, git.Name)
patches, _ := listPatches(rp, state)
tmpl.ExecuteTemplate(w, "patch_list.html", withUser(r, map[string]any{
"Repo": git.Repo(),
"IsPrivate": git.IsPrivate(),
"Ref": ref,
"Patches": patches,
"State": state,
"FreeBranches": nonDefaultBranches(rp),
"Description": git.Description(),
"Branches": git.Branches(),
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "patches",
}))
}
}
func PatchNewGet(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
ref := git.DefaultBranch()
rp := repoPath(cfg, git.Name)
tmpl.ExecuteTemplate(w, "patch_new.html", withUser(r, map[string]any{
"Repo": git.Repo(),
"IsPrivate": git.IsPrivate(),
"Ref": ref,
"FreeBranches": nonDefaultBranches(rp),
"Description": git.Description(),
"Branches": git.Branches(),
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "patches",
}))
}
}
func PatchNewPost(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
title := strings.TrimSpace(r.FormValue("title"))
if title == "" {
renderError(w, r, tmpl, http.StatusBadRequest, "Title is required")
return
}
body := strings.TrimSpace(r.FormValue("body"))
author, authorName := threadAuthor(r)
rp := repoPath(cfg, git.Name)
var p *Patch
var err error
branch := strings.TrimSpace(r.FormValue("branch"))
if branch != "" {
p, err = createPatchFromBranch(rp, branch, title, body, author, authorName)
} else {
// Try file upload
file, header, ferr := r.FormFile("patch_file")
if ferr != nil {
renderError(w, r, tmpl, http.StatusBadRequest, "Either a branch or a .patch file is required")
return
}
defer file.Close()
patchContent, rerr := io.ReadAll(file)
if rerr != nil || len(patchContent) == 0 {
renderError(w, r, tmpl, http.StatusBadRequest, "Failed to read patch file")
return
}
_ = header
p, err = createPatchFromFile(rp, title, body, author, authorName, patchContent)
}
if err != nil {
renderError(w, r, tmpl, http.StatusInternalServerError, err.Error())
return
}
go deliverPatchCreate(cfg, git.Name, p)
http.Redirect(w, r, fmt.Sprintf("/%s/patches/%s", git.Name, p.ID), http.StatusSeeOther)
}
}
func PatchView(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
patchID := r.PathValue("patchId")
rp := repoPath(cfg, git.Name)
p, err := loadPatch(rp, patchID)
if err != nil {
renderError(w, r, tmpl, http.StatusNotFound, "Patch not found")
return
}
diff, _ := patchDiff(rp, p)
ref := git.DefaultBranch()
tmpl.ExecuteTemplate(w, "patch_view.html", withUser(r, map[string]any{
"Repo": git.Repo(),
"IsPrivate": git.IsPrivate(),
"Ref": ref,
"Patch": p,
"Diff": diff,
"Description": git.Description(),
"Branches": git.Branches(),
"Tags": git.Tags(),
"Commits": git.CommitCount(ref),
"ActiveTab": "patches",
}))
}
}
func PatchMergePost(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
patchID := r.PathValue("patchId")
rp := repoPath(cfg, git.Name)
p, err := loadPatch(rp, patchID)
if err != nil {
renderError(w, r, tmpl, http.StatusNotFound, "Patch not found")
return
}
if err := mergePatch(rp, p); err != nil {
slog.Error("merge patch", "repo", git.Name, "patch", patchID, "error", err)
renderError(w, r, tmpl, http.StatusInternalServerError, err.Error())
return
}
http.Redirect(w, r, fmt.Sprintf("/%s/patches/%s", git.Name, patchID), http.StatusSeeOther)
}
}
func PatchClosePost(cfg Config, tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
if !checkRepoAccess(w, r, git) {
return
}
patchID := r.PathValue("patchId")
rp := repoPath(cfg, git.Name)
if err := closePatch(rp, patchID); err != nil {
slog.Error("close patch", "repo", git.Name, "patch", patchID, "error", err)
renderError(w, r, tmpl, http.StatusInternalServerError, err.Error())
return
}
http.Redirect(w, r, fmt.Sprintf("/%s/patches/%s", git.Name, patchID), http.StatusSeeOther)
}
}