Small task automation and simplification tool for bare-bones git hosting. https://aphrodite.dev/~notebook/projects/gitmgr.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

211 lines
5.2 KiB

package main
import (
"context"
"errors"
"fmt"
"github.com/gorilla/mux"
"log"
"net/http"
"os"
"os/signal"
"path"
"syscall"
)
func run(handlers Handlers) error {
router := mux.NewRouter()
router.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticFS)))
router.HandleFunc("/", handlers.HandleHomepage).Methods(http.MethodGet, http.MethodPost)
router.HandleFunc("/edit", handlers.HandleEditRepository).Methods(http.MethodGet, http.MethodPost)
router.HandleFunc("/toggle-visibility", handlers.HandleToggleVisibility).Methods(http.MethodGet)
router.HandleFunc("/delete", handlers.HandleDeleteRepository).Methods(http.MethodGet)
done := make(chan struct{})
quit := make(chan os.Signal, 1)
server := http.Server{
Handler: router,
ReadTimeout: httpTimeout,
WriteTimeout: httpTimeout,
}
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
go func(quit chan os.Signal) {
<-quit
log.Println("closing connections...")
ctx, cancel := context.WithTimeout(context.Background(), shutdownTime)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
panic(err)
}
close(done)
}(quit)
log.Println("starting server...")
err := server.Serve(handlers.config.Listen)
if err == nil || err == http.ErrServerClosed {
<-done // handled by quit
err = nil
}
return err
}
var emptyNameError = errors.New("repository name cannot be empty")
type Handlers struct {
config Config
gitStore Git
}
func (h *Handlers) log(line string, data ...interface{}) {
if h.config.VerboseLogging {
log.Printf(line+"\n", data...)
}
}
func (h *Handlers) HandleHomepage(w http.ResponseWriter, req *http.Request) {
// Handle repository creation
hasErrored := false
repoInitLog := ""
newRepoName := ""
action := req.FormValue("action")
if action == "Create" {
newRepoName = req.FormValue("name")
mirror, head := req.FormValue("mirror"), DefaultHead
if len(mirror) != 0 && len(newRepoName) == 0 {
newRepoName = path.Base(mirror)
}
if len(newRepoName) == 0 {
hasErrored = true
repoInitLog = emptyNameError.Error()
} else {
if len(mirror) == 0 {
h.log("initializing repository %s", newRepoName)
hasErrored, repoInitLog = h.gitStore.InitRepo(newRepoName, head)
} else {
h.log("cloning repository %s into %s", mirror, newRepoName)
hasErrored, repoInitLog = h.gitStore.MirrorRepo(newRepoName, mirror)
if !hasErrored {
err := h.gitStore.GetRepo(newRepoName).UpdateMetadata(
fmt.Sprintf("Mirror of %s", mirror),
"Repository mirrors",
head,
)
if err != nil {
hasErrored = true
repoInitLog = err.Error()
}
}
}
}
}
repos, err := h.gitStore.ListRepos()
if err != nil {
_ = fmt.Errorf("%e", err)
}
byCategory := GroupReposByCategory(repos)
_ = Views.ExecuteTemplate(w, "homepage", struct {
Repos map[string]RepoList
HasRepoInitFailed bool
RepoInitLog string
NewRepoName string
}{
Repos: byCategory,
HasRepoInitFailed: hasErrored,
RepoInitLog: repoInitLog,
NewRepoName: newRepoName,
})
}
func (h *Handlers) HandleEditRepository(w http.ResponseWriter, req *http.Request) {
repoName := req.FormValue("repository")
if len(repoName) == 0 || !h.gitStore.Exists(repoName) {
http.Redirect(w, req, "/", 303)
return
}
repo := h.gitStore.GetRepo(repoName)
var formErrors []error
if req.Method == http.MethodPost {
newName, category, description, head :=
req.FormValue("name"),
req.FormValue("category"),
req.FormValue("description"),
req.FormValue("head")
// Renaming
if len(newName) == 0 {
formErrors = append(formErrors, emptyNameError)
} else if repoName != newName {
h.log("renaming repository %s to %s", repoName, newName)
err := repo.Rename(newName)
if err != nil {
formErrors = append(formErrors, err)
}
}
// Metadata updating
h.log("updating metadata of %s", newName)
err := repo.UpdateMetadata(description, category, head)
if err != nil {
formErrors = append(formErrors, err)
}
if len(formErrors) == 0 {
http.Redirect(w, req, "/", 303)
return
}
}
_ = Views.ExecuteTemplate(w, "edit-repository", struct {
Repo Repo
Errors []error
}{
Repo: *repo,
Errors: formErrors,
})
}
func (h *Handlers) HandleToggleVisibility(w http.ResponseWriter, req *http.Request) {
repo := req.FormValue("repository")
if len(repo) != 0 && h.gitStore.Exists(repo) {
h.log("changing visibility of %s", repo)
err := h.gitStore.GetRepo(repo).ToggleVisibility()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
}
}
http.Redirect(w, req, "/", 303)
}
func (h *Handlers) HandleDeleteRepository(w http.ResponseWriter, req *http.Request) {
repo := req.FormValue("repository")
if len(repo) == 0 || !h.gitStore.Exists(repo) || len(req.FormValue("cancel")) > 0 {
http.Redirect(w, req, "/", 303)
return
}
var err error
if len(req.FormValue("confirm")) > 0 {
h.log("deleting repository %s", repo)
err = h.gitStore.Delete(repo)
if err == nil {
http.Redirect(w, req, "/", 303)
return
}
}
// Ask for confirmation
_ = Views.ExecuteTemplate(w, "delete-confirm", struct {
Name string
Error error
}{
Name: repo,
Error: err,
})
}