internal/web/templates/mr.html

200 lines · 13621 bytes

  1{{define "width"}}{{if eq .View "diff"}}wide{{else}}reading{{end}}{{end}}
  2{{define "title"}}!{{.MR.Number}} · {{.Repo.OwnerName}}/{{.Repo.Name}}{{end}}
  3{{define "content"}}
  4{{$base := printf "/%s/%s/mrs/%d" .Repo.OwnerName .Repo.Name .MR.Number}}
  5<h1 class="issuetitle">{{.MR.Title}} <span class="issuenumber">!{{.MR.Number}}</span></h1>
  6<p class="issuemeta"><span class="chip chip-{{.MR.State}}">{{if eq .MR.State "source_gone"}}source gone{{else}}{{.MR.State}}{{end}}</span>{{if .MR.Draft}} <span class="chip chip-neutral">draft</span>{{end}}
  7  {{if and (eq .MR.State "merged") .MR.MergedBy}}merged by <a href="/{{.MR.MergedBy}}">{{.MR.MergedBy}}</a>{{with .MR.MergedAt}} on {{when .}}{{end}}
  8  {{else if and (eq .MR.State "closed") .MR.ClosedBy}}closed without merging by <a href="/{{.MR.ClosedBy}}">{{.MR.ClosedBy}}</a>{{with .MR.ClosedAt}} on {{when .}}{{end}}{{if .MR.SupersededBy}}, in favour of <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/mrs/{{.MR.SupersededBy}}">!{{.MR.SupersededBy}}</a>{{end}}
  9  {{else if or (eq .MR.State "merged") (eq .MR.State "closed")}}{{if eq .MR.State "merged"}}merged{{else}}closed without merging{{end}}
 10  {{else}}opened by <a href="/{{.MR.Author}}">{{.MR.Author}}</a> on {{when .MR.CreatedAt}}{{end}}
 11  · <code>{{if .MR.SourcePath}}{{.MR.SourcePath}}:{{end}}{{.MR.SourceRef}}</code> into <code>{{.MR.TargetRef}}</code></p>
 12{{with field . "StackedOn"}}<p class="meta">Stacked on <a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{.Number}}">!{{.Number}} {{.Title}}</a>: merges into its branch until that lands, then onto its target.</p>{{end}}
 13{{with field . "Stacked"}}<p class="meta">Builds on this: {{range $i, $k := .}}{{if $i}}, {{end}}<a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{$k.Number}}">!{{$k.Number}} {{$k.Title}}</a>{{end}}. Merging with squash or rebase is refused while they are open.</p>{{end}}
 14{{with field . "Supersedes"}}<p class="meta">supersedes {{range $i, $m := .}}{{if $i}}, {{end}}<a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{$m.Number}}">!{{$m.Number}}</a>{{end}}</p>{{end}}
 15
 16{{if .Notice}}<p class="error" role="alert">{{.Notice}}</p>{{end}}
 17
 18<div class="withaside">
 19<div class="mainside">
 20
 21<nav class="subtabs" aria-label="Merge request">
 22  <a {{if eq .View "conversation"}}aria-current="page" {{end}}href="{{$base}}">Conversation{{if .Comments}} <i>{{len .Comments}}</i>{{end}}</a>
 23  <a {{if eq .View "commits"}}aria-current="page" {{end}}href="{{$base}}?view=commits">Commits{{if .Commits}} <i>{{len .Commits}}</i>{{end}}</a>
 24  <a {{if eq .View "diff"}}aria-current="page" {{end}}href="{{$base}}?view=diff">Files changed{{if .Stat.Files}} <i>{{.Stat.Files}}</i>{{end}}</a>
 25</nav>
 26
 27{{if eq .View "conversation"}}
 28<div class="prose">
 29<h2>Discussion</h2>
 30{{if .CanEdit}}<details class="editbox"{{if .Draft.Is "edit"}} open{{end}}><summary>Edit</summary>
 31{{if .Draft.Is "edit"}}{{template "previewblock" .Draft.HTML}}{{end}}
 32<form method="post" action="{{$base}}/edit" class="commentform">
 33<p><input type="text" name="title" aria-label="Title" value="{{.Draft.Or "edit" "title" .MR.Title}}" required></p>
 34<p><textarea name="body" aria-label="Description" rows="8">{{.Draft.Or "edit" "body" .MR.Body}}</textarea></p>
 35<p><button type="submit" class="btn">Save</button> {{template "previewbtn"}}</p>
 36</form></details>{{end}}
 37{{if .BodyHTML}}<article class="comment">
 38  <header class="commenthead"><strong><a href="/{{.MR.Author}}">{{.MR.Author}}</a></strong></header>
 39  <div class="rendered">{{.BodyHTML}}</div>
 40  {{template "reactions" index .Reactions 0}}
 41</article>{{else}}{{template "reactions" index .Reactions 0}}{{end}}
 42{{range .Comments}}
 43{{if eq .Kind "system"}}<div class="syscomment">{{.BodyHTML}} <span class="when">{{when .CreatedAt}}</span></div>
 44{{else}}<article class="comment">
 45  <header class="commenthead"><strong><a href="/{{.Author}}">{{.Author}}</a></strong> <span class="when">{{when .CreatedAt}}</span></header>
 46  <div class="rendered">{{.BodyHTML}}</div>
 47  {{template "reactions" index $.Reactions .ID}}
 48</article>{{end}}
 49{{end}}
 50{{if .DetachedThreads}}<h2>Threads on earlier revisions</h2>
 51{{range .DetachedThreads}}{{template "thread" dict "T" . "Base" $base "Viewer" $.Viewer "Class" "stale"}}{{end}}{{end}}
 52{{if .Viewer}}
 53{{if .Draft.Is "comment"}}{{template "previewblock" .Draft.HTML}}{{end}}
 54<form method="post" action="{{$base}}/comment" class="commentform">
 55<p><textarea name="body" aria-label="Comment" rows="4" placeholder="Comment as {{.Viewer}}">{{.Draft.Or "comment" "body" ""}}</textarea></p>
 56<p><button type="submit">Comment</button> {{template "previewbtn"}}</p>
 57</form>
 58{{end}}
 59</div>
 60
 61{{else if eq .View "commits"}}
 62{{if gt .CommitsTotal (len .Commits)}}<p class="meta">first {{len .Commits}} of {{.CommitsTotal}} commits; the rest are in the branch</p>{{end}}
 63<ul class="loglist">
 64{{range .Commits}}<li>
 65  <div class="commitmain">
 66    <p class="subject"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/commit/{{.SHA}}">{{.Subject}}</a></p>
 67    <p class="meta">{{template "authorname" dict "Name" .AuthorName "User" .AuthorUser "Email" ""}} · {{when .Date}}</p>
 68  </div>
 69  <div class="commitside">
 70    {{template "sigbadge" .Sig}}
 71    <code><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/commit/{{.SHA}}">{{.ShortSHA}}</a></code>
 72  </div>
 73</li>
 74{{else}}<li class="empty">no commits on this merge request</li>
 75{{end}}
 76</ul>
 77
 78{{else}}
 79{{if .DiffFiles}}<p class="diffstat">{{.Stat.Files}} file{{if ne .Stat.Files 1}}s{{end}} changed, <span class="add">+{{.Stat.Adds}}</span> <span class="del">−{{.Stat.Dels}}</span>{{if .DiffTruncated}} · shown up to 4 MiB; the counts and the last file are partial{{end}}</p>
 80{{if .DiffTruncated}}<p class="error" role="alert">This diff is larger than 4 MiB and is cut off below. Fetch the branch to see all of it.</p>{{end}}
 81{{template "difffiles" dict "Files" .DiffFiles "Base" $base "Viewer" .Viewer "L" .Layout}}
 82{{else if .HeadPruned}}<p class="empty-note">The head of this merge request is no longer in the repository; its diff is not available.</p>
 83{{else}}<p class="empty-note">No changes between the source and target.{{if .HeadMerged}} The source branch was already merged or fast-forwarded into <code>{{.MR.TargetRef}}</code>.{{end}}</p>{{end}}
 84{{end}}
 85
 86</div>
 87
 88<aside class="aside">
 89  {{if and .CanWrite (or (eq .MR.State "open") (eq .MR.State "source_gone"))}}
 90  <div class="grp">
 91    <h2>Review</h2>
 92    <form method="post" action="{{$base}}/review" class="actions">
 93      <button type="submit" name="verdict" value="approve" class="btn">Approve</button>
 94      <button type="submit" name="verdict" value="request-changes" class="btn">Request changes</button>
 95    </form>
 96  </div>
 97  <div class="grp">
 98    <h2>Merge</h2>
 99    {{if .Unresolved}}<p class="row none">{{.Unresolved}} unresolved thread{{if ne .Unresolved 1}}s{{end}}</p>{{end}}
