···49func (p PullState) IsClosed() bool {50 return p == PullClosed51}52-func (p PullState) IsDelete() bool {53 return p == PullDeleted54}55···885886func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState PullState) error {887 _, err := e.Exec(888- `update pulls set state = ? where repo_at = ? and pull_id = ? and state <> ?`,889 pullState,890 repoAt,891 pullId,892 PullDeleted, // only update state of non-deleted pulls0893 )894 return err895}···1033 return pulls, nil1034}103500000000000001036// position of this pull in the stack1037func (stack Stack) Position(pull *Pull) int {1038 return slices.IndexFunc(stack, func(p *Pull) bool {···1109 combined.WriteString("\n")1110 }1111 return combined.String()0000000000000000000001112}
···49func (p PullState) IsClosed() bool {50 return p == PullClosed51}52+func (p PullState) IsDeleted() bool {53 return p == PullDeleted54}55···885886func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState PullState) error {887 _, err := e.Exec(888+ `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,889 pullState,890 repoAt,891 pullId,892 PullDeleted, // only update state of non-deleted pulls893+ PullMerged, // only update state of non-merged pulls894 )895 return err896}···1032 return pulls, nil1033}10341035+func GetAbandonedPulls(e Execer, stackId string) ([]*Pull, error) {1036+ pulls, err := GetPulls(1037+ e,1038+ FilterEq("stack_id", stackId),1039+ FilterEq("state", PullDeleted),1040+ )1041+ if err != nil {1042+ return nil, err1043+ }1044+1045+ return pulls, nil1046+}1047+1048// position of this pull in the stack1049func (stack Stack) Position(pull *Pull) int {1050 return slices.IndexFunc(stack, func(p *Pull) bool {···1095 combined.WriteString("\n")1096 }1097 return combined.String()1098+}1099+1100+// filter out PRs that are "active"1101+//1102+// PRs that are still open are active1103+func (stack Stack) Mergeable() Stack {1104+ var mergeable Stack1105+1106+ for _, p := range stack {1107+ // stop at the first merged PR1108+ if p.State == PullMerged || p.State == PullClosed {1109+ break1110+ }1111+1112+ // skip over deleted PRs1113+ if p.State != PullDeleted {1114+ mergeable = append(mergeable, p)1115+ }1116+ }1117+1118+ return mergeable1119}
···1516 {{ if .Pull.IsStacked }}17 <div class="mt-8">018 {{ template "repo/pulls/fragments/pullStack" . }}19 </div>20 {{ end }}···85 {{ end }}86 </div>87 </summary>88+89 {{ if .IsFormatPatch }}90 {{ $patches := .AsFormatPatch }}91 {{ $round := .RoundNumber }}···169 {{ end }}170171 {{ if $.LoggedInUser }}172+ {{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck "Stack" $.Stack) }}173 {{ else }}174 <div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm px-6 py-4 w-fit dark:text-white">175 <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>···198 {{ i "git-merge" "w-4 h-4" }}199 <span class="font-medium">pull request successfully merged</span200 >201+ </div>202+ </div>203+ {{ else if .Pull.State.IsDeleted }}204+ <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative w-fit">205+ <div class="flex items-center gap-2 text-red-500 dark:text-red-300">206+ {{ i "git-pull-request-closed" "w-4 h-4" }}207+ <span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span>208 </div>209 </div>210 {{ else if and .MergeCheck .MergeCheck.Error }}
+6
appview/state/middleware.go
···178 log.Println("failed to get stack", err)179 return180 }00000181182 ctx = context.WithValue(ctx, "stack", stack)0183 }184185 next.ServeHTTP(w, r.WithContext(ctx))
···178 log.Println("failed to get stack", err)179 return180 }181+ abandonedPulls, err := db.GetAbandonedPulls(s.db, pr.StackId)182+ if err != nil {183+ log.Println("failed to get abandoned pulls", err)184+ return185+ }186187 ctx = context.WithValue(ctx, "stack", stack)188+ ctx = context.WithValue(ctx, "abandonedPulls", abandonedPulls)189 }190191 next.ServeHTTP(w, r.WithContext(ctx))
+25-44
appview/state/pull.go
···75 RoundNumber: roundNumber,76 MergeCheck: mergeCheckResponse,77 ResubmitCheck: resubmitResult,078 })79 return80 }···9899 // can be nil if this pull is not stacked100 stack, _ := r.Context().Value("stack").(db.Stack)0101102 totalIdents := 1103 for _, submission := range pull.Submissions {···134 }135136 s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{137- LoggedInUser: user,138- RepoInfo: f.RepoInfo(s, user),139- DidHandleMap: didHandleMap,140- Pull: pull,141- Stack: stack,142- MergeCheck: mergeCheckResponse,143- ResubmitCheck: resubmitResult,0144 })145}146···170 if pull.IsStacked() {171 // combine patches of substack172 subStack := stack.Below(pull)173-174 // collect the portion of the stack that is mergeable175- var mergeable db.Stack176- for _, p := range subStack {177- // stop at the first merged PR178- if p.State == db.PullMerged || p.State == db.PullClosed {179- break180- }181-182- // skip over deleted PRs183- if p.State != db.PullDeleted {184- mergeable = append(mergeable, p)185- }186- }187-188 patch = mergeable.CombinedPatch()189 }190···216}217218func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult {219- if pull.State == db.PullMerged || pull.PullSource == nil {220 return pages.Unknown221 }222···894 return895 }8960000000897 tx, err := s.db.BeginTx(r.Context(), nil)898 if err != nil {899 log.Println("failed to start tx")···945 })946 if err != nil {947 log.Println("failed to create pull request", err)948- s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")949- return950- }951- client, err := s.oauth.AuthorizedClient(r)952- if err != nil {953- log.Println("failed to get authorized client", err)954 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")955 return956 }···18121813 // combine patches of substack1814 subStack := stack.Below(pull)1815-1816 // collect the portion of the stack that is mergeable1817- for _, p := range subStack {1818- // stop at the first merged/closed PR1819- if p.State == db.PullMerged || p.State == db.PullClosed {1820- break1821- }1822-1823- // skip over deleted PRs1824- if p.State == db.PullDeleted {1825- continue1826- }1827-1828- pullsToMerge = append(pullsToMerge, p)1829- }1830 }18311832 patch := pullsToMerge.CombinedPatch()···1995 var pullsToReopen []*db.Pull1996 pullsToReopen = append(pullsToReopen, pull)19971998- // if this PR is stacked, then we want to reopen all PRs below this one on the stack1999 if pull.IsStacked() {2000 stack := r.Context().Value("stack").(db.Stack)2001- subStack := stack.StrictlyBelow(pull)2002 pullsToReopen = append(pullsToReopen, subStack...)2003 }2004
···75 RoundNumber: roundNumber,76 MergeCheck: mergeCheckResponse,77 ResubmitCheck: resubmitResult,78+ Stack: stack,79 })80 return81 }···9798 // can be nil if this pull is not stacked99 stack, _ := r.Context().Value("stack").(db.Stack)100+ abandonedPulls, _ := r.Context().Value("abandonedPulls").([]*db.Pull)101102 totalIdents := 1103 for _, submission := range pull.Submissions {···132 }133134 s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{135+ LoggedInUser: user,136+ RepoInfo: f.RepoInfo(s, user),137+ DidHandleMap: didHandleMap,138+ Pull: pull,139+ Stack: stack,140+ AbandonedPulls: abandonedPulls,141+ MergeCheck: mergeCheckResponse,142+ ResubmitCheck: resubmitResult,143 })144}145···167 if pull.IsStacked() {168 // combine patches of substack169 subStack := stack.Below(pull)0170 // collect the portion of the stack that is mergeable171+ mergeable := subStack.Mergeable()172+ // combine each patch00000000000173 patch = mergeable.CombinedPatch()174 }175···225}226227func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult {228+ if pull.State == db.PullMerged || pull.State == db.PullDeleted || pull.PullSource == nil {229 return pages.Unknown230 }231···903 return904 }905906+ client, err := s.oauth.AuthorizedClient(r)907+ if err != nil {908+ log.Println("failed to get authorized client", err)909+ s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")910+ return911+ }912+913 tx, err := s.db.BeginTx(r.Context(), nil)914 if err != nil {915 log.Println("failed to start tx")···947 })948 if err != nil {949 log.Println("failed to create pull request", err)000000950 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")951 return952 }···18201821 // combine patches of substack1822 subStack := stack.Below(pull)01823 // collect the portion of the stack that is mergeable1824+ mergeable := subStack.Mergeable()1825+ // add to total patch1826+ pullsToMerge = append(pullsToMerge, mergeable...)00000000001827 }18281829 patch := pullsToMerge.CombinedPatch()···2014 var pullsToReopen []*db.Pull2015 pullsToReopen = append(pullsToReopen, pull)20162017+ // if this PR is stacked, then we want to reopen all PRs above this one on the stack2018 if pull.IsStacked() {2019 stack := r.Context().Value("stack").(db.Stack)2020+ subStack := stack.StrictlyAbove(pull)2021 pullsToReopen = append(pullsToReopen, subStack...)2022 }2023