Monorepo for Tangled tangled.org
771
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview/db: update db schemas for new pull model

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by tangled.org 3e5c0ffe 9592f2ef

+420 -140
+42
appview/db/db.go
··· 1367 1367 return err 1368 1368 }) 1369 1369 1370 + orm.RunMigration(conn, logger, "add-blob-data-to-pull-submissions", func(tx *sql.Tx) error { 1371 + _, err := tx.Exec(` 1372 + alter table pull_submissions add column patch_blob_ref text; 1373 + alter table pull_submissions add column patch_blob_mime text; 1374 + alter table pull_submissions add column patch_blob_size integer; 1375 + `) 1376 + return err 1377 + }) 1378 + 1379 + orm.RunMigration(conn, logger, "replace-parent-change-id-with-aturi", func(tx *sql.Tx) error { 1380 + // add new column 1381 + _, err := tx.Exec(` 1382 + alter table pulls add column dependent_on text; 1383 + `) 1384 + if err != nil { 1385 + return err 1386 + } 1387 + 1388 + // populate dependent_on with at_uri of the parent 1389 + _, err = tx.Exec(` 1390 + update pulls 1391 + set dependent_on = ( 1392 + select at_uri 1393 + from pulls as parent 1394 + where parent.stack_id = pulls.stack_id 1395 + and parent.change_id = pulls.parent_change_id 1396 + ) 1397 + where parent_change_id is not null; 1398 + `) 1399 + if err != nil { 1400 + return err 1401 + } 1402 + 1403 + // drop old columns 1404 + _, err = tx.Exec(` 1405 + alter table pulls drop column parent_change_id; 1406 + alter table pulls drop column stack_id; 1407 + `) 1408 + 1409 + return err 1410 + }) 1411 + 1370 1412 return &DB{ 1371 1413 db, 1372 1414 logger,
+377 -139
appview/db/pulls.go
··· 12 12 "time" 13 13 14 14 "github.com/bluesky-social/indigo/atproto/syntax" 15 + lexutil "github.com/bluesky-social/indigo/lex/util" 16 + "github.com/ipfs/go-cid" 15 17 "tangled.org/core/appview/models" 16 18 "tangled.org/core/appview/pagination" 17 19 "tangled.org/core/orm" 20 + "tangled.org/core/sets" 18 21 ) 19 22 20 - func NewPull(tx *sql.Tx, pull *models.Pull) error { 23 + func comparePullSource(existing, new *models.PullSource) bool { 24 + if existing == nil && new == nil { 25 + return true 26 + } 27 + if existing == nil || new == nil { 28 + return false 29 + } 30 + if existing.Branch != new.Branch { 31 + return false 32 + } 33 + if existing.RepoAt == nil && new.RepoAt == nil { 34 + return true 35 + } 36 + if existing.RepoAt == nil || new.RepoAt == nil { 37 + return false 38 + } 39 + return *existing.RepoAt == *new.RepoAt 40 + } 41 + 42 + func compareSubmissions(existing, new []*models.PullSubmission) bool { 43 + if len(existing) != len(new) { 44 + return false 45 + } 46 + for i := range existing { 47 + if existing[i].Blob.Ref.String() != new[i].Blob.Ref.String() { 48 + return false 49 + } 50 + if existing[i].Blob.MimeType != new[i].Blob.MimeType { 51 + return false 52 + } 53 + if existing[i].Blob.Size != new[i].Blob.Size { 54 + return false 55 + } 56 + } 57 + return true 58 + } 59 + 60 + func PutPull(tx *sql.Tx, pull *models.Pull) error { 61 + // ensure sequence exists 62 + _, err := tx.Exec(` 63 + insert or ignore into repo_pull_seqs (repo_at, next_pull_id) 64 + values (?, 1) 65 + `, pull.RepoAt) 66 + if err != nil { 67 + return err 68 + } 69 + 70 + pulls, err := GetPulls( 71 + tx, 72 + orm.FilterEq("owner_did", pull.OwnerDid), 73 + orm.FilterEq("rkey", pull.Rkey), 74 + ) 75 + switch { 76 + case err != nil: 77 + return err 78 + case len(pulls) == 0: 79 + return createNewPull(tx, pull) 80 + case len(pulls) != 1: // should be unreachable 81 + return fmt.Errorf("invalid number of pulls returned: %d", len(pulls)) 82 + default: 83 + existingPull := pulls[0] 84 + if existingPull.State == models.PullMerged { 85 + return nil 86 + } 87 + 88 + dependentOnEqual := (existingPull.DependentOn == nil && pull.DependentOn == nil) || 89 + (existingPull.DependentOn != nil && pull.DependentOn != nil && *existingPull.DependentOn == *pull.DependentOn) 90 + 91 + pullSourceEqual := comparePullSource(existingPull.PullSource, pull.PullSource) 92 + submissionsEqual := compareSubmissions(existingPull.Submissions, pull.Submissions) 93 + 94 + if existingPull.Title == pull.Title && 95 + existingPull.Body == pull.Body && 96 + existingPull.TargetBranch == pull.TargetBranch && 97 + existingPull.RepoAt == pull.RepoAt && 98 + dependentOnEqual && 99 + pullSourceEqual && 100 + submissionsEqual { 101 + return nil 102 + } 103 + 104 + isLonger := len(existingPull.Submissions) < len(pull.Submissions) 105 + if isLonger { 106 + isAppendOnly := compareSubmissions(existingPull.Submissions, pull.Submissions[:len(existingPull.Submissions)]) 107 + if !isAppendOnly { 108 + return fmt.Errorf("the new pull does not treat submissions as append-only") 109 + } 110 + } else if !submissionsEqual { 111 + return fmt.Errorf("the new pull does not treat submissions as append-only") 112 + } 113 + 114 + pull.ID = existingPull.ID 115 + pull.PullId = existingPull.PullId 116 + return updatePull(tx, pull, existingPull) 117 + } 118 + } 119 + 120 + func createNewPull(tx *sql.Tx, pull *models.Pull) error { 21 121 _, err := tx.Exec(` 22 122 insert or ignore into repo_pull_seqs (repo_at, next_pull_id) 23 123 values (?, 1) ··· 47 147 x := pull.PullSource.RepoAt.String() 48 148 sourceRepoAt = &x 49 149 } 50 - } 51 - 52 - var stackId, changeId, parentChangeId *string 53 - if pull.StackId != "" { 54 - stackId = &pull.StackId 55 - } 56 - if pull.ChangeId != "" { 57 - changeId = &pull.ChangeId 58 - } 59 - if pull.ParentChangeId != "" { 60 - parentChangeId = &pull.ParentChangeId 61 150 } 62 151 63 152 result, err := tx.Exec( 64 153 ` 65 154 insert into pulls ( 66 - repo_at, owner_did, pull_id, title, target_branch, body, rkey, state, source_branch, source_repo_at, stack_id, change_id, parent_change_id 155 + repo_at, 156 + owner_did, 157 + pull_id, 158 + title, 159 + target_branch, 160 + body, 161 + rkey, 162 + state, 163 + dependent_on, 164 + source_branch, 165 + source_repo_at 67 166 ) 68 - values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 167 + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 69 168 pull.RepoAt, 70 169 pull.OwnerDid, 71 170 pull.PullId, ··· 74 173 pull.Body, 75 174 pull.Rkey, 76 175 pull.State, 176 + pull.DependentOn, 77 177 sourceBranch, 78 178 sourceRepoAt, 79 - stackId, 80 - changeId, 81 - parentChangeId, 82 179 ) 83 180 if err != nil { 84 181 return err ··· 91 188 } 92 189 pull.ID = int(id) 93 190 94 - _, err = tx.Exec(` 95 - insert into pull_submissions (pull_at, round_number, patch, combined, source_rev) 96 - values (?, ?, ?, ?, ?) 97 - `, pull.AtUri(), 0, pull.Submissions[0].Patch, pull.Submissions[0].Combined, pull.Submissions[0].SourceRev) 98 - if err != nil { 99 - return err 191 + for i, s := range pull.Submissions { 192 + _, err = tx.Exec(` 193 + insert into pull_submissions ( 194 + pull_at, 195 + round_number, 196 + patch, 197 + combined, 198 + source_rev, 199 + patch_blob_ref, 200 + patch_blob_mime, 201 + patch_blob_size 202 + ) 203 + values (?, ?, ?, ?, ?, ?, ?, ?) 204 + `, 205 + pull.AtUri(), 206 + i, 207 + s.Patch, 208 + s.Combined, 209 + s.SourceRev, 210 + s.Blob.Ref.String(), 211 + s.Blob.MimeType, 212 + s.Blob.Size, 213 + ) 214 + if err != nil { 215 + return err 216 + } 100 217 } 101 218 102 219 if err := putReferences(tx, pull.AtUri(), pull.References); err != nil { ··· 106 223 return nil 107 224 } 108 225 109 - func GetPullAt(e Execer, repoAt syntax.ATURI, pullId int) (syntax.ATURI, error) { 110 - pull, err := GetPull(e, repoAt, pullId) 226 + func updatePull(tx *sql.Tx, pull *models.Pull, existingPull *models.Pull) error { 227 + var sourceBranch, sourceRepoAt *string 228 + if pull.PullSource != nil { 229 + sourceBranch = &pull.PullSource.Branch 230 + if pull.PullSource.RepoAt != nil { 231 + x := pull.PullSource.RepoAt.String() 232 + sourceRepoAt = &x 233 + } 234 + } 235 + 236 + _, err := tx.Exec(` 237 + update pulls set 238 + title = ?, 239 + body = ?, 240 + target_branch = ?, 241 + dependent_on = ?, 242 + source_branch = ?, 243 + source_repo_at = ? 244 + where owner_did = ? and rkey = ? 245 + `, pull.Title, pull.Body, pull.TargetBranch, pull.DependentOn, sourceBranch, sourceRepoAt, pull.OwnerDid, pull.Rkey) 111 246 if err != nil { 112 - return "", err 247 + return err 248 + } 249 + 250 + // insert new submissions (append-only) 251 + for i := len(existingPull.Submissions); i < len(pull.Submissions); i++ { 252 + s := pull.Submissions[i] 253 + _, err = tx.Exec(` 254 + insert into pull_submissions ( 255 + pull_at, 256 + round_number, 257 + patch, 258 + combined, 259 + source_rev, 260 + patch_blob_ref, 261 + patch_blob_mime, 262 + patch_blob_size 263 + ) 264 + values (?, ?, ?, ?, ?, ?, ?, ?) 265 + `, 266 + pull.AtUri(), 267 + i, 268 + s.Patch, 269 + s.Combined, 270 + s.SourceRev, 271 + s.Blob.Ref.String(), 272 + s.Blob.MimeType, 273 + s.Blob.Size, 274 + ) 275 + if err != nil { 276 + return err 277 + } 113 278 } 114 - return pull.AtUri(), err 279 + 280 + if err := putReferences(tx, pull.AtUri(), pull.References); err != nil { 281 + return fmt.Errorf("put reference_links: %w", err) 282 + } 283 + return nil 115 284 } 116 285 117 286 func NextPullId(e Execer, repoAt syntax.ATURI) (int, error) { ··· 157 326 rkey, 158 327 source_branch, 159 328 source_repo_at, 160 - stack_id, 161 - change_id, 162 - parent_change_id 329 + dependent_on 163 330 from 164 331 pulls 165 332 %s ··· 177 344 for rows.Next() { 178 345 var pull models.Pull 179 346 var createdAt string 180 - var sourceBranch, sourceRepoAt, stackId, changeId, parentChangeId sql.NullString 347 + var sourceBranch, sourceRepoAt, dependentOn sql.NullString 181 348 err := rows.Scan( 182 349 &pull.ID, 183 350 &pull.OwnerDid, ··· 191 358 &pull.Rkey, 192 359 &sourceBranch, 193 360 &sourceRepoAt, 194 - &stackId, 195 - &changeId, 196 - &parentChangeId, 361 + &dependentOn, 197 362 ) 198 363 if err != nil { 199 364 return nil, err ··· 218 383 } 219 384 } 220 385 221 - if stackId.Valid { 222 - pull.StackId = stackId.String 223 - } 224 - if changeId.Valid { 225 - pull.ChangeId = changeId.String 226 - } 227 - if parentChangeId.Valid { 228 - pull.ParentChangeId = parentChangeId.String 386 + if dependentOn.Valid { 387 + x := syntax.ATURI(dependentOn.String) 388 + pull.DependentOn = &x 229 389 } 230 390 231 391 pulls[pull.AtUri()] = &pull ··· 257 417 } 258 418 } 259 419 260 - // collect pull source for all pulls that need it 261 - var sourceAts []syntax.ATURI 420 + // build up reverse mappings: p.Repo and p.PullSource 421 + var repoAts []syntax.ATURI 262 422 for _, p := range pulls { 423 + repoAts = append(repoAts, p.RepoAt) 263 424 if p.PullSource != nil && p.PullSource.RepoAt != nil { 264 - sourceAts = append(sourceAts, *p.PullSource.RepoAt) 425 + repoAts = append(repoAts, *p.PullSource.RepoAt) 265 426 } 266 427 } 267 - sourceRepos, err := GetRepos(e, orm.FilterIn("at_uri", sourceAts)) 428 + 429 + repos, err := GetRepos(e, orm.FilterIn("at_uri", repoAts)) 268 430 if err != nil && !errors.Is(err, sql.ErrNoRows) { 269 431 return nil, fmt.Errorf("failed to get source repos: %w", err) 270 432 } 271 - sourceRepoMap := make(map[syntax.ATURI]*models.Repo) 272 - for _, r := range sourceRepos { 273 - sourceRepoMap[r.RepoAt()] = &r 433 + 434 + repoMap := make(map[syntax.ATURI]*models.Repo) 435 + for _, r := range repos { 436 + repoMap[r.RepoAt()] = &r 274 437 } 438 + 275 439 for _, p := range pulls { 440 + if repo, ok := repoMap[p.RepoAt]; ok { 441 + p.Repo = repo 442 + } 443 + 276 444 if p.PullSource != nil && p.PullSource.RepoAt != nil { 277 - if sourceRepo, ok := sourceRepoMap[*p.PullSource.RepoAt]; ok { 445 + if sourceRepo, ok := repoMap[*p.PullSource.RepoAt]; ok { 278 446 p.PullSource.Repo = sourceRepo 279 447 } 280 448 } ··· 305 473 return GetPullsPaginated(e, pagination.Page{}, filters...) 306 474 } 307 475 308 - func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { 309 - pulls, err := GetPullsPaginated(e, pagination.Page{Limit: 1}, orm.FilterEq("repo_at", repoAt), orm.FilterEq("pull_id", pullId)) 476 + func GetPull(e Execer, filters ...orm.Filter) (*models.Pull, error) { 477 + pulls, err := GetPullsPaginated(e, pagination.Page{Limit: 1}, filters...) 310 478 if err != nil { 311 479 return nil, err 312 480 } ··· 339 507 patch, 340 508 combined, 341 509 created, 342 - source_rev 510 + source_rev, 511 + patch_blob_ref, 512 + patch_blob_mime, 513 + patch_blob_size 343 514 from 344 515 pull_submissions 345 516 %s ··· 358 529 for rows.Next() { 359 530 var submission models.PullSubmission 360 531 var submissionCreatedStr string 361 - var submissionSourceRev, submissionCombined sql.NullString 532 + var submissionSourceRev, submissionCombined sql.Null[string] 533 + var patchBlobRef, patchBlobMime sql.Null[string] 534 + var patchBlobSize sql.Null[int64] 362 535 err := rows.Scan( 363 536 &submission.ID, 364 537 &submission.PullAt, ··· 367 540 &submissionCombined, 368 541 &submissionCreatedStr, 369 542 &submissionSourceRev, 543 + &patchBlobRef, 544 + &patchBlobMime, 545 + &patchBlobSize, 370 546 ) 371 547 if err != nil { 372 548 return nil, err ··· 377 553 } 378 554 379 555 if submissionSourceRev.Valid { 380 - submission.SourceRev = submissionSourceRev.String 556 + submission.SourceRev = submissionSourceRev.V 381 557 } 382 558 383 559 if submissionCombined.Valid { 384 - submission.Combined = submissionCombined.String 560 + submission.Combined = submissionCombined.V 561 + } 562 + 563 + if patchBlobRef.Valid { 564 + submission.Blob.Ref = lexutil.LexLink(cid.MustParse(patchBlobRef.V)) 565 + } 566 + 567 + if patchBlobMime.Valid { 568 + submission.Blob.MimeType = patchBlobMime.V 569 + } 570 + 571 + if patchBlobSize.Valid { 572 + submission.Blob.Size = patchBlobSize.V 385 573 } 386 574 387 575 submissionMap[submission.ID] = &submission ··· 612 800 return i, nil 613 801 } 614 802 615 - func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error { 616 - _, err := e.Exec( 617 - `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? and state <> ?)`, 618 - pullState, 619 - repoAt, 620 - pullId, 621 - models.PullDeleted, // only update state of non-deleted pulls 622 - models.PullMerged, // only update state of non-merged pulls 623 - ) 624 - return err 625 - } 626 - 627 - func ClosePull(e Execer, repoAt syntax.ATURI, pullId int) error { 628 - err := SetPullState(e, repoAt, pullId, models.PullClosed) 629 - return err 630 - } 631 - 632 - func ReopenPull(e Execer, repoAt syntax.ATURI, pullId int) error { 633 - err := SetPullState(e, repoAt, pullId, models.PullOpen) 634 - return err 635 - } 636 - 637 - func MergePull(e Execer, repoAt syntax.ATURI, pullId int) error { 638 - err := SetPullState(e, repoAt, pullId, models.PullMerged) 639 - return err 640 - } 641 - 642 - func DeletePull(e Execer, repoAt syntax.ATURI, pullId int) error { 643 - err := SetPullState(e, repoAt, pullId, models.PullDeleted) 644 - return err 645 - } 646 - 647 - func ResubmitPull(e Execer, pullAt syntax.ATURI, newRoundNumber int, newPatch string, combinedPatch string, newSourceRev string) error { 648 - _, err := e.Exec(` 649 - insert into pull_submissions (pull_at, round_number, patch, combined, source_rev) 650 - values (?, ?, ?, ?, ?) 651 - `, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev) 652 - 653 - return err 654 - } 655 - 656 - func SetPullParentChangeId(e Execer, parentChangeId string, filters ...orm.Filter) error { 803 + // use with transaction 804 + func SetPullsState(e Execer, pullState models.PullState, filters ...orm.Filter) error { 657 805 var conditions []string 658 806 var args []any 659 807 660 - args = append(args, parentChangeId) 661 - 808 + args = append(args, pullState) 662 809 for _, filter := range filters { 663 810 conditions = append(conditions, filter.Condition()) 664 811 args = append(args, filter.Arg()...) 665 812 } 813 + args = append(args, models.PullAbandoned) // only update state of non-deleted pulls 814 + args = append(args, models.PullMerged) // only update state of non-merged pulls 666 815 667 816 whereClause := "" 668 817 if conditions != nil { 669 818 whereClause = " where " + strings.Join(conditions, " and ") 670 819 } 671 820 672 - query := fmt.Sprintf("update pulls set parent_change_id = ? %s", whereClause) 821 + query := fmt.Sprintf("update pulls set state = ? %s and state <> ? and state <> ?", whereClause) 822 + 673 823 _, err := e.Exec(query, args...) 824 + return err 825 + } 826 + 827 + func ClosePulls(e Execer, filters ...orm.Filter) error { 828 + return SetPullsState(e, models.PullClosed, filters...) 829 + } 830 + 831 + func ReopenPulls(e Execer, filters ...orm.Filter) error { 832 + return SetPullsState(e, models.PullOpen, filters...) 833 + } 834 + 835 + func MergePulls(e Execer, filters ...orm.Filter) error { 836 + return SetPullsState(e, models.PullMerged, filters...) 837 + } 838 + 839 + func AbandonPulls(e Execer, filters ...orm.Filter) error { 840 + return SetPullsState(e, models.PullAbandoned, filters...) 841 + } 842 + 843 + func ResubmitPull( 844 + e Execer, 845 + pullAt syntax.ATURI, 846 + newRoundNumber int, 847 + newPatch string, 848 + combinedPatch string, 849 + newSourceRev string, 850 + blob *lexutil.LexBlob, 851 + ) error { 852 + _, err := e.Exec(` 853 + insert into pull_submissions ( 854 + pull_at, 855 + round_number, 856 + patch, 857 + combined, 858 + source_rev, 859 + patch_blob_ref, 860 + patch_blob_mime, 861 + patch_blob_size 862 + ) 863 + values (?, ?, ?, ?, ?, ?, ?, ?) 864 + `, pullAt, newRoundNumber, newPatch, combinedPatch, newSourceRev, blob.Ref.String(), blob.MimeType, blob.Size) 674 865 675 866 return err 676 867 } 677 868 678 - // Only used when stacking to update contents in the event of a rebase (the interdiff should be empty). 679 - // otherwise submissions are immutable 680 - func UpdatePull(e Execer, newPatch, sourceRev string, filters ...orm.Filter) error { 869 + func SetDependentOn(e Execer, dependentOn syntax.ATURI, filters ...orm.Filter) error { 681 870 var conditions []string 682 871 var args []any 683 872 684 - args = append(args, sourceRev) 685 - args = append(args, newPatch) 873 + args = append(args, dependentOn) 686 874 687 875 for _, filter := range filters { 688 876 conditions = append(conditions, filter.Condition()) ··· 694 882 whereClause = " where " + strings.Join(conditions, " and ") 695 883 } 696 884 697 - query := fmt.Sprintf("update pull_submissions set source_rev = ?, patch = ? %s", whereClause) 885 + query := fmt.Sprintf("update pulls set dependent_on = ? %s", whereClause) 698 886 _, err := e.Exec(query, args...) 699 887 700 888 return err ··· 712 900 models.PullOpen, 713 901 models.PullMerged, 714 902 models.PullClosed, 715 - models.PullDeleted, 903 + models.PullAbandoned, 716 904 repoAt, 717 905 ) 718 906 ··· 724 912 return count, nil 725 913 } 726 914 727 - // change-id parent-change-id 915 + // change-id dependent_on 728 916 // 729 - // 4 w ,-------- z (TOP) 730 - // 3 z <----',------- y 731 - // 2 y <-----',------ x 917 + // 4 w ,-------- at_uri(z) (TOP) 918 + // 3 z <----',------- at_uri(y) 919 + // 2 y <-----',------ at_uri(x) 732 920 // 1 x <------' nil (BOT) 733 921 // 734 - // `w` is parent of none, so it is the top of the stack 735 - func GetStack(e Execer, stackId string) (models.Stack, error) { 736 - unorderedPulls, err := GetPulls( 737 - e, 738 - orm.FilterEq("stack_id", stackId), 739 - orm.FilterNotEq("state", models.PullDeleted), 740 - ) 922 + // `w` has no dependents, so it is the top of the stack 923 + // 924 + // this unfortunately does a db query for *each* pull of the stack, 925 + // ideally this would be a recursive query, but in the interest of implementation simplicity, 926 + // we took the less performant route 927 + // 928 + // TODO: make this less bad 929 + func GetStack(e Execer, atUri syntax.ATURI) (models.Stack, error) { 930 + // first get the pull for the given at-uri 931 + pull, err := GetPull(e, orm.FilterEq("at_uri", atUri)) 741 932 if err != nil { 742 933 return nil, err 743 934 } 744 - // map of parent-change-id to pull 745 - changeIdMap := make(map[string]*models.Pull, len(unorderedPulls)) 746 - parentMap := make(map[string]*models.Pull, len(unorderedPulls)) 747 - for _, p := range unorderedPulls { 748 - changeIdMap[p.ChangeId] = p 749 - if p.ParentChangeId != "" { 750 - parentMap[p.ParentChangeId] = p 935 + 936 + // Collect all pulls in the stack by traversing up and down 937 + allPulls := []*models.Pull{pull} 938 + visited := sets.New[syntax.ATURI]() 939 + 940 + // Traverse up to find all dependents 941 + current := pull 942 + for { 943 + dependent, err := GetPull(e, 944 + orm.FilterEq("dependent_on", current.AtUri()), 945 + orm.FilterNotEq("state", models.PullAbandoned), 946 + ) 947 + if err != nil || dependent == nil { 948 + break 949 + } 950 + if visited.Contains(dependent.AtUri()) { 951 + return allPulls, fmt.Errorf("circular dependency detected in stack") 952 + } 953 + allPulls = append(allPulls, dependent) 954 + visited.Insert(dependent.AtUri()) 955 + current = dependent 956 + } 957 + 958 + // Traverse down to find all dependencies 959 + current = pull 960 + for current.DependentOn != nil { 961 + dependency, err := GetPull( 962 + e, 963 + orm.FilterEq("at_uri", current.DependentOn), 964 + orm.FilterNotEq("state", models.PullAbandoned), 965 + ) 966 + 967 + if err != nil { 968 + return allPulls, fmt.Errorf("failed to find parent pull request, stack is malformed, missing PR: %s", current.DependentOn) 751 969 } 970 + if visited.Contains(dependency.AtUri()) { 971 + return allPulls, fmt.Errorf("circular dependency detected in stack") 972 + } 973 + allPulls = append(allPulls, dependency) 974 + visited.Insert(dependency.AtUri()) 975 + current = dependency 752 976 } 753 977 754 - // the top of the stack is the pull that is not a parent of any pull 978 + // sort the list: find the top and build ordered list 979 + atUriMap := make(map[syntax.ATURI]*models.Pull, len(allPulls)) 980 + dependentMap := make(map[syntax.ATURI]*models.Pull, len(allPulls)) 981 + 982 + for _, p := range allPulls { 983 + atUriMap[p.AtUri()] = p 984 + if p.DependentOn != nil { 985 + dependentMap[*p.DependentOn] = p 986 + } 987 + } 988 + 989 + // the top of the stack is the pull that no other pull depends on 755 990 var topPull *models.Pull 756 - for _, maybeTop := range unorderedPulls { 757 - if _, ok := parentMap[maybeTop.ChangeId]; !ok { 991 + for _, maybeTop := range allPulls { 992 + if _, ok := dependentMap[maybeTop.AtUri()]; !ok { 758 993 topPull = maybeTop 759 994 break 760 995 } ··· 763 998 pulls := []*models.Pull{} 764 999 for { 765 1000 pulls = append(pulls, topPull) 766 - if topPull.ParentChangeId != "" { 767 - if next, ok := changeIdMap[topPull.ParentChangeId]; ok { 1001 + if topPull.DependentOn != nil { 1002 + if next, ok := atUriMap[*topPull.DependentOn]; ok { 768 1003 topPull = next 769 1004 } else { 770 - return nil, fmt.Errorf("failed to find parent pull request, stack is malformed") 1005 + return pulls, fmt.Errorf("failed to find parent pull request, stack is malformed") 771 1006 } 772 1007 } else { 773 1008 break ··· 777 1012 return pulls, nil 778 1013 } 779 1014 780 - func GetAbandonedPulls(e Execer, stackId string) ([]*models.Pull, error) { 781 - pulls, err := GetPulls( 782 - e, 783 - orm.FilterEq("stack_id", stackId), 784 - orm.FilterEq("state", models.PullDeleted), 785 - ) 1015 + func GetAbandonedPulls(e Execer, atUri syntax.ATURI) ([]*models.Pull, error) { 1016 + stack, err := GetStack(e, atUri) 786 1017 if err != nil { 787 1018 return nil, err 788 1019 } 789 1020 790 - return pulls, nil 1021 + var abandoned []*models.Pull 1022 + for _, p := range stack { 1023 + if p.State == models.PullAbandoned { 1024 + abandoned = append(abandoned, p) 1025 + } 1026 + } 1027 + 1028 + return abandoned, nil 791 1029 }
+1 -1
appview/db/repos.go
··· 272 272 models.PullOpen, 273 273 models.PullMerged, 274 274 models.PullClosed, 275 - models.PullDeleted, 275 + models.PullAbandoned, 276 276 }, args...) 277 277 278 278 rows, err = e.Query(pullCountQuery, pullArgs...)