this repo has no description
at push-tvqsxkkxqolv 319 lines 6.0 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 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 Repo *Repo 81} 82 83func (p Pull) AsRecord() tangled.RepoPull { 84 var source *tangled.RepoPull_Source 85 if p.PullSource != nil { 86 s := p.PullSource.AsRecord() 87 source = &s 88 source.Sha = p.LatestSha() 89 } 90 91 record := tangled.RepoPull{ 92 Title: p.Title, 93 Body: &p.Body, 94 CreatedAt: p.Created.Format(time.RFC3339), 95 Target: &tangled.RepoPull_Target{ 96 Repo: p.RepoAt.String(), 97 Branch: p.TargetBranch, 98 }, 99 Patch: p.LatestPatch(), 100 Source: source, 101 } 102 return record 103} 104 105type PullSource struct { 106 Branch string 107 RepoAt *syntax.ATURI 108 109 // optionally populate this for reverse mappings 110 Repo *Repo 111} 112 113func (p PullSource) AsRecord() tangled.RepoPull_Source { 114 var repoAt *string 115 if p.RepoAt != nil { 116 s := p.RepoAt.String() 117 repoAt = &s 118 } 119 record := tangled.RepoPull_Source{ 120 Branch: p.Branch, 121 Repo: repoAt, 122 } 123 return record 124} 125 126type PullSubmission struct { 127 // ids 128 ID int 129 PullId int 130 131 // at ids 132 RepoAt syntax.ATURI 133 134 // content 135 RoundNumber int 136 Patch string 137 Combined string 138 Comments []PullComment 139 SourceRev string // include the rev that was used to create this submission: only for branch/fork PRs 140 141 // meta 142 Created time.Time 143} 144 145type PullComment struct { 146 // ids 147 ID int 148 PullId int 149 SubmissionId int 150 151 // at ids 152 RepoAt string 153 OwnerDid string 154 CommentAt string 155 156 // content 157 Body string 158 159 // meta 160 Created time.Time 161} 162 163func (p *Pull) LatestPatch() string { 164 latestSubmission := p.Submissions[p.LastRoundNumber()] 165 return latestSubmission.Patch 166} 167 168func (p *Pull) LatestSha() string { 169 latestSubmission := p.Submissions[p.LastRoundNumber()] 170 return latestSubmission.SourceRev 171} 172 173func (p *Pull) PullAt() syntax.ATURI { 174 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.OwnerDid, tangled.RepoPullNSID, p.Rkey)) 175} 176 177func (p *Pull) LastRoundNumber() int { 178 return len(p.Submissions) - 1 179} 180 181func (p *Pull) IsPatchBased() bool { 182 return p.PullSource == nil 183} 184 185func (p *Pull) IsBranchBased() bool { 186 if p.PullSource != nil { 187 if p.PullSource.RepoAt != nil { 188 return p.PullSource.RepoAt == &p.RepoAt 189 } else { 190 // no repo specified 191 return true 192 } 193 } 194 return false 195} 196 197func (p *Pull) IsForkBased() bool { 198 if p.PullSource != nil { 199 if p.PullSource.RepoAt != nil { 200 // make sure repos are different 201 return p.PullSource.RepoAt != &p.RepoAt 202 } 203 } 204 return false 205} 206 207func (p *Pull) IsStacked() bool { 208 return p.StackId != "" 209} 210 211func (s PullSubmission) IsFormatPatch() bool { 212 return patchutil.IsFormatPatch(s.Patch) 213} 214 215func (s PullSubmission) AsFormatPatch() []types.FormatPatch { 216 patches, err := patchutil.ExtractPatches(s.Patch) 217 if err != nil { 218 log.Println("error extracting patches from submission:", err) 219 return []types.FormatPatch{} 220 } 221 222 return patches 223} 224 225func (s PullSubmission) CombinedPatch() string { 226 if s.Combined == "" { 227 return s.Patch 228 } 229 230 return s.Combined 231} 232 233type Stack []*Pull 234 235// position of this pull in the stack 236func (stack Stack) Position(pull *Pull) int { 237 return slices.IndexFunc(stack, func(p *Pull) bool { 238 return p.ChangeId == pull.ChangeId 239 }) 240} 241 242// all pulls below this pull (including self) in this stack 243// 244// nil if this pull does not belong to this stack 245func (stack Stack) Below(pull *Pull) Stack { 246 position := stack.Position(pull) 247 248 if position < 0 { 249 return nil 250 } 251 252 return stack[position:] 253} 254 255// all pulls below this pull (excluding self) in this stack 256func (stack Stack) StrictlyBelow(pull *Pull) Stack { 257 below := stack.Below(pull) 258 259 if len(below) > 0 { 260 return below[1:] 261 } 262 263 return nil 264} 265 266// all pulls above this pull (including self) in this stack 267func (stack Stack) Above(pull *Pull) Stack { 268 position := stack.Position(pull) 269 270 if position < 0 { 271 return nil 272 } 273 274 return stack[:position+1] 275} 276 277// all pulls below this pull (excluding self) in this stack 278func (stack Stack) StrictlyAbove(pull *Pull) Stack { 279 above := stack.Above(pull) 280 281 if len(above) > 0 { 282 return above[:len(above)-1] 283 } 284 285 return nil 286} 287 288// the combined format-patches of all the newest submissions in this stack 289func (stack Stack) CombinedPatch() string { 290 // go in reverse order because the bottom of the stack is the last element in the slice 291 var combined strings.Builder 292 for idx := range stack { 293 pull := stack[len(stack)-1-idx] 294 combined.WriteString(pull.LatestPatch()) 295 combined.WriteString("\n") 296 } 297 return combined.String() 298} 299 300// filter out PRs that are "active" 301// 302// PRs that are still open are active 303func (stack Stack) Mergeable() Stack { 304 var mergeable Stack 305 306 for _, p := range stack { 307 // stop at the first merged PR 308 if p.State == PullMerged || p.State == PullClosed { 309 break 310 } 311 312 // skip over deleted PRs 313 if p.State != PullDeleted { 314 mergeable = append(mergeable, p) 315 } 316 } 317 318 return mergeable 319}