100    {{if .MR.QueuedAt}}<p class="row"><span class="dot pend"></span>Queued to merge by <a href="/{{.MR.QueuedBy}}">{{.MR.QueuedBy}}</a>{{if .MR.QueueStrategy}} ({{.MR.QueueStrategy}}){{end}}<span class="sub">{{when .MR.QueuedAt}}</span></p>
101    {{with .MR.QueueReason}}<p class="row none">waiting: {{.}}</p>{{end}}
102    <form method="post" action="{{$base}}/merge" class="actions">
103      <input type="hidden" name="cancel" value="on">
104      <button type="submit" class="btn">Cancel queued merge</button>
105    </form>{{end}}
106    <form method="post" action="{{$base}}/merge" class="actions">
107      <label class="none" for="strategy">Strategy</label>
108      <select id="strategy" name="strategy">
109        <option value="auto">Automatic</option>
110        <option value="ff">Fast-forward</option>
111        <option value="merge">Merge commit</option>
112        <option value="squash">Squash</option>
113        <option value="rebase">Rebase</option>
114      </select>
115      <button type="submit">Merge</button>
116      <button type="submit" name="when_ready" value="on" class="btn">Merge when ready</button>
117    </form>
118    <form method="post" action="{{$base}}/close" class="actions">
119      <label class="vh" for="by">Closed in favour of</label>
120      <input type="text" id="by" name="by" size="4" autocomplete="off" placeholder="!N">
121      <button type="submit" class="danger">Close without merging</button>
122    </form>
123    <form method="post" action="{{$base}}/draft" class="actions">
124      {{if .MR.Draft}}<button type="submit" class="btn">Ready for review</button>
125      {{else}}<input type="hidden" name="draft" value="on"><button type="submit" class="btn">Convert to draft</button>{{end}}
126    </form>
127  </div>
128  {{end}}
129  {{with .Gates}}<div class="grp">
130    <h2>Merge gates</h2>
131    {{if .Unmet}}{{range .Unmet}}<p class="row"><span class="dot bad"></span>{{.}}</p>{{end}}
132    {{else}}<p class="row"><span class="dot ok"></span>All gates met</p>{{end}}
133    {{if .ApprovalsRequired}}<p class="row none">approvals: {{len .Approvals}} of {{.ApprovalsRequired}}{{if .Approvals}} ({{range $i, $a := .Approvals}}{{if $i}}, {{end}}{{$a}}{{end}}){{end}}</p>{{end}}
134    {{range .OwnersOutstanding}}<p class="row none">waiting on {{range $i, $o := .Owners}}{{if $i}} or {{end}}<a href="/{{$o}}">{{$o}}</a>{{end}} for {{range $i, $f := .Files}}{{if $i}}, {{end}}<code>{{$f}}</code>{{end}}</p>{{end}}
135    {{range .ChecksMissing}}<p class="row none">waiting on <code>{{.}}</code>, not yet reported</p>{{end}}
136    <p class="row none">{{if .FastForward}}fast-forward possible{{else}}not a fast-forward: rebase, or merge with a merge commit{{end}}</p>
137  </div>{{end}}
138  <div class="grp">
139    <h2>Checks</h2>
140    {{if .Checks}}<p class="row"><span class="badge check-{{.Combined}}">{{.Combined}}</span></p>
141    {{range .Checks}}<p class="row"><span class="dot {{if eq .State "success"}}ok{{else if eq .State "failure"}}bad{{else}}pend{{end}}"></span>{{if .TargetURL}}<a href="{{.TargetURL}}" rel="nofollow">{{.Context}}</a>{{else}}{{.Context}}{{end}} <span class="none">{{.State}}</span><span class="sub">{{when .UpdatedAt}}{{if .Duration}} · {{.Duration}}{{end}}</span></p>{{end}}
142    {{else}}<p class="none">No checks reported</p>{{end}}
143  </div>
144  <div class="grp">
145    <h2>Reviews</h2>
146    {{range .Reviews}}<p class="row"><span class="dot {{if eq .Verdict "approve"}}ok{{else}}pend{{end}}"></span><a href="/{{.Reviewer}}">{{.Reviewer}}</a> {{.Verdict}}{{if .Stale}} <span class="chip chip-stale">stale</span>{{end}}{{if not .Counts}} <span class="chip chip-neutral" title="This reviewer has no write access, so the merge gates do not count it">advisory</span>{{end}}<span class="sub">{{when .CreatedAt}}</span></p>
147    {{else}}<p class="none">none yet</p>{{end}}
148  </div>
149  {{if .Revisions}}<div class="grp">
150    <h2>Revisions</h2>
151    {{range $i, $rv := .Revisions}}<p class="row none">{{add $i 1}}. <code>{{short $rv.SHA}}</code> {{when $rv.CreatedAt}}{{if $i}} · <a href="{{$base}}/range-diff?from={{(index $.Revisions (sub $i 1)).SHA}}&amp;to={{$rv.SHA}}">compare to previous</a>{{end}}</p>
152    {{end}}
153  </div>{{end}}
154  <div class="grp">
155    <h2>Reviewers</h2>
156    {{if .MR.ReviewRequests}}<p class="row">{{range .MR.ReviewRequests}}<a href="/{{.}}">{{.}}</a> {{end}}</p>
157    {{else}}<p class="none">no reviewers</p>{{end}}
158    {{if and .CanWrite (or (eq .MR.State "open") (eq .MR.State "source_gone"))}}
159    <form method="post" action="{{$base}}/review-request" class="actions">
160      <input type="text" name="add" aria-label="Add reviewers" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
161      <input type="text" name="remove" aria-label="Remove reviewers" autocomplete="off" spellcheck="false" placeholder="remove">
162      <button type="submit" class="btn">Apply</button>
163    </form>
164    {{end}}
165  </div>
166  <div class="grp">
167    <h2>Source and target</h2>
168    <p class="row"><code>{{if .MR.SourcePath}}{{.MR.SourcePath}}:{{end}}{{.MR.SourceRef}}</code> into <code>{{.MR.TargetRef}}</code></p>
169    <p class="row none">{{if eq .MR.State "merged"}}merged at{{else}}head{{end}} <code>{{short .MR.HeadSHA}}</code>{{if .Base}} · base <code>{{short .Base}}</code>{{end}}{{if .SourceGone}} · <span class="chip chip-neutral">branch deleted</span>{{end}}</p>
170    {{if and .CanEdit (or (eq .MR.State "open") (eq .MR.State "source_gone"))}}
171    <form method="post" action="{{$base}}/retarget" class="actions">
172      <label class="none" for="target">Branch</label>
173      <select id="target" name="target">
174        {{range .Branches}}<option value="{{.Name}}"{{if eq .Name $.MR.TargetRef}} selected{{end}}>{{.Name}}</option>{{end}}
175      </select>
176      <button type="submit" class="btn">Retarget</button>
177    </form>
178    <p class="row none">Retargeting stales existing reviews.</p>
179    {{end}}
180  </div>
181  {{if .MR.Milestone}}<div class="grp">
182    <h2>Milestone</h2>
183    <p class="row"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/milestones">{{.MR.Milestone}}</a></p>
184  </div>{{end}}
185  <div class="grp">
186    <h2>Labels</h2>
187    {{if .MR.Labels}}<p class="row">{{range .MR.Labels}}<a class="chip label" style="{{index $.LabelColors .}}" href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs?label={{.}}">{{.}}</a> {{end}}</p>
188    {{else}}<p class="none">{{if or (eq .MR.State "merged") (eq .MR.State "closed")}}no labels{{else}}none yet{{end}}</p>{{end}}
189    {{if .CanWrite}}
190    <form method="post" action="{{$base}}/label" class="actions">
191      <input type="text" name="add" aria-label="Add labels" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
192      <input type="text" name="remove" aria-label="Remove labels" autocomplete="off" spellcheck="false" placeholder="remove">
193      <button type="submit" class="btn">Apply</button>
194    </form>
195    {{end}}
196  </div>
197</aside>
198</div>
199{{end}}
200