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/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()