this repo has no description
1package models
2
3import (
4 "fmt"
5 "log"
6 "slices"
7 "strings"
8 "time"
9
10 "github.com/bluesky-social/indigo/atproto/syntax"
11 "tangled.org/core/api/tangled"
12 "tangled.org/core/patchutil"
13 "tangled.org/core/types"
14)
15
16type PullState int
17
18const (
19 PullClosed PullState = iota
20 PullOpen
21 PullMerged
22 PullDeleted
23)
24
25func (p PullState) String() string {
26 switch p {
27 case PullOpen:
28 return "open"
29 case PullMerged:
30 return "merged"
31 case PullClosed:
32 return "closed"
33 case PullDeleted:
34 return "deleted"
35 default:
36 return "closed"
37 }
38}
39
40func (p PullState) IsOpen() bool {
41 return p == PullOpen
42}
43func (p PullState) IsMerged() bool {
44 return p == PullMerged
45}
46func (p PullState) IsClosed() bool {
47 return p == PullClosed
48}
49func (p PullState) IsDeleted() bool {
50 return p == PullDeleted
51}
52
53type Pull struct {
54 // ids
55 ID int
56 PullId int
57
58 // at ids
59 RepoAt syntax.ATURI
60 OwnerDid string
61 Rkey string
62
63 // content
64 Title string
65 Body string
66 TargetBranch string
67 State PullState
68 Submissions []*PullSubmission
69
70 // stacking
71 StackId string // nullable string
72 ChangeId string // nullable string
73 ParentChangeId string // nullable string
74
75 // meta
76 Created time.Time
77 PullSource *PullSource
78
79 // optionally, populate this when querying for reverse mappings
80 Labels LabelState
81 Repo *Repo
82}
83
84func (p Pull) AsRecord() tangled.RepoPull {
85 var source *tangled.RepoPull_Source
86 if p.PullSource != nil {
87 source = &tangled.RepoPull_Source{}
88 source.Branch = p.PullSource.Branch
89 source.Sha = p.LatestSha()
90 if p.PullSource.RepoAt != nil {
91 s := p.PullSource.RepoAt.String()
92 source.Repo = &s
93 }
94 }
95
96 record := tangled.RepoPull{
97 Title: p.Title,
98 Body: &p.Body,
99 CreatedAt: p.Created.Format(time.RFC3339),
100 Target: &tangled.RepoPull_Target{
101 Repo: p.RepoAt.String(),
102 Branch: p.TargetBranch,
103 },
104 Patch: p.LatestPatch(),
105 Source: source,
106 }
107 return record
108}
109
110type PullSource struct {
111 Branch string
112 RepoAt *syntax.ATURI
113
114 // optionally populate this for reverse mappings
115 Repo *Repo
116}
117
118type PullSubmission struct {
119 // ids
120 ID int
121
122 // at ids
123 PullAt syntax.ATURI
124
125 // content
126 RoundNumber int
127 Patch string
128 Combined string
129 Comments []PullComment
130 SourceRev string // include the rev that was used to create this submission: only for branch/fork PRs
131
132 // meta
133 Created time.Time
134}
135
136type PullComment struct {
137 // ids
138 ID int
139 PullId int
140 SubmissionId int
141
142 // at ids
143 RepoAt string
144 OwnerDid string
145 CommentAt string
146
147 // content
148 Body string
149
150 // meta
151 Mentions []syntax.DID
152 References []syntax.ATURI
153
154 // meta
155 Created time.Time
156}
157
158func (p *PullComment) AtUri() syntax.ATURI {
159 return syntax.ATURI(p.CommentAt)
160}
161
162// func (p *PullComment) AsRecord() tangled.RepoPullComment {
163// mentions := make([]string, len(p.Mentions))
164// for i, did := range p.Mentions {
165// mentions[i] = string(did)
166// }
167// references := make([]string, len(p.References))
168// for i, uri := range p.References {
169// references[i] = string(uri)
170// }
171// return tangled.RepoPullComment{
172// Pull: p.PullAt,
173// Body: p.Body,
174// Mentions: mentions,
175// References: references,
176// CreatedAt: p.Created.Format(time.RFC3339),
177// }
178// }
179
180func (p *Pull) LastRoundNumber() int {
181 return len(p.Submissions) - 1
182}
183
184func (p *Pull) LatestSubmission() *PullSubmission {
185 return p.Submissions[p.LastRoundNumber()]
186}
187
188func (p *Pull) LatestPatch() string {
189 return p.LatestSubmission().Patch
190}
191
192func (p *Pull) LatestSha() string {
193 return p.LatestSubmission().SourceRev
194}
195
196func (p *Pull) AtUri() syntax.ATURI {
197 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.OwnerDid, tangled.RepoPullNSID, p.Rkey))
198}
199
200func (p *Pull) IsPatchBased() bool {
201 return p.PullSource == nil
202}
203
204func (p *Pull) IsBranchBased() bool {
205 if p.PullSource != nil {
206 if p.PullSource.RepoAt != nil {
207 return p.PullSource.RepoAt == &p.RepoAt
208 } else {
209 // no repo specified
210 return true
211 }
212 }
213 return false
214}
215
216func (p *Pull) IsForkBased() bool {
217 if p.PullSource != nil {
218 if p.PullSource.RepoAt != nil {
219 // make sure repos are different
220 return p.PullSource.RepoAt != &p.RepoAt
221 }
222 }
223 return false
224}
225
226func (p *Pull) IsStacked() bool {
227 return p.StackId != ""
228}
229
230func (p *Pull) Participants() []string {
231 participantSet := make(map[string]struct{})
232 participants := []string{}
233
234 addParticipant := func(did string) {
235 if _, exists := participantSet[did]; !exists {
236 participantSet[did] = struct{}{}
237 participants = append(participants, did)
238 }
239 }
240
241 addParticipant(p.OwnerDid)
242
243 for _, s := range p.Submissions {
244 for _, sp := range s.Participants() {
245 addParticipant(sp)
246 }
247 }
248
249 return participants
250}
251
252func (s PullSubmission) IsFormatPatch() bool {
253 return patchutil.IsFormatPatch(s.Patch)
254}
255
256func (s PullSubmission) AsFormatPatch() []types.FormatPatch {
257 patches, err := patchutil.ExtractPatches(s.Patch)
258 if err != nil {
259 log.Println("error extracting patches from submission:", err)
260 return []types.FormatPatch{}
261 }
262
263 return patches
264}
265
266func (s *PullSubmission) Participants() []string {
267 participantSet := make(map[string]struct{})
268 participants := []string{}
269
270 addParticipant := func(did string) {
271 if _, exists := participantSet[did]; !exists {
272 participantSet[did] = struct{}{}
273 participants = append(participants, did)
274 }
275 }
276
277 addParticipant(s.PullAt.Authority().String())
278
279 for _, c := range s.Comments {
280 addParticipant(c.OwnerDid)
281 }
282
283 return participants
284}
285
286func (s PullSubmission) CombinedPatch() string {
287 if s.Combined == "" {
288 return s.Patch
289 }
290
291 return s.Combined
292}
293
294type Stack []*Pull
295
296// position of this pull in the stack
297func (stack Stack) Position(pull *Pull) int {
298 return slices.IndexFunc(stack, func(p *Pull) bool {
299 return p.ChangeId == pull.ChangeId
300 })
301}
302
303// all pulls below this pull (including self) in this stack
304//
305// nil if this pull does not belong to this stack
306func (stack Stack) Below(pull *Pull) Stack {
307 position := stack.Position(pull)
308
309 if position < 0 {
310 return nil
311 }
312
313 return stack[position:]
314}
315
316// all pulls below this pull (excluding self) in this stack
317func (stack Stack) StrictlyBelow(pull *Pull) Stack {
318 below := stack.Below(pull)
319
320 if len(below) > 0 {
321 return below[1:]
322 }
323
324 return nil
325}
326
327// all pulls above this pull (including self) in this stack
328func (stack Stack) Above(pull *Pull) Stack {
329 position := stack.Position(pull)
330
331 if position < 0 {
332 return nil
333 }
334
335 return stack[:position+1]
336}
337
338// all pulls below this pull (excluding self) in this stack
339func (stack Stack) StrictlyAbove(pull *Pull) Stack {
340 above := stack.Above(pull)
341
342 if len(above) > 0 {
343 return above[:len(above)-1]
344 }
345
346 return nil
347}
348
349// the combined format-patches of all the newest submissions in this stack
350func (stack Stack) CombinedPatch() string {
351 // go in reverse order because the bottom of the stack is the last element in the slice
352 var combined strings.Builder
353 for idx := range stack {
354 pull := stack[len(stack)-1-idx]
355 combined.WriteString(pull.LatestPatch())
356 combined.WriteString("\n")
357 }
358 return combined.String()
359}
360
361// filter out PRs that are "active"
362//
363// PRs that are still open are active
364func (stack Stack) Mergeable() Stack {
365 var mergeable Stack
366
367 for _, p := range stack {
368 // stop at the first merged PR
369 if p.State == PullMerged || p.State == PullClosed {
370 break
371 }
372
373 // skip over deleted PRs
374 if p.State != PullDeleted {
375 mergeable = append(mergeable, p)
376 }
377 }
378
379 return mergeable
380}
381
382type BranchDeleteStatus struct {
383 Repo *Repo
384 Branch string
385}