forked from tangled.org/core
this repo has no description

appview: pulls: display abandoned pulls

Changed files
+144 -88
appview
-7
appview/db/db.go
···
db.Exec("pragma foreign_keys = off;")
runMigration(db, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error {
_, err := tx.Exec(`
-
-- disable fk to not delete submissions table
-
pragma foreign_keys = off;
-
create table pulls_new (
-- identifiers
id integer primary key autoincrement,
···
drop table pulls;
alter table pulls_new rename to pulls;
-
-
-- reenable fk
-
pragma foreign_keys = on;
`)
return err
})
db.Exec("pragma foreign_keys = on;")
-
>>>>>>> Conflict 1 of 1 ends
return &DB{db}, nil
}
+37 -2
appview/db/pulls.go
···
func (p PullState) IsClosed() bool {
return p == PullClosed
}
-
func (p PullState) IsDelete() bool {
+
func (p PullState) IsDeleted() bool {
return p == PullDeleted
}
···
func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState PullState) error {
_, err := e.Exec(
-
`update pulls set state = ? where repo_at = ? and pull_id = ? and state <> ?`,
+
`update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,
pullState,
repoAt,
pullId,
PullDeleted, // only update state of non-deleted pulls
+
PullMerged, // only update state of non-merged pulls
)
return err
}
···
return pulls, nil
+
func GetAbandonedPulls(e Execer, stackId string) ([]*Pull, error) {
+
pulls, err := GetPulls(
+
e,
+
FilterEq("stack_id", stackId),
+
FilterEq("state", PullDeleted),
+
)
+
if err != nil {
+
return nil, err
+
}
+
+
return pulls, nil
+
}
+
// position of this pull in the stack
func (stack Stack) Position(pull *Pull) int {
return slices.IndexFunc(stack, func(p *Pull) bool {
···
return combined.String()
+
+
// filter out PRs that are "active"
+
//
+
// PRs that are still open are active
+
func (stack Stack) Mergeable() Stack {
+
var mergeable Stack
+
+
for _, p := range stack {
+
// stop at the first merged PR
+
if p.State == PullMerged || p.State == PullClosed {
+
break
+
}
+
+
// skip over deleted PRs
+
if p.State != PullDeleted {
+
mergeable = append(mergeable, p)
+
}
+
}
+
+
return mergeable
+
}
+10 -8
appview/pages/pages.go
···
}
type RepoSinglePullParams struct {
-
LoggedInUser *oauth.User
-
RepoInfo repoinfo.RepoInfo
-
Active string
-
DidHandleMap map[string]string
-
Pull *db.Pull
-
Stack db.Stack
-
MergeCheck types.MergeCheckResponse
-
ResubmitCheck ResubmitResult
+
LoggedInUser *oauth.User
+
RepoInfo repoinfo.RepoInfo
+
Active string
+
DidHandleMap map[string]string
+
Pull *db.Pull
+
Stack db.Stack
+
AbandonedPulls []*db.Pull
+
MergeCheck types.MergeCheckResponse
+
ResubmitCheck ResubmitResult
}
func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
···
RoundNumber int
MergeCheck types.MergeCheckResponse
ResubmitCheck ResubmitResult
+
Stack db.Stack
}
func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error {
+12 -1
appview/pages/templates/repo/pulls/fragments/pullActions.html
···
{{ define "repo/pulls/fragments/pullActions" }}
{{ $lastIdx := sub (len .Pull.Submissions) 1 }}
{{ $roundNumber := .RoundNumber }}
+
{{ $stack := .Stack }}
+
+
{{ $totalPulls := sub 0 1 }}
+
{{ $below := sub 0 1 }}
+
{{ $stackCount := "" }}
+
{{ if .Pull.IsStacked }}
+
{{ $totalPulls = len $stack }}
+
{{ $below = $stack.Below .Pull }}
+
{{ $mergeable := len $below.Mergeable }}
+
{{ $stackCount = printf "%d/%d" $mergeable $totalPulls }}
+
{{ end }}
{{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }}
{{ $isMerged := .Pull.State.IsMerged }}
···
hx-confirm="Are you sure you want to merge pull #{{ .Pull.PullId }} into the `{{ .Pull.TargetBranch }}` branch?"
class="btn p-2 flex items-center gap-2 group" {{ $disabled }}>
{{ i "git-merge" "w-4 h-4" }}
-
<span>merge</span>
+
<span>merge{{if $stackCount}} {{$stackCount}}{{end}}</span>
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
</button>
{{ end }}
+4 -1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
···
<header class="flex items-center gap-2 pb-2">
{{ block "pullState" .Pull }} {{ end }}
<h1 class="text-2xl dark:text-white">
-
<span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span>
{{ .Pull.Title }}
+
<span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span>
</h1>
</header>
···
{{ else if .State.IsMerged }}
{{ $bgColor = "bg-purple-600 dark:bg-purple-700" }}
{{ $icon = "git-merge" }}
+
{{ else if .State.IsDeleted }}
+
{{ $bgColor = "bg-red-600 dark:bg-red-700" }}
+
{{ $icon = "git-pull-request-closed" }}
{{ end }}
<div id="state" class="inline-flex items-center rounded px-3 py-1 {{ $bgColor }}" >
+40 -21
appview/pages/templates/repo/pulls/fragments/pullStack.html
···
{{ define "repo/pulls/fragments/pullStack" }}
-
<div class="grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700">
-
{{ range $pull := .Stack }}
-
{{ $isCurrent := false }}
-
{{ with $.Pull }}
-
{{ $isCurrent = eq $pull.PullId $.Pull.PullId }}
-
{{ end }}
-
<a href="/{{ $.RepoInfo.FullName }}/pulls/{{ $pull.PullId }}" class="no-underline hover:no-underline hover:bg-gray-100 hover:dark:bg-gray-700">
-
<div class="flex gap-2 items-center px-2 {{ if $isCurrent }}bg-gray-100 dark:bg-gray-700{{ end }}">
-
{{ if $isCurrent }}
-
{{ i "arrow-right" "w-4 h-4" }}
-
{{ end }}
-
<div class="{{ if not $isCurrent }} ml-6 {{ end }} w-full py-2">
-
{{ block "summarizedHeader" $pull }} {{ end }}
-
</div>
-
</div>
-
</a>
-
{{ end }}
-
</div>
+
<p class="text-sm font-bold p-2 dark:text-white">STACK</p>
+
{{ block "pullList" (list .Stack $) }} {{ end }}
+
+
{{ if gt (len .AbandonedPulls) 0 }}
+
<p class="text-sm font-bold p-2 dark:text-white">ABANDONED PULLS</p>
+
{{ block "pullList" (list .AbandonedPulls $) }} {{ end }}
+
{{ end }}
{{ end }}
{{ define "summarizedHeader" }}
<div class="flex text-sm items-center justify-between w-full">
-
<div class="flex items-center gap-2">
+
<div class="flex items-center gap-2 text-ellipsis">
{{ block "summarizedPullState" . }} {{ end }}
<span>
<span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span>
···
{{ $commentCount := len $lastSubmission.Comments }}
<span>
<div class="inline-flex items-center gap-2">
+
{{ i "message-square" "w-3 h-3 md:hidden" }}
{{ $commentCount }}
-
comment{{if ne $commentCount 1}}s{{end}}
+
<span class="hidden md:inline">comment{{if ne $commentCount 1}}s{{end}}</span>
</div>
</span>
<span class="mx-2 before:content-['·'] before:select-none"></span>
-
<span>round <span class="font-mono">#{{ $latestRound }}</span></span>
+
<span>
+
<span class="hidden md:inline">round</span>
+
<span class="font-mono">#{{ $latestRound }}</span>
+
</span>
</div>
</div>
{{ end }}
···
{{ else if .State.IsMerged }}
{{ $fgColor = "text-purple-600 dark:text-purple-500" }}
{{ $icon = "git-merge" }}
+
{{ else if .State.IsDeleted }}
+
{{ $fgColor = "text-red-600 dark:text-red-500" }}
+
{{ $icon = "git-pull-request-closed" }}
{{ end }}
{{ $style := printf "w-4 h-4 %s" $fgColor }}
{{ i $icon $style }}
{{ end }}
+
+
{{ define "pullList" }}
+
{{ $list := index . 0 }}
+
{{ $root := index . 1 }}
+
<div class="grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700">
+
{{ range $pull := $list }}
+
{{ $isCurrent := false }}
+
{{ with $root.Pull }}
+
{{ $isCurrent = eq $pull.PullId $root.Pull.PullId }}
+
{{ end }}
+
<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">
+
<div class="flex gap-2 items-center px-2 {{ if $isCurrent }}bg-gray-100/50 dark:bg-gray-700/50{{ end }}">
+
{{ if $isCurrent }}
+
{{ i "arrow-right" "w-4 h-4" }}
+
{{ end }}
+
<div class="{{ if not $isCurrent }} ml-6 {{ end }} w-full py-2">
+
{{ block "summarizedHeader" $pull }} {{ end }}
+
</div>
+
</div>
+
</a>
+
{{ end }}
+
</div>
+
{{ end }}
+9 -3
appview/pages/templates/repo/pulls/pull.html
···
{{ if .Pull.IsStacked }}
<div class="mt-8">
-
<p class="text-sm font-bold p-2 dark:text-white">STACK</p>
{{ template "repo/pulls/fragments/pullStack" . }}
</div>
{{ end }}
···
{{ end }}
</div>
</summary>
-
+
{{ if .IsFormatPatch }}
{{ $patches := .AsFormatPatch }}
{{ $round := .RoundNumber }}
···
{{ end }}
{{ if $.LoggedInUser }}
-
{{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck) }}
+
{{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck "Stack" $.Stack) }}
{{ else }}
<div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm px-6 py-4 w-fit dark:text-white">
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
···
{{ i "git-merge" "w-4 h-4" }}
<span class="font-medium">pull request successfully merged</span
>
+
</div>
+
</div>
+
{{ else if .Pull.State.IsDeleted }}
+
<div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative w-fit">
+
<div class="flex items-center gap-2 text-red-500 dark:text-red-300">
+
{{ i "git-pull-request-closed" "w-4 h-4" }}
+
<span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span>
</div>
</div>
{{ else if and .MergeCheck .MergeCheck.Error }}
+6
appview/state/middleware.go
···
log.Println("failed to get stack", err)
return
}
+
abandonedPulls, err := db.GetAbandonedPulls(s.db, pr.StackId)
+
if err != nil {
+
log.Println("failed to get abandoned pulls", err)
+
return
+
}
ctx = context.WithValue(ctx, "stack", stack)
+
ctx = context.WithValue(ctx, "abandonedPulls", abandonedPulls)
}
next.ServeHTTP(w, r.WithContext(ctx))
+25 -44
appview/state/pull.go
···
RoundNumber: roundNumber,
MergeCheck: mergeCheckResponse,
ResubmitCheck: resubmitResult,
+
Stack: stack,
})
return
}
···
// can be nil if this pull is not stacked
stack, _ := r.Context().Value("stack").(db.Stack)
+
abandonedPulls, _ := r.Context().Value("abandonedPulls").([]*db.Pull)
totalIdents := 1
for _, submission := range pull.Submissions {
···
}
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
-
LoggedInUser: user,
-
RepoInfo: f.RepoInfo(s, user),
-
DidHandleMap: didHandleMap,
-
Pull: pull,
-
Stack: stack,
-
MergeCheck: mergeCheckResponse,
-
ResubmitCheck: resubmitResult,
+
LoggedInUser: user,
+
RepoInfo: f.RepoInfo(s, user),
+
DidHandleMap: didHandleMap,
+
Pull: pull,
+
Stack: stack,
+
AbandonedPulls: abandonedPulls,
+
MergeCheck: mergeCheckResponse,
+
ResubmitCheck: resubmitResult,
})
}
···
if pull.IsStacked() {
// combine patches of substack
subStack := stack.Below(pull)
-
// collect the portion of the stack that is mergeable
-
var mergeable db.Stack
-
for _, p := range subStack {
-
// stop at the first merged PR
-
if p.State == db.PullMerged || p.State == db.PullClosed {
-
break
-
}
-
-
// skip over deleted PRs
-
if p.State != db.PullDeleted {
-
mergeable = append(mergeable, p)
-
}
-
}
-
+
mergeable := subStack.Mergeable()
+
// combine each patch
patch = mergeable.CombinedPatch()
}
···
}
func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult {
-
if pull.State == db.PullMerged || pull.PullSource == nil {
+
if pull.State == db.PullMerged || pull.State == db.PullDeleted || pull.PullSource == nil {
return pages.Unknown
}
···
return
}
+
client, err := s.oauth.AuthorizedClient(r)
+
if err != nil {
+
log.Println("failed to get authorized client", err)
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
+
return
+
}
+
tx, err := s.db.BeginTx(r.Context(), nil)
if err != nil {
log.Println("failed to start tx")
···
})
if err != nil {
log.Println("failed to create pull request", err)
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
-
return
-
}
-
client, err := s.oauth.AuthorizedClient(r)
-
if err != nil {
-
log.Println("failed to get authorized client", err)
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
return
}
···
// combine patches of substack
subStack := stack.Below(pull)
-
// collect the portion of the stack that is mergeable
-
for _, p := range subStack {
-
// stop at the first merged/closed PR
-
if p.State == db.PullMerged || p.State == db.PullClosed {
-
break
-
}
-
-
// skip over deleted PRs
-
if p.State == db.PullDeleted {
-
continue
-
}
-
-
pullsToMerge = append(pullsToMerge, p)
-
}
+
mergeable := subStack.Mergeable()
+
// add to total patch
+
pullsToMerge = append(pullsToMerge, mergeable...)
patch := pullsToMerge.CombinedPatch()
···
var pullsToReopen []*db.Pull
pullsToReopen = append(pullsToReopen, pull)
-
// if this PR is stacked, then we want to reopen all PRs below this one on the stack
+
// if this PR is stacked, then we want to reopen all PRs above this one on the stack
if pull.IsStacked() {
stack := r.Context().Value("stack").(db.Stack)
-
subStack := stack.StrictlyBelow(pull)
+
subStack := stack.StrictlyAbove(pull)
pullsToReopen = append(pullsToReopen, subStack...)
+1 -1
flake.nix
···
g = config.services.tangled-knotserver.gitUser;
in [
"d /var/lib/knotserver 0770 ${u} ${g} - -" # Create the directory first
-
"f+ /var/lib/knotserver/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=679f15000084699abc6a20d3ef449efa3656583f38e456a08f0638250688ff2e"
+
"f+ /var/lib/knotserver/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=38a7c3237c2a585807e06a5bcfac92eb39442063f3da306b7acb15cfdc51d19d"
];
services.tangled-knotserver = {
enable = true;