~bigbes/sr-ht-spec

ref: 3c563e4d3eafe7ead010a894d18b0eb8ab31e6c5 sr-ht-spec/service/review.go -rw-r--r-- 3.6 KiB
3c563e4d — Eugene Blikh feat(web): proposal review page — prose diff + approve/reject (Phase 4) 26 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package service

import (
	"context"
	"fmt"
	"sort"

	"sourcecraft.dev/bigbes/sr-ht-spec/core"
)

// ProposalDoc is one document a proposal touches, as the review page needs it:
// its path, the approved content it was based on, and the proposed content on
// the branch. It is the input to the prose diff, which is the web layer's to
// render — this layer reads git and hands over bytes.
type ProposalDoc struct {
	// Path is the document's path on the proposal branch.
	Path string

	// Base is the document's content at the proposal's base — the approved text
	// the change was made against. Nil for a document the proposal adds, which
	// is the signal to render it as wholly new rather than as a diff.
	Base []byte

	// Proposed is the document's content on the proposal branch.
	Proposed []byte

	// New reports whether the document did not exist at the base.
	New bool
}

// ProposalDiff returns every document a proposal changes, each with the base and
// proposed content the review page diffs.
//
// It reads the base and the proposal branch through the normal pinned-revision
// path, not the ReadDocumentAtRef bypass: the branch tip is resolved to a commit
// sha first, and an object name is a legitimate read whatever it points at. The
// bypass exists for reading a branch *by name*; here the review already holds
// the proposal and can pin it.
//
// Only genuinely changed documents are returned — a proposal branch is cut from
// the base, so most of its documents are byte-identical to it and are not diffs.
// A proposal changes only documents (agents cannot rename or delete), so a
// document present at the base is present on the branch; the reverse asymmetry,
// a document added by the proposal, is marked New.
func (s *Service) ProposalDiff(ctx context.Context, p Proposal) ([]ProposalDoc, error) {
	sp, err := s.OpenSpace(ctx, p.Space)
	if err != nil {
		return nil, err
	}

	baseDocs, err := s.ListDocuments(ctx, sp, p.BaseRev)
	if err != nil {
		return nil, fmt.Errorf("service: read base %s of proposal %d: %w", short(p.BaseRev), p.ID, err)
	}
	base := make(map[string][]byte, len(baseDocs))
	for _, d := range baseDocs {
		base[d.Path] = d.Data
	}

	head, err := sp.Repo.BranchHead(ctx, p.Branch)
	if err != nil {
		return nil, readErr(err, "read head of %s in %s", p.Branch, p.Space)
	}
	branchDocs, err := s.ListDocuments(ctx, sp, head.String())
	if err != nil {
		return nil, fmt.Errorf("service: read proposal branch %s: %w", p.Branch, err)
	}

	var out []ProposalDoc
	for _, d := range branchDocs {
		prior, existed := base[d.Path]
		switch {
		case !existed:
			out = append(out, ProposalDoc{Path: d.Path, Proposed: d.Data, New: true})
		case !bytesEqual(prior, d.Data):
			out = append(out, ProposalDoc{Path: d.Path, Base: prior, Proposed: d.Data})
		}
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
	return out, nil
}

// bytesEqual reports byte equality. It exists so ProposalDiff does not pull in
// bytes for a single comparison, and reads as intent at the call site.
func bytesEqual(a, b []byte) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

// MergeHuman lands a proposal on the owner's approval — the review page's
// approve button. It is Merge with the approval kind fixed, so the surface does
// not choose it: a browser approve is always human, and a caller that could pass
// ApprovalPolicy here would be able to launder a firehose merge as reviewed.
func (s *Service) MergeHuman(ctx context.Context, ref core.SpaceRef, proposalID int) (Proposal, error) {
	return s.Merge(ctx, ref, proposalID, core.ApprovalHuman)
}