Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2

appview: pulls: display abandoned pulls

+144 -88
-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, ··· 443 446 444 447 drop table pulls; 445 448 alter table pulls_new rename to pulls; 446 - 447 - -- reenable fk 448 - pragma foreign_keys = on; 449 449 `) 450 450 return err 451 451 }) 452 452 db.Exec("pragma foreign_keys = on;") 453 453 454 - >>>>>>> Conflict 1 of 1 ends 455 454 return &DB{db}, nil 456 455 } 457 456
+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 } ··· 1033 1032 return pulls, nil 1034 1033 } 1035 1034 1035 + 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, err 1043 + } 1044 + 1045 + return pulls, nil 1046 + } 1047 + 1036 1048 // position of this pull in the stack 1037 1049 func (stack Stack) Position(pull *Pull) int { 1038 1050 return slices.IndexFunc(stack, func(p *Pull) bool { ··· 1109 1095 combined.WriteString("\n") 1110 1096 } 1111 1097 return combined.String() 1098 + } 1099 + 1100 + // filter out PRs that are "active" 1101 + // 1102 + // PRs that are still open are active 1103 + func (stack Stack) Mergeable() Stack { 1104 + var mergeable Stack 1105 + 1106 + for _, p := range stack { 1107 + // stop at the first merged PR 1108 + if p.State == PullMerged || p.State == PullClosed { 1109 + break 1110 + } 1111 + 1112 + // skip over deleted PRs 1113 + if p.State != PullDeleted { 1114 + mergeable = append(mergeable, p) 1115 + } 1116 + } 1117 + 1118 + return mergeable 1112 1119 }
+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 { ··· 838 837 RoundNumber int 839 838 MergeCheck types.MergeCheckResponse 840 839 ResubmitCheck ResubmitResult 840 + Stack db.Stack 841 841 } 842 842 843 843 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 }} ··· 44 33 hx-confirm="Are you sure you want to merge pull #{{ .Pull.PullId }} into the `{{ .Pull.TargetBranch }}` branch?" 45 34 class="btn p-2 flex items-center gap-2 group" {{ $disabled }}> 46 35 {{ i "git-merge" "w-4 h-4" }} 47 - <span>merge</span> 36 + <span>merge{{if $stackCount}} {{$stackCount}}{{end}}</span> 48 37 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 49 38 </button> 50 39 {{ 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> ··· 24 35 {{ $commentCount := len $lastSubmission.Comments }} 25 36 <span> 26 37 <div class="inline-flex items-center gap-2"> 38 + {{ i "message-square" "w-3 h-3 md:hidden" }} 27 39 {{ $commentCount }} 28 - comment{{if ne $commentCount 1}}s{{end}} 40 + <span class="hidden md:inline">comment{{if ne $commentCount 1}}s{{end}}</span> 29 41 </div> 30 42 </span> 31 43 <span class="mx-2 before:content-['·'] before:select-none"></span> 32 - <span>round <span class="font-mono">#{{ $latestRound }}</span></span> 44 + <span> 45 + <span class="hidden md:inline">round</span> 46 + <span class="font-mono">#{{ $latestRound }}</span> 47 + </span> 33 48 </div> 34 49 </div> 35 50 {{ end }} ··· 48 55 {{ else if .State.IsMerged }} 49 56 {{ $fgColor = "text-purple-600 dark:text-purple-500" }} 50 57 {{ $icon = "git-merge" }} 58 + {{ else if .State.IsDeleted }} 59 + {{ $fgColor = "text-red-600 dark:text-red-500" }} 60 + {{ $icon = "git-pull-request-closed" }} 51 61 {{ end }} 52 62 53 63 {{ $style := printf "w-4 h-4 %s" $fgColor }} 54 64 55 65 {{ i $icon $style }} 66 + {{ end }} 67 + 68 + {{ define "pullList" }} 69 + {{ $list := index . 0 }} 70 + {{ $root := index . 1 }} 71 + <div class="grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700"> 72 + {{ range $pull := $list }} 73 + {{ $isCurrent := false }} 74 + {{ with $root.Pull }} 75 + {{ $isCurrent = eq $pull.PullId $root.Pull.PullId }} 76 + {{ end }} 77 + <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"> 78 + <div class="flex gap-2 items-center px-2 {{ if $isCurrent }}bg-gray-100/50 dark:bg-gray-700/50{{ end }}"> 79 + {{ if $isCurrent }} 80 + {{ i "arrow-right" "w-4 h-4" }} 81 + {{ end }} 82 + <div class="{{ if not $isCurrent }} ml-6 {{ end }} w-full py-2"> 83 + {{ block "summarizedHeader" $pull }} {{ end }} 84 + </div> 85 + </div> 86 + </a> 87 + {{ end }} 88 + </div> 56 89 {{ 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 }} ··· 84 85 {{ end }} 85 86 </div> 86 87 </summary> 87 - 88 + 88 89 {{ if .IsFormatPatch }} 89 90 {{ $patches := .AsFormatPatch }} 90 91 {{ $round := .RoundNumber }} ··· 168 169 {{ end }} 169 170 170 171 {{ if $.LoggedInUser }} 171 - {{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck) }} 172 + {{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck "Stack" $.Stack) }} 172 173 {{ else }} 173 174 <div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm px-6 py-4 w-fit dark:text-white"> 174 175 <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> ··· 197 198 {{ i "git-merge" "w-4 h-4" }} 198 199 <span class="font-medium">pull request successfully merged</span 199 200 > 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> 200 208 </div> 201 209 </div> 202 210 {{ 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 } ··· 98 97 99 98 // can be nil if this pull is not stacked 100 99 stack, _ := r.Context().Value("stack").(db.Stack) 100 + abandonedPulls, _ := r.Context().Value("abandonedPulls").([]*db.Pull) 101 101 102 102 totalIdents := 1 103 103 for _, submission := range pull.Submissions { ··· 134 132 } 135 133 136 134 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, 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, 144 143 }) 145 144 } 146 145 ··· 170 167 if pull.IsStacked() { 171 168 // combine patches of substack 172 169 subStack := stack.Below(pull) 173 - 174 170 // collect the portion of the stack that is mergeable 175 - var mergeable db.Stack 176 - for _, p := range subStack { 177 - // stop at the first merged PR 178 - if p.State == db.PullMerged || p.State == db.PullClosed { 179 - break 180 - } 181 - 182 - // skip over deleted PRs 183 - if p.State != db.PullDeleted { 184 - mergeable = append(mergeable, p) 185 - } 186 - } 187 - 171 + mergeable := subStack.Mergeable() 172 + // combine each patch 188 173 patch = mergeable.CombinedPatch() 189 174 } 190 175 ··· 216 225 } 217 226 218 227 func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult { 219 - if pull.State == db.PullMerged || pull.PullSource == nil { 228 + if pull.State == db.PullMerged || pull.State == db.PullDeleted || pull.PullSource == nil { 220 229 return pages.Unknown 221 230 } 222 231 ··· 894 903 return 895 904 } 896 905 906 + 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 + return 911 + } 912 + 897 913 tx, err := s.db.BeginTx(r.Context(), nil) 898 914 if err != nil { 899 915 log.Println("failed to start tx") ··· 945 947 }) 946 948 if err != nil { 947 949 log.Println("failed to create pull request", err) 948 - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 949 - return 950 - } 951 - client, err := s.oauth.AuthorizedClient(r) 952 - if err != nil { 953 - log.Println("failed to get authorized client", err) 954 950 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 955 951 return 956 952 } ··· 1812 1820 1813 1821 // combine patches of substack 1814 1822 subStack := stack.Below(pull) 1815 - 1816 1823 // collect the portion of the stack that is mergeable 1817 - for _, p := range subStack { 1818 - // stop at the first merged/closed PR 1819 - if p.State == db.PullMerged || p.State == db.PullClosed { 1820 - break 1821 - } 1822 - 1823 - // skip over deleted PRs 1824 - if p.State == db.PullDeleted { 1825 - continue 1826 - } 1827 - 1828 - pullsToMerge = append(pullsToMerge, p) 1829 - } 1824 + mergeable := subStack.Mergeable() 1825 + // add to total patch 1826 + pullsToMerge = append(pullsToMerge, mergeable...) 1830 1827 } 1831 1828 1832 1829 patch := pullsToMerge.CombinedPatch() ··· 1995 2014 var pullsToReopen []*db.Pull 1996 2015 pullsToReopen = append(pullsToReopen, pull) 1997 2016 1998 - // if this PR is stacked, then we want to reopen all PRs below this one on the stack 2017 + // if this PR is stacked, then we want to reopen all PRs above this one on the stack 1999 2018 if pull.IsStacked() { 2000 2019 stack := r.Context().Value("stack").(db.Stack) 2001 - subStack := stack.StrictlyBelow(pull) 2020 + subStack := stack.StrictlyAbove(pull) 2002 2021 pullsToReopen = append(pullsToReopen, subStack...) 2003 2022 } 2004 2023
+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;