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 · 14d 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".

forge/landing/land.go +10−5

@@ -96,7 +96,10 @@ func Land(g Gate, id, actor string, now time.Time) (*Plan, error) {
96 96 if plan.Request.State == "merged" && plan.Request.MergedHead != "" {
97 97 return plan, nil
98 98 }
99 closed, err := marked(g, id, plan.Head, actor, now)
99 // The base already contains the head, so there is no "before" left to
100 // name: plan.OldTip is the moved tip, not the one the landing started
101 // from. Marking without a base keeps the record honest.
102 closed, err := marked(g, id, "", plan.Head, actor, now)
100 103 if err != nil {
101 104 return plan, &ErrLandedUnmarked{NewTip: plan.OldTip, Err: err}
102 105 }
@@ -120,7 +123,7 @@ func Land(g Gate, id, actor string, now time.Time) (*Plan, error) {
120 123 g.Audit(fmt.Sprintf("merge %s by %s (head %s)", g.Ref(plan.Base), actor, format.SHA(plan.NewTip)))
121 124 }
122 125 }
123 closed, err := marked(g, id, plan.NewTip, actor, now)
126 closed, err := marked(g, id, plan.OldTip, plan.NewTip, actor, now)
124 127 if err != nil {
125 128 return plan, &ErrLandedUnmarked{NewTip: plan.NewTip, Err: err}
126 129 }
@@ -212,9 +215,11 @@ func Prepare(g Gate, id, actor string, now time.Time) (*Plan, error) {
212 215 }
213 216
214 217 // marked writes the merged state and turns a validation problem into an error, so a
215 // landing never leaves a request file the metadata reader would refuse.
216 func marked(g Gate, id, head, actor string, now time.Time) ([]string, error) {
217 _, closed, err := metadata.MarkMerged(g.Repo, id, head, actor, now)
218 // landing never leaves a request file the metadata reader would refuse. The base tip
219 // is what the branch pointed at before it moved: it is the only record of what the
220 // merge changed once the head is in the base.
221 func marked(g Gate, id, baseTip, head, actor string, now time.Time) ([]string, error) {
222 _, closed, err := metadata.MarkMerged(g.Repo, id, baseTip, head, actor, now)
218 223 if err != nil {
219 224 return nil, err
220 225 }