Commit b7fc5924ee
Verified · cmc
e2e/mr_test.go +13
| @@ -267,4 +267,17 @@ func TestMergeRequests(t *testing.T) { | ||
| 267 | 267 | if !strings.Contains(body, "feature.txt") { |
| 268 | 268 | t.Fatalf("merged MR web diff empty:\n%s", body) |
| 269 | 269 | } |
| 270 | // The MR page lists the commits it carries, linked to commit pages. | |
| 271 | if !strings.Contains(body, ">commits <") || !strings.Contains(body, "/alice/lib/commit/") { | |
| 272 | t.Fatalf("mr commits section missing:\n%s", body) | |
| 273 | } | |
| 274 | // So does mr show, human and JSON. | |
| 275 | showOut, _, code := inst.ssh(t, aliceKey, "", "mr", "show", "alice/lib", "1") | |
| 276 | if code != 0 || !strings.Contains(showOut, "commit: ") { | |
| 277 | t.Fatalf("mr show missing commits: %d\n%s", code, showOut) | |
| 278 | } | |
| 279 | showJSON, _, _ := inst.ssh(t, aliceKey, "", "mr", "show", "alice/lib", "1", "--json") | |
| 280 | if !strings.Contains(showJSON, `"commits":[`) || !strings.Contains(showJSON, `"subject"`) { | |
| 281 | t.Fatalf("mr show json missing commits: %s", showJSON) | |
| 282 | } | |
| 270 | 283 | } |
internal/control/mr.go +32 −1
| @@ -11,6 +11,7 @@ import ( | ||
| 11 | 11 | "gitbay.org/gitbay/internal/gitutil" |
| 12 | 12 | "gitbay.org/gitbay/internal/policy" |
| 13 | 13 | "gitbay.org/gitbay/internal/protocol" |
| 14 | "gitbay.org/gitbay/internal/sig" | |
| 14 | 15 | "gitbay.org/gitbay/internal/store" |
| 15 | 16 | ) |
| 16 | 17 | |
| @@ -414,19 +415,49 @@ func runMRShow(c *Ctx, args []string) int { | ||
| 414 | 415 | for _, r := range reviews { |
| 415 | 416 | rs = append(rs, reviewOut{r.Reviewer, r.Verdict, r.Stale}) |
| 416 | 417 | } |
| 418 | // The commits this MR carries: base..head, the diff's range. | |
| 419 | type commitOut struct { | |
| 420 | SHA string `json:"sha"` | |
| 421 | Subject string `json:"subject"` | |
| 422 | } | |
| 423 | var commits []commitOut | |
| 424 | dir := RepoDir(c.Cfg.Server.Root, repo.OwnerName, repo.Name) | |
| 425 | base := mr.MergedBase | |
| 426 | if base == "" { | |
| 427 | if b, err := gitutil.MergeBase(dir, "refs/heads/"+mr.TargetRef, mrHeadRef(mr.Number)); err == nil { | |
| 428 | base = b | |
| 429 | } | |
| 430 | } | |
| 431 | if base != "" { | |
| 432 | if shas, err := gitutil.RevListRange(dir, base, mrHeadRef(mr.Number)); err == nil { | |
| 433 | for _, sha := range shas { | |
| 434 | subject := "" | |
| 435 | if raw, err := gitutil.ReadCommit(dir, sha); err == nil { | |
| 436 | if parsed, err := sig.ParseCommit(raw); err == nil { | |
| 437 | subject = parsed.Subject | |
| 438 | } | |
| 439 | } | |
| 440 | commits = append(commits, commitOut{sha, subject}) | |
| 441 | } | |
| 442 | } | |
| 443 | } | |
| 417 | 444 | d := struct { |
| 418 | 445 | mrOut |
| 419 | 446 | Checks []checkOut `json:"checks,omitempty"` |
| 420 | 447 | Combined string `json:"checks_combined,omitempty"` |
| 421 | 448 | UnresolvedThreads int `json:"unresolved_threads,omitempty"` |
| 449 | Commits []commitOut `json:"commits,omitempty"` | |
| 422 | 450 | Comments []commentOut `json:"comments,omitempty"` |
| 423 | 451 | Reviews []reviewOut `json:"reviews,omitempty"` |
| 424 | }{mrToOut(repo, mr, true), checks, store.CombinedStatus(statuses), unresolved, cs, rs} | |
| 452 | }{mrToOut(repo, mr, true), checks, store.CombinedStatus(statuses), unresolved, commits, cs, rs} | |
| 425 | 453 | return c.emit(d, func(w io.Writer) { |
| 426 | 454 | fmt.Fprintf(w, "!%d %s [%s] by %s\n%s -> %s @ %.10s\n", d.Number, d.Title, d.State, d.Author, d.Source, d.TargetRef, d.HeadSHA) |
| 427 | 455 | if d.Body != "" { |
| 428 | 456 | fmt.Fprintf(w, "\n%s\n", d.Body) |
| 429 | 457 | } |
| 458 | for _, cm := range commits { | |
| 459 | fmt.Fprintf(w, "commit: %.10s %s\n", cm.SHA, cm.Subject) | |
| 460 | } | |
| 430 | 461 | for _, x := range checks { |
| 431 | 462 | fmt.Fprintf(w, "check: %s %s\n", x.Context, x.State) |
| 432 | 463 | } |
internal/httpd/web.go +25 −1
| @@ -1345,6 +1345,29 @@ func (s *Server) mr(w http.ResponseWriter, r *http.Request) { | ||
| 1345 | 1345 | stat.Files++ |
| 1346 | 1346 | } |
| 1347 | 1347 | } |
| 1348 | // The commits this MR carries: base..head, the same range as the diff. | |
| 1349 | type commitRow struct { | |
| 1350 | SHA, ShortSHA, Subject, AuthorName, Date string | |
| 1351 | Sig sigView | |
| 1352 | } | |
| 1353 | var commits []commitRow | |
| 1354 | if base != "" { | |
| 1355 | const maxMRCommits = 100 | |
| 1356 | shas, _ := gitutil.RevListRange(p.Dir, base, headRef) | |
| 1357 | if len(shas) > maxMRCommits { | |
| 1358 | shas = shas[:maxMRCommits] | |
| 1359 | } | |
| 1360 | for _, sha := range shas { | |
| 1361 | v, parsed := s.sigFor(p.Repo, p.Dir, sha) | |
| 1362 | cr := commitRow{SHA: sha, ShortSHA: sha[:10], Sig: v} | |
| 1363 | if parsed != nil { | |
| 1364 | cr.Subject = parsed.Subject | |
| 1365 | cr.AuthorName = parsed.AuthorName | |
| 1366 | cr.Date = time.Unix(parsed.AuthorUnix, 0).UTC().Format("2006-01-02") | |
| 1367 | } | |
| 1368 | commits = append(commits, cr) | |
| 1369 | } | |
| 1370 | } | |
| 1348 | 1371 | s.render(w, "mr.html", struct { |
| 1349 | 1372 | repoPage |
| 1350 | 1373 | MR store.MR |
| @@ -1355,10 +1378,11 @@ func (s *Server) mr(w http.ResponseWriter, r *http.Request) { | ||
| 1355 | 1378 | Reviews []store.MRReview |
| 1356 | 1379 | DiffLines []diffLine |
| 1357 | 1380 | Stat diffStat |
| 1381 | Commits []commitRow | |
| 1358 | 1382 | CanEdit bool |
| 1359 | 1383 | DetachedThreads []diffThread |
| 1360 | 1384 | }{p, m, md(m.Body), checks, store.CombinedStatus(checks), renderComments(comments, md), |
| 1361 | reviews, lines, stat, s.canEditItem(r, p.Repo, m.Author), detachedThreads}) | |
| 1385 | reviews, lines, stat, commits, s.canEditItem(r, p.Repo, m.Author), detachedThreads}) | |
| 1362 | 1386 | } |
| 1363 | 1387 | |
| 1364 | 1388 | func (s *Server) refs(w http.ResponseWriter, r *http.Request) { |
internal/web/templates/mr.html +14
| @@ -33,6 +33,20 @@ | ||
| 33 | 33 | <p><button type="submit">comment</button></p> |
| 34 | 34 | </form> |
| 35 | 35 | {{end}} |
| 36 | {{if .Commits}}<h3>commits <span class="count">{{len .Commits}}</span></h3> | |
| 37 | <ul class="loglist"> | |
| 38 | {{range .Commits}}<li> | |
| 39 | <div class="commitmain"> | |
| 40 | <p class="subject"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/commit/{{.SHA}}">{{.Subject}}</a></p> | |
| 41 | <p class="meta">{{.AuthorName}} · {{.Date}}</p> | |
| 42 | </div> | |
| 43 | <div class="commitside"> | |
| 44 | {{template "sigbadge" .Sig}} | |
| 45 | <code><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/commit/{{.SHA}}">{{.ShortSHA}}</a></code> | |
| 46 | </div> | |
| 47 | </li> | |
| 48 | {{end}} | |
| 49 | </ul>{{end}} | |
| 36 | 50 | <details class="difffold"> |
| 37 | 51 | <summary><h3>diff</h3><span class="meta">{{.Stat.Files}} file{{if ne .Stat.Files 1}}s{{end}} changed, <span class="add">+{{.Stat.Adds}}</span> <span class="del">−{{.Stat.Dels}}</span></span></summary> |
| 38 | 52 | <pre class="diff">{{range .DiffLines}}<span class="{{.Class}}">{{.Text}}</span> |