main / internal/interface/web/pull_handler.go
4.8 KB · Go Raw
1package web
2
3import (
4 "errors"
5 "net/http"
6 "strconv"
7 "strings"
8
9 "github.com/go-chi/chi"
10
11 "gitgud/internal/app"
12 "gitgud/internal/domain"
13 "gitgud/internal/interface/web/templates"
14)
15
16func (h *Handlers) pullsList(w http.ResponseWriter, r *http.Request) {
17 repo := h.viewRepo(w, r)
18 if repo == nil {
19 return
20 }
21
22 state := r.URL.Query().Get("state")
23 var filter domain.PRState
24 switch state {
25 case "merged":
26 filter = domain.PRMerged
27 case "closed":
28 filter = domain.PRClosed
29 case "all":
30 filter = ""
31 default:
32 state = "open"
33 filter = domain.PROpen
34 }
35
36 prs, err := h.pulls.List(r.Context(), repo.ID, filter)
37 if err != nil {
38 h.writeError(w, r, err)
39 return
40 }
41 open, merged, closed, err := h.pulls.Counts(r.Context(), repo.ID)
42 if err != nil {
43 h.writeError(w, r, err)
44 return
45 }
46 render(w, r, http.StatusOK, templates.PullsList(currentUser(r.Context()), repo, state, prs, open, merged, closed))
47}
48
49func (h *Handlers) comparePull(w http.ResponseWriter, r *http.Request) {
50 repo := h.viewRepo(w, r)
51 if repo == nil {
52 return
53 }
54
55 branches, err := h.browse.Branches(r.Context(), repo)
56 if err != nil {
57 h.writeError(w, r, err)
58 return
59 }
60
61 base := r.URL.Query().Get("base")
62 if base == "" {
63 base = repo.DefaultBranch
64 }
65 head := r.URL.Query().Get("head")
66
67 var cmp *domain.Comparison
68 errMsg := ""
69 if head != "" && head != base {
70 cmp, err = h.pulls.Compare(r.Context(), repo, base, head)
71 if err != nil {
72 errMsg = "could not compare those branches"
73 }
74 }
75 render(w, r, http.StatusOK, templates.Compare(currentUser(r.Context()), repo, base, head, branches, cmp, errMsg))
76}
77
78func (h *Handlers) createPull(w http.ResponseWriter, r *http.Request) {
79 repo := h.viewRepo(w, r)
80 if repo == nil {
81 return
82 }
83 title := r.FormValue("title")
84 body := r.FormValue("body")
85 base := r.FormValue("base")
86 head := r.FormValue("head")
87
88 pr, err := h.pulls.Open(r.Context(), repo, currentUser(r.Context()), title, body, base, head)
89 if err != nil {
90 if errors.Is(err, domain.ErrValidation) {
91 branches, _ := h.browse.Branches(r.Context(), repo)
92 cmp, _ := h.pulls.Compare(r.Context(), repo, base, head)
93 msg := strings.TrimSuffix(err.Error(), ": "+domain.ErrValidation.Error())
94 render(w, r, http.StatusBadRequest, templates.Compare(currentUser(r.Context()), repo, base, head, branches, cmp, msg))
95 return
96 }
97 h.writeError(w, r, err)
98 return
99 }
100 http.Redirect(w, r, repoURL(repo, "/pulls/"+strconv.Itoa(pr.Number)), http.StatusSeeOther)
101}
102
103func (h *Handlers) pullDetail(w http.ResponseWriter, r *http.Request) {
104 repo, pr := h.loadPull(w, r)
105 if pr == nil {
106 return
107 }
108 h.renderPull(w, r, repo, pr, "", http.StatusOK)
109}
110
111func (h *Handlers) addPullComment(w http.ResponseWriter, r *http.Request) {
112 repo, pr := h.loadPull(w, r)
113 if pr == nil {
114 return
115 }
116 if err := h.pulls.Comment(r.Context(), pr, currentUser(r.Context()), r.FormValue("body")); err != nil {
117 h.writeError(w, r, err)
118 return
119 }
120 http.Redirect(w, r, repoURL(repo, "/pulls/"+strconv.Itoa(pr.Number)), http.StatusSeeOther)
121}
122
123func (h *Handlers) mergePull(w http.ResponseWriter, r *http.Request) {
124 repo, pr := h.loadPull(w, r)
125 if pr == nil {
126 return
127 }
128 err := h.pulls.Merge(r.Context(), repo, pr, currentUser(r.Context()))
129 if err == nil {
130 h.flash(r, "Pull request merged.")
131 http.Redirect(w, r, repoURL(repo, "/pulls/"+strconv.Itoa(pr.Number)), http.StatusSeeOther)
132 return
133 }
134 if errors.Is(err, domain.ErrConflict) {
135 h.renderPull(w, r, repo, pr, "Cannot merge automatically — the branches conflict.", http.StatusConflict)
136 return
137 }
138 h.writeError(w, r, err)
139}
140
141func (h *Handlers) closePull(w http.ResponseWriter, r *http.Request) {
142 repo, pr := h.loadPull(w, r)
143 if pr == nil {
144 return
145 }
146 if err := h.pulls.Close(r.Context(), repo, pr, currentUser(r.Context())); err != nil {
147 h.writeError(w, r, err)
148 return
149 }
150 http.Redirect(w, r, repoURL(repo, "/pulls/"+strconv.Itoa(pr.Number)), http.StatusSeeOther)
151}
152
153func (h *Handlers) renderPull(w http.ResponseWriter, r *http.Request, repo *domain.Repository, pr *domain.PullRequest, errMsg string, status int) {
154 cmp, _ := h.pulls.Compare(r.Context(), repo, pr.BaseBranch, pr.HeadBranch)
155 comments, err := h.pulls.Comments(r.Context(), pr.ID)
156 if err != nil {
157 h.writeError(w, r, err)
158 return
159 }
160 canMerge := pr.State == domain.PROpen && app.CanMergePR(repo, currentUser(r.Context())) && cmp != nil && cmp.Mergeable
161 render(w, r, status, templates.PullDetail(currentUser(r.Context()), repo, pr, cmp, comments, canMerge, errMsg))
162}
163
164func (h *Handlers) loadPull(w http.ResponseWriter, r *http.Request) (*domain.Repository, *domain.PullRequest) {
165 repo := h.viewRepo(w, r)
166 if repo == nil {
167 return nil, nil
168 }
169 number, err := strconv.Atoi(chi.URLParam(r, "number"))
170 if err != nil {
171 h.notFound(w, r)
172 return nil, nil
173 }
174 pr, err := h.pulls.Get(r.Context(), repo.ID, number)
175 if err != nil {
176 h.writeError(w, r, err)
177 return nil, nil
178 }
179 return repo, pr
180}