internal/httpd/mractions.go

v1.12.0
gitbay/internal/httpd/mractions.go history · blame · raw

192 lines · 6321 bytes

  1package httpd
  2
  3import (
  4	"fmt"
  5	"net/http"
  6	"net/url"
  7	"strconv"
  8	"strings"
  9
 10	"gitbay.org/gitbay/internal/control"
 11	"gitbay.org/gitbay/internal/gitutil"
 12	"gitbay.org/gitbay/internal/store"
 13)
 14
 15// Merge request actions. Each one runs the control command the CLI runs,
 16// so review rules, merge gates, and audit entries have a single
 17// implementation; the browser only chooses arguments and shows the
 18// result.
 19
 20// mrRedirect returns to the merge request, carrying a failure message the
 21// page renders as a banner.
 22func (s *Server) mrRedirect(w http.ResponseWriter, r *http.Request, msg string) {
 23	dest := fmt.Sprintf("/%s/%s/mrs/%s",
 24		r.PathValue("owner"), r.PathValue("repo"), r.PathValue("n"))
 25	s.setFlash(w, msg)
 26	http.Redirect(w, r, dest, http.StatusSeeOther)
 27}
 28
 29// mrDiffRedirect returns to the diff view, where the thread controls are.
 30func (s *Server) mrDiffRedirect(w http.ResponseWriter, r *http.Request, msg string) {
 31	dest := fmt.Sprintf("/%s/%s/mrs/%s?view=diff",
 32		r.PathValue("owner"), r.PathValue("repo"), r.PathValue("n"))
 33	s.setFlash(w, msg)
 34	http.Redirect(w, r, dest, http.StatusSeeOther)
 35}
 36
 37// mrArgs builds "<verb> owner/name <n>" for the mr command family.
 38func mrArgs(r *http.Request, verb string, extra ...string) []string {
 39	repo := r.PathValue("owner") + "/" + r.PathValue("repo")
 40	return append([]string{"mr", verb, repo, r.PathValue("n")}, extra...)
 41}
 42
 43func (s *Server) mrReviewSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 44	flag := map[string]string{
 45		"approve":         "--approve",
 46		"request-changes": "--request-changes",
 47		"comment":         "--comment",
 48	}[r.FormValue("verdict")]
 49	if flag == "" {
 50		s.mrRedirect(w, r, "pick approve, request changes, or comment")
 51		return
 52	}
 53	_, msg, code := s.runControlCode(u, mrArgs(r, "review", flag))
 54	s.done(w, r, code, msg, s.mrRedirect)
 55}
 56
 57func (s *Server) mrMergeSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 58	args := []string{}
 59	if st := strings.TrimSpace(r.FormValue("strategy")); st != "" && st != "auto" {
 60		args = append(args, "--strategy", st)
 61	}
 62	_, msg, code := s.runControlCode(u, mrArgs(r, "merge", args...))
 63	s.done(w, r, code, msg, s.mrRedirect)
 64}
 65
 66func (s *Server) mrCloseSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 67	_, msg, code := s.runControlCode(u, mrArgs(r, "close"))
 68	s.done(w, r, code, msg, s.mrRedirect)
 69}
 70
 71// mrDiffCommentSubmit opens a review thread on a diff line, or replies to
 72// one. The body goes in on stdin: it is user prose, and argv is visible in
 73// /proc.
 74func (s *Server) mrDiffCommentSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 75	body := strings.TrimSpace(r.FormValue("body"))
 76	if body == "" {
 77		s.mrDiffRedirect(w, r, "empty comment")
 78		return
 79	}
 80	var extra []string
 81	if reply := strings.TrimSpace(r.FormValue("reply")); reply != "" {
 82		if _, err := strconv.ParseInt(reply, 10, 64); err != nil {
 83			s.mrDiffRedirect(w, r, "bad thread id")
 84			return
 85		}
 86		extra = []string{"--reply", reply}
 87	} else {
 88		path := strings.TrimSpace(r.FormValue("path"))
 89		line := strings.TrimSpace(r.FormValue("line"))
 90		if n, err := strconv.ParseInt(line, 10, 64); path == "" || err != nil || n < 1 {
 91			s.mrDiffRedirect(w, r, "pick a line to comment on")
 92			return
 93		}
 94		extra = []string{"--path", path, "--line", line}
 95		if r.FormValue("side") == "old" {
 96			extra = append(extra, "--old")
 97		}
 98	}
 99	msg, code := s.runControlStdinCode(u, mrArgs(r, "diff-comment", append(extra, "--file", "-")...), body)
