internal/httpd/mractions.go

2867b44a48ea8afa6b7c21d5e1d69272a7b235fa
gitbay/internal/httpd/mractions.go history · blame · raw

228 lines · 7633 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) mrReviewRequestSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 58	args := append(fieldArgs("--add", r.FormValue("add")), fieldArgs("--remove", r.FormValue("remove"))...)
 59	if len(args) == 0 {
 60		s.mrRedirect(w, r, "name at least one person")
 61		return
 62	}
 63	repo := r.PathValue("owner") + "/" + r.PathValue("repo")
 64	_, msg, code := s.runControlCode(u, append([]string{"mr", "review", "request", repo, r.PathValue("n")}, args...))
 65	s.done(w, r, code, msg, s.mrRedirect)
 66}
 67
 68func (s *Server) mrMergeSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 69	args := []string{}
 70	if st := strings.TrimSpace(r.FormValue("strategy")); st != "" && st != "auto" {
 71		args = append(args, "--strategy", st)
 72	}
 73	_, msg, code := s.runControlCode(u, mrArgs(r, "merge", args...))
 74	s.done(w, r, code, msg, s.mrRedirect)
 75}
 76
 77func (s *Server) mrCloseSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 78	_, msg, code := s.runControlCode(u, mrArgs(r, "close"))
 79	s.done(w, r, code, msg, s.mrRedirect)
 80}
 81
 82// mrDraftSubmit toggles the draft mark. The form says which way it is
 83// going, so a stale page cannot flip the wrong one.
 84func (s *Server) mrDraftSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 85	verb := "ready"
 86	if r.FormValue("draft") == "on" {
 87		verb = "draft"
 88	}
 89	_, msg, code := s.runControlCode(u, mrArgs(r, verb))
 90	s.done(w, r, code, msg, s.mrRedirect)
 91}
 92
 93// mrDiffCommentSubmit opens a review thread on a diff line, or replies to
 94// one. The body goes in on stdin: it is user prose, and argv is visible in
 95// /proc.
 96func (s *Server) mrDiffCommentSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
 97	body := strings.TrimSpace(r.FormValue("body"))
 98	if body == "" {
 99		s.mrDiffRedirect(w, r, "empty comment")
100		return
101	}
102	var extra []string
103	if reply := strings.TrimSpace(r.FormValue("reply")); reply != "" {
104		if _, err := strconv.ParseInt(reply, 10, 64); err != nil {
105			s.mrDiffRedirect(w, r, "bad thread id")
106			return
107		}
108		extra = []string{"--reply", reply}
109	} else {
110		path := strings.TrimSpace(r.FormValue("path"))
111		line := strings.TrimSpace(r.FormValue("line"))
112		if n, err := strconv.ParseInt(line, 10, 64); path == "" || err != nil || n < 1 {
113			s.mrDiffRedirect(w, r, "pick a line to comment on")
114			return
115		}
116		extra = []string{"--path", path, "--line", line}
117		if r.FormValue("side") == "old" {
118			extra = append(extra, "--old")
119		}
120	}
121	// "Add to review" holds the comment back until the verdict; "Comment"
122	// posts it now, which is what the form did before there was a choice.
123	if r.FormValue("pending") == "on" {
124		extra = append(extra, "--pending")
125	}
126	msg, code := s.runControlStdinCode(u, mrArgs(r, "diff-comment", append(extra, "--file", "-")...), body)
127	s.done(w, r, code, msg, s.mrDiffRedirect)
128}
129
130// mrRetargetSubmit moves the merge request onto another branch.
131func (s *Server) mrRetargetSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
132	target := strings.TrimSpace(r.FormValue("target"))
133	if target == "" {
134		s.mrRedirect(w, r, "pick a branch to retarget onto")
135		return
136	}
137	_, msg, code := s.runControlCode(u, mrArgs(r, "retarget", target))
138	s.done(w, r, code, msg, s.mrRedirect)
139}
140
141// mrThreadSubmit resolves or reopens one review thread.
142func (s *Server) mrThreadSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
143	verb := "resolve"
144	if r.FormValue("action") == "unresolve" {
145		verb = "unresolve"
146	}
147	id := strings.TrimSpace(r.FormValue("thread"))
148	if _, err := strconv.ParseInt(id, 10, 64); err != nil {
149		s.mrRedirect(w, r, "bad thread id")
150		return
151	}
152	_, msg, code := s.runControlCode(u, mrArgs(r, verb, id))
153	s.done(w, r, code, msg, s.mrRedirect)
154}
155
156// mrNewPage is the create form: branches to choose from, plus whatever
157// the last attempt had in it so a refusal does not lose the draft.
158type mrNewPage struct {
159	repoPage
160	Branches []gitutil.Ref
161	Source   string
162	Target   string
163	Title    string
164	Body     string
165	Format   string
166	Notice   string
167}
168
169func (s *Server) mrCreateForm(w http.ResponseWriter, r *http.Request, u store.User) {
170	p, ok := s.repoFor(w, r, "")
171	if !ok {
172		return
173	}
174	p.Tab = "merge requests"
175	branches, _ := gitutil.Refs(p.Dir, "heads")
176	q := r.URL.Query()
177	target := q.Get("target")
178	if target == "" {
179		target = p.Repo.DefaultBranch
180	}
181	format := q.Get("format")
182	if format != "org" {
183		format = "md"
184	}
185	s.render(w, "mrnew.html", mrNewPage{
186		repoPage: p, Branches: branches,
187		Source: q.Get("source"), Target: target,
188		Title: q.Get("title"), Body: q.Get("body"), Format: format, Notice: s.takeFlash(w, r),
189	})
190}
191
192func (s *Server) mrCreateSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
193	p, ok := s.repoFor(w, r, "")
194	if !ok {
195		return
196	}
197	source := strings.TrimSpace(r.FormValue("source"))
198	target := strings.TrimSpace(r.FormValue("target"))
199	title := strings.TrimSpace(r.FormValue("title"))
200	body := strings.TrimSpace(r.FormValue("body"))
201	format := r.FormValue("format")
202	if format != "org" {
203		format = "md"
204	}
205
206	back := func(msg string) {
207		q := url.Values{"source": {source}, "target": {target}, "title": {title}, "body": {body}, "format": {format}}
208		s.setFlash(w, msg)
209		http.Redirect(w, r, fmt.Sprintf("/%s/mrs/new?%s", p.Repo.Path(), q.Encode()), http.StatusSeeOther)
210	}
211	if source == "" || title == "" {
212		back("pick a source branch and give the merge request a title")
213		return
214	}
215	argv := []string{"mr", "create", p.Repo.Path(), "--source", source, "--title", title, "--format", format}
216	if target != "" {
217		argv = append(argv, "--target", target)
218	}
219	if body != "" {
220		argv = append(argv, "--body", body)
221	}
222	var created control.MRCreated
223	if msg, ok := s.runControlInto(u, argv, &created); !ok {
224		back(msg)
225		return
226	}
227	http.Redirect(w, r, fmt.Sprintf("/%s/mrs/%d", p.Repo.Path(), created.Number), http.StatusSeeOther)
228}