forked from tangled.org/core
Monorepo for Tangled

appview: pulls: display abandoned pulls

Changed files
+144 -88
appview
-7
appview/db/db.go
··· 393 393 db.Exec("pragma foreign_keys = off;") 394 394 runMigration(db, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error { 395 395 _, err := tx.Exec(` 396 - -- disable fk to not delete submissions table 397 - pragma foreign_keys = off; 398 - 399 396 create table pulls_new ( 400 397 -- identifiers 401 398 id integer primary key autoincrement, ··· 446 443 447 444 drop table pulls; 448 445 alter table pulls_new rename to pulls; 449 - 450 - -- reenable fk 451 - pragma foreign_keys = on; 452 446 `) 453 447 return err 454 448 }) 455 449 db.Exec("pragma foreign_keys = on;") 456 450 457 - >>>>>>> Conflict 1 of 1 ends 458 451 return &DB{db}, nil 459 452 } 460 453
+37 -2
appview/db/pulls.go
··· 49 49 func (p PullState) IsClosed() bool { 50 50 return p == PullClosed 51 51 } 52 - func (p PullState) IsDelete() bool { 52 + func (p PullState) IsDeleted() bool { 53 53 return p == PullDeleted 54 54 } 55 55 ··· 885 885 886 886 func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState PullState) error { 887 887 _, err := e.Exec( 888 - `update pulls set state = ? where repo_at = ? and pull_id = ? and state <> ?`, 888 + `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`, 889 889 pullState, 890 890 repoAt, 891 891 pullId, 892 892 PullDeleted, // only update state of non-deleted pulls 893 + PullMerged, // only update state of non-merged pulls 893 894 ) 894 895 return err 895 896 } ··· 1032 1033 return pulls, nil 1033 1034 } 1034 1035 1036 + func GetAbandonedPulls(e Execer, stackId string) ([]*Pull, error) { 1037 + pulls, err := GetPulls( 1038 + e, 1039 + FilterEq("stack_id", stackId), 1040 + FilterEq("state", PullDeleted), 1041 + ) 1042 + if err != nil { 1043 + return nil, err 1044 + } 1045 + 1046 + return pulls, nil 1047 + } 1048 + 1035 1049 // position of this pull in the stack 1036 1050 func (stack Stack) Position(pull *Pull) int { 1037 1051 return slices.IndexFunc(stack, func(p *Pull) bool { ··· 1096 1110 } 1097 1111 return combined.String() 1098 1112 } 1113 + 1114 + // filter out PRs that are "active" 1115 + // 1116 + // PRs that are still open are active 1117 + func (stack Stack) Mergeable() Stack { 1118 + var mergeable Stack 1119 + 1120 + for _, p := range stack { 1121 + // stop at the first merged PR 1122 + if p.State == PullMerged || p.State == PullClosed { 1123 + break 1124 + } 1125 + 1126 + // skip over deleted PRs 1127 + if p.State != PullDeleted { 1128 + mergeable = append(mergeable, p) 1129 + } 1130 + } 1131 + 1132 + return mergeable 1133 + }
+10 -8
appview/pages/pages.go
··· 738 738 } 739 739 740 740 type RepoSinglePullParams struct { 741 - LoggedInUser *oauth.User 742 - RepoInfo repoinfo.RepoInfo 743 - Active string 744 - DidHandleMap map[string]string 745 - Pull *db.Pull 746 - Stack db.Stack 747 - MergeCheck types.MergeCheckResponse 748 - ResubmitCheck ResubmitResult 741 + LoggedInUser *oauth.User 742 + RepoInfo repoinfo.RepoInfo 743 + Active string 744 + DidHandleMap map[string]string 745 + Pull *db.Pull 746 + Stack db.Stack 747 + AbandonedPulls []*db.Pull 748 + MergeCheck types.MergeCheckResponse 749 + ResubmitCheck ResubmitResult 749 750 } 750 751 751 752 func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { ··· 837 838 RoundNumber int 838 839 MergeCheck types.MergeCheckResponse 839 840 ResubmitCheck ResubmitResult 841 + Stack db.Stack 840 842 } 841 843 842 844 func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error {
+12 -1
appview/pages/templates/repo/pulls/fragments/pullActions.html
··· 1 1 {{ define "repo/pulls/fragments/pullActions" }} 2 2 {{ $lastIdx := sub (len .Pull.Submissions) 1 }} 3 3 {{ $roundNumber := .RoundNumber }} 4 + {{ $stack := .Stack }} 5 + 6 + {{ $totalPulls := sub 0 1 }} 7 + {{ $below := sub 0 1 }} 8 + {{ $stackCount := "" }} 9 + {{ if .Pull.IsStacked }} 10 + {{ $totalPulls = len $stack }} 11 + {{ $below = $stack.Below .Pull }} 12 + {{ $mergeable := len $below.Mergeable }} 13 + {{ $stackCount = printf "%d/%d" $mergeable $totalPulls }} 14 + {{ end }} 4 15 5 16 {{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }} 6 17 {{ $isMerged := .Pull.State.IsMerged }} ··· 33 44 hx-confirm="Are you sure you want to merge pull #{{ .Pull.PullId }} into the `{{ .Pull.TargetBranch }}` branch?" 34 45 class="btn p-2 flex items-center gap-2 group" {{ $disabled }}> 35 46 {{ i "git-merge" "w-4 h-4" }} 36 - <span>merge</span> 47 + <span>merge{{if $stackCount}} {{$stackCount}}{{end}}</span> 37 48 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 38 49 </button> 39 50 {{ end }}
+4 -1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 2 2 <header class="flex items-center gap-2 pb-2"> 3 3 {{ block "pullState" .Pull }} {{ end }} 4 4 <h1 class="text-2xl dark:text-white"> 5 - <span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span> 6 5 {{ .Pull.Title }} 6 + <span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span> 7 7 </h1> 8 8 </header> 9 9 ··· 96 96 {{ else if .State.IsMerged }} 97 97 {{ $bgColor = "bg-purple-600 dark:bg-purple-700" }} 98 98 {{ $icon = "git-merge" }} 99 + {{ else if .State.IsDeleted }} 100 + {{ $bgColor = "bg-red-600 dark:bg-red-700" }} 101 + {{ $icon = "git-pull-request-closed" }} 99 102 {{ end }} 100 103 101 104 <div id="state" class="inline-flex items-center rounded px-3 py-1 {{ $bgColor }}" >
+40 -21
appview/pages/templates/repo/pulls/fragments/pullStack.html
··· 1 1 {{ define "repo/pulls/fragments/pullStack" }} 2 - <div class="grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700"> 3 - {{ range $pull := .Stack }} 4 - {{ $isCurrent := false }} 5 - {{ with $.Pull }} 6 - {{ $isCurrent = eq $pull.PullId $.Pull.PullId }} 7 - {{ end }} 8 - <a href="/{{ $.RepoInfo.FullName }}/pulls/{{ $pull.PullId }}" class="no-underline hover:no-underline hover:bg-gray-100 hover:dark:bg-gray-700"> 9 - <div class="flex gap-2 items-center px-2 {{ if $isCurrent }}bg-gray-100 dark:bg-gray-700{{ end }}"> 10 - {{ if $isCurrent }} 11 - {{ i "arrow-right" "w-4 h-4" }} 12 - {{ end }} 13 - <div class="{{ if not $isCurrent }} ml-6 {{ end }} w-full py-2"> 14 - {{ block "summarizedHeader" $pull }} {{ end }} 15 - </div> 16 - </div> 17 - </a> 18 - {{ end }} 19 - </div> 2 + <p class="text-sm font-bold p-2 dark:text-white">STACK</p> 3 + {{ block "pullList" (list .Stack $) }} {{ end }} 4 + 5 + {{ if gt (len .AbandonedPulls) 0 }} 6 + <p class="text-sm font-bold p-2 dark:text-white">ABANDONED PULLS</p> 7 + {{ block "pullList" (list .AbandonedPulls $) }} {{ end }} 8 + {{ end }} 20 9 {{ end }} 21 10 22 11 {{ define "summarizedHeader" }} 23 12 <div class="flex text-sm items-center justify-between w-full"> 24 - <div class="flex items-center gap-2"> 13 + <div class="flex items-center gap-2 text-ellipsis"> 25 14 {{ block "summarizedPullState" . }} {{ end }} 26 15 <span> 27 16 <span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span> ··· 35 24 {{ $commentCount := len $lastSubmission.Comments }} 36 25 <span> 37 26 <div class="inline-flex items-center gap-2"> 27 + {{ i "message-square" "w-3 h-3 md:hidden" }} 38 28 {{ $commentCount }} 39 - comment{{if ne $commentCount 1}}s{{end}} 29 + <span class="hidden md:inline">comment{{if ne $commentCount 1}}s{{end}}</span> 40 30 </div> 41 31 </span> 42 32 <span class="mx-2 before:content-['·'] before:select-none"></span> 43 - <span>round <span class="font-mono">#{{ $latestRound }}</span></span> 33 + <span> 34 + <span class="hidden md:inline">round</span> 35 + <span class="font-mono">#{{ $latestRound }}</span> 36 + </span> 44 37 </div> 45 38 </div> 46 39 {{ end }} ··· 55 48 {{ else if .State.IsMerged }} 56 49 {{ $fgColor = "text-purple-600 dark:text-purple-500" }} 57 50 {{ $icon = "git-merge" }} 51 + {{ else if .State.IsDeleted }} 52 + {{ $fgColor = "text-red-600 dark:text-red-500" }} 53 + {{ $icon = "git-pull-request-closed" }} 58 54 {{ end }} 59 55 60 56 {{ $style := printf "w-4 h-4 %s" $fgColor }} 61 57 62 58 {{ i $icon $style }} 63 59 {{ end }} 60 + 61 + {{ define "pullList" }} 62 + {{ $list := index . 0 }} 63 + {{ $root := index . 1 }} 64 + <div class="grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700"> 65 + {{ range $pull := $list }} 66 + {{ $isCurrent := false }} 67 + {{ with $root.Pull }} 68 + {{ $isCurrent = eq $pull.PullId $root.Pull.PullId }} 69 + {{ end }} 70 + <a href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $pull.PullId }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 71 + <div class="flex gap-2 items-center px-2 {{ if $isCurrent }}bg-gray-100/50 dark:bg-gray-700/50{{ end }}"> 72 + {{ if $isCurrent }} 73 + {{ i "arrow-right" "w-4 h-4" }} 74 + {{ end }} 75 + <div class="{{ if not $isCurrent }} ml-6 {{ end }} w-full py-2"> 76 + {{ block "summarizedHeader" $pull }} {{ end }} 77 + </div> 78 + </div> 79 + </a> 80 + {{ end }} 81 + </div> 82 + {{ end }}
+9 -3
appview/pages/templates/repo/pulls/pull.html
··· 15 15 16 16 {{ if .Pull.IsStacked }} 17 17 <div class="mt-8"> 18 - <p class="text-sm font-bold p-2 dark:text-white">STACK</p> 19 18 {{ template "repo/pulls/fragments/pullStack" . }} 20 19 </div> 21 20 {{ end }} ··· 85 84 {{ end }} 86 85 </div> 87 86 </summary> 88 - 87 + 89 88 {{ if .IsFormatPatch }} 90 89 {{ $patches := .AsFormatPatch }} 91 90 {{ $round := .RoundNumber }} ··· 169 168 {{ end }} 170 169 171 170 {{ if $.LoggedInUser }} 172 - {{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck) }} 171 + {{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck "Stack" $.Stack) }} 173 172 {{ else }} 174 173 <div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm px-6 py-4 w-fit dark:text-white"> 175 174 <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> ··· 198 197 {{ i "git-merge" "w-4 h-4" }} 199 198 <span class="font-medium">pull request successfully merged</span 200 199 > 200 + </div> 201 + </div> 202 + {{ else if .Pull.State.IsDeleted }} 203 + <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 204 + <div class="flex items-center gap-2 text-red-500 dark:text-red-300"> 205 + {{ i "git-pull-request-closed" "w-4 h-4" }} 206 + <span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span> 201 207 </div> 202 208 </div> 203 209 {{ else if and .MergeCheck .MergeCheck.Error }}
+6
appview/state/middleware.go
··· 178 178 log.Println("failed to get stack", err) 179 179 return 180 180 } 181 + abandonedPulls, err := db.GetAbandonedPulls(s.db, pr.StackId) 182 + if err != nil { 183 + log.Println("failed to get abandoned pulls", err) 184 + return 185 + } 181 186 182 187 ctx = context.WithValue(ctx, "stack", stack) 188 + ctx = context.WithValue(ctx, "abandonedPulls", abandonedPulls) 183 189 } 184 190 185 191 next.ServeHTTP(w, r.WithContext(ctx))
+25 -44
appview/state/pull.go
··· 75 75 RoundNumber: roundNumber, 76 76 MergeCheck: mergeCheckResponse, 77 77 ResubmitCheck: resubmitResult, 78 + Stack: stack, 78 79 }) 79 80 return 80 81 } ··· 97 98 98 99 // can be nil if this pull is not stacked 99 100 stack, _ := r.Context().Value("stack").(db.Stack) 101 + abandonedPulls, _ := r.Context().Value("abandonedPulls").([]*db.Pull) 100 102 101 103 totalIdents := 1 102 104 for _, submission := range pull.Submissions { ··· 132 134 } 133 135 134 136 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 - MergeCheck: mergeCheckResponse, 141 - ResubmitCheck: resubmitResult, 137 + LoggedInUser: user, 138 + RepoInfo: f.RepoInfo(s, user), 139 + DidHandleMap: didHandleMap, 140 + Pull: pull, 141 + Stack: stack, 142 + AbandonedPulls: abandonedPulls, 143 + MergeCheck: mergeCheckResponse, 144 + ResubmitCheck: resubmitResult, 142 145 }) 143 146 } 144 147 ··· 167 170 if pull.IsStacked() { 168 171 // combine patches of substack 169 172 subStack := stack.Below(pull) 170 - 171 173 // collect the portion of the stack that is mergeable 172 - var mergeable db.Stack 173 - for _, p := range subStack { 174 - // stop at the first merged PR 175 - if p.State == db.PullMerged || p.State == db.PullClosed { 176 - break 177 - } 178 - 179 - // skip over deleted PRs 180 - if p.State != db.PullDeleted { 181 - mergeable = append(mergeable, p) 182 - } 183 - } 184 - 174 + mergeable := subStack.Mergeable() 175 + // combine each patch 185 176 patch = mergeable.CombinedPatch() 186 177 } 187 178 ··· 225 216 } 226 217 227 218 func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult { 228 - if pull.State == db.PullMerged || pull.PullSource == nil { 219 + if pull.State == db.PullMerged || pull.State == db.PullDeleted || pull.PullSource == nil { 229 220 return pages.Unknown 230 221 } 231 222 ··· 903 894 return 904 895 } 905 896 897 + client, err := s.oauth.AuthorizedClient(r) 898 + if err != nil { 899 + log.Println("failed to get authorized client", err) 900 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 901 + return 902 + } 903 + 906 904 tx, err := s.db.BeginTx(r.Context(), nil) 907 905 if err != nil { 908 906 log.Println("failed to start tx") ··· 947 945 }) 948 946 if err != nil { 949 947 log.Println("failed to create pull request", err) 950 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 951 - return 952 - } 953 - client, err := s.oauth.AuthorizedClient(r) 954 - if err != nil { 955 - log.Println("failed to get authorized client", err) 956 948 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 957 949 return 958 950 } ··· 1820 1812 1821 1813 // combine patches of substack 1822 1814 subStack := stack.Below(pull) 1823 - 1824 1815 // collect the portion of the stack that is mergeable 1825 - for _, p := range subStack { 1826 - // stop at the first merged/closed PR 1827 - if p.State == db.PullMerged || p.State == db.PullClosed { 1828 - break 1829 - } 1830 - 1831 - // skip over deleted PRs 1832 - if p.State == db.PullDeleted { 1833 - continue 1834 - } 1835 - 1836 - pullsToMerge = append(pullsToMerge, p) 1837 - } 1816 + mergeable := subStack.Mergeable() 1817 + // add to total patch 1818 + pullsToMerge = append(pullsToMerge, mergeable...) 1838 1819 } 1839 1820 1840 1821 patch := pullsToMerge.CombinedPatch() ··· 2014 1995 var pullsToReopen []*db.Pull 2015 1996 pullsToReopen = append(pullsToReopen, pull) 2016 1997 2017 - // if this PR is stacked, then we want to reopen all PRs below this one on the stack 1998 + // if this PR is stacked, then we want to reopen all PRs above this one on the stack 2018 1999 if pull.IsStacked() { 2019 2000 stack := r.Context().Value("stack").(db.Stack) 2020 - subStack := stack.StrictlyBelow(pull) 2001 + subStack := stack.StrictlyAbove(pull) 2021 2002 pullsToReopen = append(pullsToReopen, subStack...) 2022 2003 } 2023 2004
+1 -1
flake.nix
··· 435 435 g = config.services.tangled-knotserver.gitUser; 436 436 in [ 437 437 "d /var/lib/knotserver 0770 ${u} ${g} - -" # Create the directory first 438 - "f+ /var/lib/knotserver/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=679f15000084699abc6a20d3ef449efa3656583f38e456a08f0638250688ff2e" 438 + "f+ /var/lib/knotserver/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=38a7c3237c2a585807e06a5bcfac92eb39442063f3da306b7acb15cfdc51d19d" 439 439 ]; 440 440 services.tangled-knotserver = { 441 441 enable = true;