100	s.done(w, r, code, msg, s.mrDiffRedirect)
101}
102
103// mrRetargetSubmit moves the merge request onto another branch.
104func (s *Server) mrRetargetSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
105	target := strings.TrimSpace(r.FormValue("target"))
106	if target == "" {
107		s.mrRedirect(w, r, "pick a branch to retarget onto")
108		return
109	}
110	_, msg, code := s.runControlCode(u, mrArgs(r, "retarget", target))
111	s.done(w, r, code, msg, s.mrRedirect)
112}
113
114// mrThreadSubmit resolves or reopens one review thread.
115func (s *Server) mrThreadSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
116	verb := "resolve"
117	if r.FormValue("action") == "unresolve" {
118		verb = "unresolve"
119	}
120	id := strings.TrimSpace(r.FormValue("thread"))
121	if _, err := strconv.ParseInt(id, 10, 64); err != nil {
122		s.mrRedirect(w, r, "bad thread id")
123		return
124	}
125	_, msg, code := s.runControlCode(u, mrArgs(r, verb, id))
126	s.done(w, r, code, msg, s.mrRedirect)
127}
128
129// mrNewPage is the create form: branches to choose from, plus whatever
130// the last attempt had in it so a refusal does not lose the draft.
131type mrNewPage struct {
132	repoPage
133	Branches []gitutil.Ref
134	Source   string
135	Target   string
136	Title    string
137	Body     string
138	Notice   string
139}
140
141func (s *Server) mrCreateForm(w http.ResponseWriter, r *http.Request, u store.User) {
142	p, ok := s.repoFor(w, r, "")
143	if !ok {
144		return
145	}
146	p.Tab = "merge requests"
147	branches, _ := gitutil.Refs(p.Dir, "heads")
148	q := r.URL.Query()
149	target := q.Get("target")
150	if target == "" {
151		target = p.Repo.DefaultBranch
152	}
153	s.render(w, "mrnew.html", mrNewPage{
154		repoPage: p, Branches: branches,
155		Source: q.Get("source"), Target: target,
156		Title: q.Get("title"), Body: q.Get("body"), Notice: s.takeFlash(w, r),
157	})
158}
159
160func (s *Server) mrCreateSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
161	p, ok := s.repoFor(w, r, "")
162	if !ok {
163		return
164	}
165	source := strings.TrimSpace(r.FormValue("source"))
166	target := strings.TrimSpace(r.FormValue("target"))
167	title := strings.TrimSpace(r.FormValue("title"))
168	body := strings.TrimSpace(r.FormValue("body"))
169
170	back := func(msg string) {
171		q := url.Values{"source": {source}, "target": {target}, "title": {title}, "body": {body}}
172		s.setFlash(w, msg)
173		http.Redirect(w, r, fmt.Sprintf("/%s/mrs/new?%s", p.Repo.Path(), q.Encode()), http.StatusSeeOther)
174	}
175	if source == "" || title == "" {
176		back("pick a source branch and give the merge request a title")
177		return
178	}
179	argv := []string{"mr", "create", p.Repo.Path(), "--source", source, "--title", title}
180	if target != "" {
181		argv = append(argv, "--target", target)
182	}
183	if body != "" {
184		argv = append(argv, "--body", body)
185	}
186	var created control.MRCreated
187	if msg, ok := s.runControlInto(u, argv, &created); !ok {
188		back(msg)
189		return
190	}
191	http.Redirect(w, r, fmt.Sprintf("/%s/mrs/%d", p.Repo.Path(), created.Number), http.StatusSeeOther)
192}