New Sign in

fierj

Public

Tiny personal git forge

← fierj / patch_handlers.go
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
		}
		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)
	}
}