Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Show what a merged change request landed

2841d922c15ce32fb600d992f9f5dc2d22c48f90 · Keenan Nemetz · 13d ago · parent c765ae2e

Show what a merged change request landed

Landing moves the base to include the head, so the three-dot diff the
Files tab reviews an open request with becomes empty by definition: the
merge base of the base and the head is the head itself. A merged request
therefore looked like it had never changed anything.

The landing mark now records `merged_base`, the tip the base pointed at
before it moved, and the Files tab diffs that against what landed: the
head for a fast-forward, the merge commit otherwise, so conflict
resolution is part of the review too. The record is written in the same
commit as the mark, and a retry that finds the head already landed keeps
the base it has instead of naming the moved tip.

`merged_base` is optional on read because landings made before the field
existed have none. Such a record falls back to the merge commit's first
parent when the landing was a merge, and claims nothing when it was a
fast-forward and the starting point is gone.

The request page shows the file count and the review link for a merged
request as well, with the merge named beside it, instead of replacing
them with "Already merged".

metadata/requests.go +23−6

@@ -54,6 +54,12 @@ type Request struct {
54 54 State string // open | merged | closed
55 55 Created time.Time
56 56 Description string
57 // MergedBase is what Base pointed at before the landing. It is the "before" side
58 // of a merged request: once the head is in the base, a diff against the base as
59 // it is now is empty, so the record has to name where the branch started from.
60 // It is empty on a record written before this field existed and on a landing that
61 // found the head already in the base, which had no base tip of its own to name.
62 MergedBase string
57 63 // MergedHead is the commit that landed on Base, which is the head itself for a
58 64 // fast-forward and a merge commit otherwise. It is empty unless State is
59 65 // "merged": a request that claims a landing has to name one, so the file can
@@ -106,6 +112,7 @@ type requestMeta struct {
106 112 State string `yaml:"state"`
107 113 Closes []string `yaml:"closes"`
108 114 Created string `yaml:"created"`
115 MergedBase string `yaml:"merged_base"`
109 116 MergedHead string `yaml:"merged_head"`
110 117 MergedBy string `yaml:"merged_by"`
111 118 MergedAt string `yaml:"merged_at"`
@@ -271,13 +278,23 @@ func applyRequest(req *Request, raw, path string, problems *[]string) {
271 278 closes, closeProblems := parseCloses(m.Closes, path)
272 279 *problems = append(*problems, closeProblems...)
273 280 req.Closes = closes
274 // The merged fields are the record of a landing, so they are all-or-nothing and
275 // only exist on a merged request. A file that says "merged" without naming a
276 // commit is refused rather than shown as landed, and a stale merged_head left on
277 // a reopened request is refused too: the next landing writes a new one.
281 // The merged fields are the record of a landing, so they only exist on a merged
282 // request. A file that says "merged" without naming a commit is refused rather
283 // than shown as landed, and a stale merged_head left on a reopened request is
284 // refused too: the next landing writes a new one. merged_base is the one optional
285 // member: it was added after the first landings and a landing that found the head
286 // already in the base has no base tip of its own to name.
287 req.MergedBase = strings.TrimSpace(m.MergedBase)
278 288 req.MergedHead = strings.TrimSpace(m.MergedHead)
279 289 req.MergedBy = strings.TrimSpace(m.MergedBy)
280 290 if req.State == "merged" {
291 // merged_base is optional because it was added after the first landings, so a
292 // file without one is an older record, not a broken one. A file that names one
293 // has to name a commit.
294 if req.MergedBase != "" && !shaRE.MatchString(req.MergedBase) {
295 *problems = append(*problems, fmt.Sprintf(
296 "%s: merged_base must be the full commit id the base pointed at before the landing", path))
297 }
281 298 if !shaRE.MatchString(req.MergedHead) {
282 299 *problems = append(*problems, fmt.Sprintf(
283 300 "%s: state is merged, so merged_head must be the full commit id that landed", path))
@@ -294,9 +311,9 @@ func applyRequest(req *Request, raw, path string, problems *[]string) {
294 311 } else {
295 312 req.MergedAt = t.UTC()
296 313 }
297 } else if req.MergedHead != "" || req.MergedBy != "" || strings.TrimSpace(m.MergedAt) != "" {
314 } else if req.MergedBase != "" || req.MergedHead != "" || req.MergedBy != "" || strings.TrimSpace(m.MergedAt) != "" {
298 315 *problems = append(*problems, fmt.Sprintf(
299 "%s: merged_head, merged_by and merged_at only belong on a merged request (state is %q)", path, req.State))
316 "%s: merged_base, merged_head, merged_by and merged_at only belong on a merged request (state is %q)", path, req.State))
300 317 }
301 318 req.Description = body
302 319 }