vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0: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/request_write.go +24−4
| @@ -53,6 +53,9 @@ func validateRequestForWrite(r *Request) error { | |||
| 53 | 53 | if r.Head != "" && !shaRE.MatchString(r.Head) { | |
| 54 | 54 | return fmt.Errorf("requests: head %q is not a full commit id", r.Head) | |
| 55 | 55 | } | |
| 56 | if r.MergedBase != "" && !shaRE.MatchString(r.MergedBase) { | ||
| 57 | return fmt.Errorf("requests: merged base %q is not a full commit id", r.MergedBase) | ||
| 58 | } | ||
| 56 | 59 | if _, problems := parseCloses(r.Closes, "request"); len(problems) > 0 { | |
| 57 | 60 | return fmt.Errorf("%s", strings.Join(problems, "; ")) | |
| 58 | 61 | } | |
| @@ -71,6 +74,9 @@ func renderRequest(r *Request) ([]byte, error) { | |||
| 71 | 74 | d.List("closes", r.Closes) | |
| 72 | 75 | d.Word("created", r.Created.UTC().Format(time.RFC3339)) | |
| 73 | 76 | if r.State == "merged" { | |
| 77 | if r.MergedBase != "" { | ||
| 78 | d.Word("merged_base", r.MergedBase) | ||
| 79 | } | ||
| 74 | 80 | d.Word("merged_head", r.MergedHead) | |
| 75 | 81 | d.Word("merged_by", r.MergedBy) | |
| 76 | 82 | d.Word("merged_at", r.MergedAt.UTC().Format(time.RFC3339)) | |
| @@ -229,15 +235,25 @@ func SetRequestState(g *git.Repo, id, state, actor string) (*Request, error) { | |||
| 229 | 235 | } | |
| 230 | 236 | ||
| 231 | 237 | // MarkMerged records a landing: the state becomes merged and the request names the | |
| 232 | // commit that landed, who landed it, and when. The commit is written in the same | ||
| 233 | // commit as the state, so the file cannot say "merged" and mean nothing. | ||
| 238 | // commit that landed, the base tip it landed on, who landed it, and when. The mark is | ||
| 239 | // written in the same commit as the state, so the file cannot say "merged" and mean | ||
| 240 | // nothing. | ||
| 241 | // | ||
| 242 | // landedBase is what the base branch pointed at before the landing. It is what the | ||
| 243 | // Files tab diffs a merged request against, because the head is in the base | ||
| 244 | // afterwards and a diff against the base as it is now says nothing. A caller that | ||
| 245 | // does not know it — a retry that finds the head already landed has no base tip of | ||
| 246 | // its own to name — passes "", which leaves whatever the record already says. | ||
| 234 | 247 | // | |
| 235 | 248 | // A request that is already merged with the same commit is left alone and returned | |
| 236 | 249 | // unchanged, which makes a retry after a half-finished landing harmless. | |
| 237 | func MarkMerged(g *git.Repo, id, landedHead, actor string, at time.Time) (*Request, []string, error) { | ||
| 250 | func MarkMerged(g *git.Repo, id, landedBase, landedHead, actor string, at time.Time) (*Request, []string, error) { | ||
| 238 | 251 | if !shaRE.MatchString(landedHead) { | |
| 239 | 252 | return nil, nil, fmt.Errorf("requests: merged head must be a full commit id, got %q", landedHead) | |
| 240 | 253 | } | |
| 254 | if landedBase != "" && !shaRE.MatchString(landedBase) { | ||
| 255 | return nil, nil, fmt.Errorf("requests: merged base must be a full commit id, got %q", landedBase) | ||
| 256 | } | ||
| 241 | 257 | var ( | |
| 242 | 258 | out *Request | |
| 243 | 259 | closed []string | |
| @@ -248,11 +264,15 @@ func MarkMerged(g *git.Repo, id, landedHead, actor string, at time.Time) (*Reque | |||
| 248 | 264 | if err != nil { | |
| 249 | 265 | return err | |
| 250 | 266 | } | |
| 251 | if r.State == "merged" && r.MergedHead == landedHead { | ||
| 267 | if landedBase == "" { | ||
| 268 | landedBase = r.MergedBase // a retry that cannot name the base keeps the one it has | ||
| 269 | } | ||
| 270 | if r.State == "merged" && r.MergedHead == landedHead && r.MergedBase == landedBase { | ||
| 252 | 271 | out = r | |
| 253 | 272 | return ErrNoChange | |
| 254 | 273 | } | |
| 255 | 274 | r.State = "merged" | |
| 275 | r.MergedBase = landedBase | ||
| 256 | 276 | r.MergedHead = landedHead | |
| 257 | 277 | r.MergedBy = actor | |
| 258 | 278 | r.MergedAt = at.UTC() | |