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