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