PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/forge/v1

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v1
Namespace
moul / forge
Files
18 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/forge/v1
gno
0.9

Files (18)

  • README.mdmarkdown
  • gnomod.tomltoml
  • change.gnogno
  • errors.gnogno
  • forge.gnogno
  • issue.gnogno
  • pkg.gnogno
  • ref.gnogno
  • release.gnogno
  • validate.gnogno
  • change_test.gnogno
  • forge_test.gnogno
  • helpers_test.gnogno
  • issue_test.gnogno
  • pkg_test.gnogno
  • ref_test.gnogno
  • release_test.gnogno
  • validate_test.gnogno
  • change.gnogno
    1package forge23import "gno.land/p/nt/avl/v0"45// Change states.6const (7	StateOpen   = "open"8

    Functions

    not supported for pure packages by the node (vm/qfuncs)

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    StateMerged = "merged"
    9 StateClosed = "closed"
    10)
    11
    12// Review verdicts.
    13const (
    14 VerdictApprove = "approve"
    15 VerdictRequestChanges = "request-changes"
    16 VerdictComment = "comment"
    17)
    18
    19// Change is a change request (a pull request): a claim that TargetRef should be
    20// moved to include HeadOID, plus the reviews of that claim.
    21//
    22// Reviews are bound to the object id they reviewed, not to the change. Push a
    23// new head and every earlier approval stops counting: not by a policy toggle a
    24// maintainer can switch off, but because the approval names an object that is
    25// no longer what is being merged.
    26type Change struct {
    27 ID int64
    28 Title string
    29 Body string
    30 Author address
    31 SourceRepo string // forge repo id, or a mirror locator; "" means this repo
    32 SourceRef string
    33 HeadOID string
    34 TargetRef string
    35 State string
    36 CreatedAt int64
    37 UpdatedAt int64
    38
    39 MergedOID string // the object TargetRef moved to
    40 MergedBy address
    41 MergedAt int64
    42
    43 reviews *avl.Tree // address string -> *Review (latest per reviewer)
    44 comments *avl.Tree // padded id -> *Comment
    45 nextComment int64
    46}
    47
    48// Review is one reviewer's verdict on one object id.
    49type Review struct {
    50 Reviewer address
    51 Verdict string
    52 OID string // the head the reviewer actually looked at
    53 Body string
    54 Height int64
    55}
    56
    57// OpenChange files a change request. Permissionless, like an issue: the
    58// proposal costs its author gas and deposit, and costs a maintainer nothing
    59// until they choose to look.
    60func (r *Repo) OpenChange(actor address, height int64, title, body, sourceRepo, sourceRef, headOID, targetRef string) (*Change, error) {
    61 if r.Archived {
    62 return nil, ErrRepoArchived
    63 }
    64 if title == "" || !ValidLine(title, MaxTitleLen) {
    65 return nil, ErrInvalidText
    66 }
    67 if !ValidText(body, MaxBodyLen) {
    68 return nil, ErrInvalidText
    69 }
    70 if sourceRepo != "" && !ValidRepoID(sourceRepo) && !ValidMirror(sourceRepo) {
    71 return nil, ErrInvalidRepoID
    72 }
    73 if sourceRef != "" && !ValidRefName(sourceRef) {
    74 return nil, ErrInvalidRefName
    75 }
    76 if !ValidOID(headOID) {
    77 return nil, ErrInvalidOID
    78 }
    79 if !ValidRefName(targetRef) {
    80 return nil, ErrInvalidRefName
    81 }
    82 c := &Change{
    83 ID: r.nextChange,
    84 Title: title,
    85 Body: body,
    86 Author: actor,
    87 SourceRepo: sourceRepo,
    88 SourceRef: sourceRef,
    89 HeadOID: headOID,
    90 TargetRef: targetRef,
    91 State: StateOpen,
    92 CreatedAt: height,
    93 UpdatedAt: height,
    94 reviews: avl.NewTree(),
    95 comments: avl.NewTree(),
    96 }
    97 r.changes.Set(seqKey(c.ID), c)
    98 r.nextChange++
    99 return c, nil
    100}
    101
    102// UpdateChangeHead repoints an open change at a new object. The author may
    103// always update their own; a writer may update anyone's (the "maintainer pushed
    104// a fixup" case).
    105func (r *Repo) UpdateChangeHead(actor address, height, id int64, headOID string) error {
    106 c := r.Change(id)
    107 if c == nil {
    108 return ErrChangeNotFound
    109 }
    110 if c.State != StateOpen {
    111 return ErrChangeNotOpen
    112 }
    113 if c.Author != actor && !r.Can(actor, RoleWriter) {
    114 return ErrUnauthorized
    115 }
    116 if !ValidOID(headOID) {
    117 return ErrInvalidOID
    118 }
    119 if headOID == c.HeadOID {
    120 return ErrSameOID
    121 }
    122 c.HeadOID = headOID
    123 c.UpdatedAt = height
    124 return nil
    125}
    126
    127// ReviewChange records a verdict against the change's current head. Anyone may
    128// review; only a writer's approval counts toward the merge policy (see
    129// CountApprovals): an unprivileged review is signal, not authority.
    130func (r *Repo) ReviewChange(actor address, height, id int64, verdict, body string) error {
    131 if r.Archived {
    132 return ErrRepoArchived
    133 }
    134 c := r.Change(id)
    135 if c == nil {
    136 return ErrChangeNotFound
    137 }
    138 if c.State != StateOpen {
    139 return ErrChangeNotOpen
    140 }
    141 switch verdict {
    142 case VerdictApprove, VerdictRequestChanges, VerdictComment:
    143 default:
    144 return ErrInvalidVerdict
    145 }
    146 if verdict == VerdictApprove && actor == c.Author && !r.AllowSelfApproval {
    147 return ErrSelfApproval
    148 }
    149 if !ValidText(body, MaxCommentLen) {
    150 return ErrInvalidText
    151 }
    152 c.reviews.Set(actor.String(), &Review{
    153 Reviewer: actor,
    154 Verdict: verdict,
    155 OID: c.HeadOID,
    156 Body: body,
    157 Height: height,
    158 })
    159 c.UpdatedAt = height
    160 return nil
    161}
    162
    163// CommentChange appends a reply to a change request.
    164func (r *Repo) CommentChange(actor address, height, id int64, body string) (*Comment, error) {
    165 if r.Archived {
    166 return nil, ErrRepoArchived
    167 }
    168 c := r.Change(id)
    169 if c == nil {
    170 return nil, ErrChangeNotFound
    171 }
    172 if body == "" || !ValidText(body, MaxCommentLen) {
    173 return nil, ErrInvalidText
    174 }
    175 cm := &Comment{ID: c.nextComment, Author: actor, Body: body, CreatedAt: height}
    176 c.comments.Set(seqKey(cm.ID), cm)
    177 c.nextComment++
    178 c.UpdatedAt = height
    179 return cm, nil
    180}
    181
    182// CloseChange withdraws or rejects a change. Author or maintainer.
    183func (r *Repo) CloseChange(actor address, height, id int64) error {
    184 c := r.Change(id)
    185 if c == nil {
    186 return ErrChangeNotFound
    187 }
    188 if c.State != StateOpen {
    189 return ErrChangeNotOpen
    190 }
    191 if c.Author != actor && !r.Can(actor, RoleMaintainer) {
    192 return ErrUnauthorized
    193 }
    194 c.State = StateClosed
    195 c.UpdatedAt = height
    196 return nil
    197}
    198
    199// CountApprovals counts approvals that still apply: cast by a writer or above,
    200// against the change's current head, and (unless the repo allows it) not the
    201// author's own.
    202func (r *Repo) CountApprovals(c *Change) int {
    203 n := 0
    204 c.reviews.Iterate("", "", func(_ string, value any) bool {
    205 rv := value.(*Review)
    206 if rv.Verdict != VerdictApprove || rv.OID != c.HeadOID {
    207 return false
    208 }
    209 if rv.Reviewer == c.Author && !r.AllowSelfApproval {
    210 return false
    211 }
    212 if !r.Can(rv.Reviewer, RoleWriter) {
    213 return false
    214 }
    215 n++
    216 return false
    217 })
    218 return n
    219}
    220
    221// CountBlocking counts writers who requested changes on the current head.
    222func (r *Repo) CountBlocking(c *Change) int {
    223 n := 0
    224 c.reviews.Iterate("", "", func(_ string, value any) bool {
    225 rv := value.(*Review)
    226 if rv.Verdict == VerdictRequestChanges && rv.OID == c.HeadOID && r.Can(rv.Reviewer, RoleWriter) {
    227 n++
    228 }
    229 return false
    230 })
    231 return n
    232}
    233
    234// MergeChange moves TargetRef to mergedOID and records the move as one more
    235// entry in the reference log, tagged with the change it came from.
    236//
    237// expectedTargetOID is a compare-and-swap on the target ref ("" when the ref
    238// does not exist yet): a change approved against one base cannot be merged onto
    239// a base that moved underneath it. mergedOID is computed off chain by whoever
    240// performs the merge: the chain records the claim, signed, ordered and
    241// attributed, and a client with the objects verifies that the result actually
    242// contains HeadOID.
    243func (r *Repo) MergeChange(actor address, height, id int64, expectedTargetOID, mergedOID, note string) (*LogEntry, error) {
    244 if r.Archived {
    245 return nil, ErrRepoArchived
    246 }
    247 c := r.Change(id)
    248 if c == nil {
    249 return nil, ErrChangeNotFound
    250 }
    251 if c.State != StateOpen {
    252 return nil, ErrChangeNotOpen
    253 }
    254 if !r.Can(actor, RoleMaintainer) {
    255 return nil, ErrUnauthorized
    256 }
    257 if !ValidOID(mergedOID) {
    258 return nil, ErrInvalidOID
    259 }
    260 if !ValidLine(note, MaxNoteLen) {
    261 return nil, ErrInvalidText
    262 }
    263 if r.CountBlocking(c) > 0 {
    264 return nil, ErrChangesRequested
    265 }
    266 if r.CountApprovals(c) < r.RequiredApprovals {
    267 return nil, ErrNotEnoughApproval
    268 }
    269 cur := r.Ref(c.TargetRef)
    270 switch {
    271 case cur == nil && expectedTargetOID != "":
    272 return nil, ErrRefNotFound
    273 case cur != nil && cur.OID != expectedTargetOID:
    274 return nil, ErrStaleRef
    275 case cur != nil && cur.OID == mergedOID:
    276 return nil, ErrSameOID
    277 }
    278 old := ""
    279 if cur != nil {
    280 old = cur.OID
    281 }
    282 r.refs.Set(c.TargetRef, &Ref{Name: c.TargetRef, OID: mergedOID, UpdatedAt: height, UpdatedBy: actor})
    283 e := r.appendLog(actor, height, c.TargetRef, old, mergedOID, KindMerge, c.ID, note)
    284 c.State = StateMerged
    285 c.MergedOID = mergedOID
    286 c.MergedBy = actor
    287 c.MergedAt = height
    288 c.UpdatedAt = height
    289 return e, nil
    290}
    291
    292// Change returns a change by id, or nil.
    293func (r *Repo) Change(id int64) *Change {
    294 v := r.changes.Get(seqKey(id))
    295 if v == nil {
    296 return nil
    297 }
    298 return v.(*Change)
    299}
    300
    301// IterateChanges walks change requests newest-first.
    302func (r *Repo) IterateChanges(offset, count int, cb func(*Change) bool) {
    303 if count <= 0 {
    304 count = r.changes.Size()
    305 }
    306 r.changes.ReverseIterateByOffset(offset, count, func(_ string, value any) bool {
    307 return cb(value.(*Change))
    308 })
    309}
    310
    311// OpenChangeCount counts change requests still open.
    312func (r *Repo) OpenChangeCount() int {
    313 n := 0
    314 r.changes.Iterate("", "", func(_ string, value any) bool {
    315 if value.(*Change).State == StateOpen {
    316 n++
    317 }
    318 return false
    319 })
    320 return n
    321}
    322
    323// ReviewCount is the number of reviewers who have weighed in (latest verdict
    324// per reviewer, on any head).
    325func (c *Change) ReviewCount() int { return c.reviews.Size() }
    326
    327// Review returns a reviewer's latest verdict, or nil.
    328func (c *Change) Review(a address) *Review {
    329 v := c.reviews.Get(a.String())
    330 if v == nil {
    331 return nil
    332 }
    333 return v.(*Review)
    334}
    335
    336// IterateReviews walks reviews in reviewer-address order.
    337func (c *Change) IterateReviews(cb func(*Review) bool) {
    338 c.reviews.Iterate("", "", func(_ string, value any) bool {
    339 return cb(value.(*Review))
    340 })
    341}
    342
    343// CommentCount is the number of replies on the change.
    344func (c *Change) CommentCount() int { return c.comments.Size() }
    345
    346// IterateComments walks replies oldest-first.
    347func (c *Change) IterateComments(offset, count int, cb func(*Comment) bool) {
    348 if count <= 0 {
    349 count = c.comments.Size()
    350 }
    351 c.comments.IterateByOffset(offset, count, func(_ string, value any) bool {
    352 return cb(value.(*Comment))
    353 })
    354}
    355
    356// Stale reports whether a review no longer applies to the change's head.
    357func (c *Change) Stale(rv *Review) bool { return rv.OID != c.HeadOID }
    358