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}