Merge branch 'main' into gpg-auth

This commit is contained in:
d
2026-06-09 05:24:55 +00:00
9 changed files with 336 additions and 82 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.34.0 # renovate: datasource=go
XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.3.0 # renovate: datasource=go
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
+51 -18
View File
@@ -2,6 +2,36 @@
This document describes maintainer expectations, project governance, and the detailed pull request review workflow (labels, merge queue, commit message format for mergers). For what contributors should do when opening and updating a PR, see [CONTRIBUTING.md](../CONTRIBUTING.md).
## Table of contents
- [Community governance and review process](#community-governance-and-review-process)
- [Table of contents](#table-of-contents)
- [Code review](#code-review)
- [Milestone](#milestone)
- [Labels](#labels)
- [Reviewing PRs](#reviewing-prs)
- [For reviewers](#for-reviewers)
- [Getting PRs merged](#getting-prs-merged)
- [Final call](#final-call)
- [Commit messages](#commit-messages)
- [PR Co-authors](#pr-co-authors)
- [PRs targeting `main`](#prs-targeting-main)
- [Backport PRs](#backport-prs)
- [Contribution Roles](#contribution-roles)
- [Maintainers](#maintainers)
- [Review expectations](#review-expectations)
- [Becoming a maintainer](#becoming-a-maintainer)
- [Stepping down, advisors, and inactivity](#stepping-down-advisors-and-inactivity)
- [Account security](#account-security)
- [Mergers](#mergers)
- [Becoming a merger](#becoming-a-merger)
- [Technical Oversight Committee (TOC)](#technical-oversight-committee-toc)
- [TOC election process](#toc-election-process)
- [Current TOC members](#current-toc-members)
- [Previous TOC/owners members](#previous-tocowners-members)
- [Governance Compensation](#governance-compensation)
- [Roadmap](#roadmap)
## Code review
### Milestone
@@ -92,7 +122,9 @@ $PR_TITLE ($INITIAL_PR_INDEX) ($BACKPORT_PR_INDEX)
$REWRITTEN_PR_SUMMARY
```
## Maintainers
## Contribution Roles
### Maintainers
We list [maintainers](../MAINTAINERS) so every PR gets proper review.
@@ -121,12 +153,25 @@ For security, maintainers should enable 2FA and sign commits with GPG when possi
Any account with write access (including bots and TOC members) **must** use [2FA](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication).
## Technical Oversight Committee (TOC)
### Mergers
Mergers are the maintainers who carry out the final merge of approved PRs. Their responsibilities, described throughout this guide, are:
- Merging PRs from the [merge queue](#getting-prs-merged) in order, once a PR has `lgtm/done`, no open discussions, and no merge conflicts.
- Rewriting the PR title and summary so the squash [commit message](#commit-messages) is clear, removing false-positive co-authors while keeping every true co-author.
- Assigning the correct labels (including `type/…`) needed for changelog and backport decisions.
- Agreeing, together with the owners, on when a release is ready (see [release management](release-management.md)).
#### Becoming a merger
A merger should already be a Gitea maintainer. To apply, use the [Discord](https://discord.gg/Gitea) `#maintainers` channel. Mergers teams may also invite contributors.
### Technical Oversight Committee (TOC)
At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions are elected as it has been over the past years, and the other three consist of appointed members from the Gitea company.
https://blog.gitea.com/quarterly-23q1/
### TOC election process
#### TOC election process
Any maintainer is eligible to be part of the community TOC if they are not associated with the Gitea company.
A maintainer can either nominate themselves, or can be nominated by other maintainers to be a candidate for the TOC election.
@@ -142,7 +187,7 @@ If an elected member that accepts the seat does not have 2FA configured yet, the
### Current TOC members
- 2024-01-01 ~ 2024-12-31
- 2025-01-01 ~ 2026-06-14
- Company
- [Jason Song](https://gitea.com/wolfogre) <i@wolfogre.com>
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
@@ -150,7 +195,7 @@ If an elected member that accepts the seat does not have 2FA configured yet, the
- Community
- [6543](https://gitea.com/6543) <6543@obermui.de>
- [delvh](https://gitea.com/delvh) <dev.lh@web.de>
- [John Olheiser](https://gitea.com/jolheiser) <john.olheiser@gmail.com>
- [lafriks](https://gitea.com/lafriks) <lauris@nix.lv>
### Previous TOC/owners members
@@ -163,7 +208,7 @@ Here's the history of the owners and the time they served:
- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
- [6543](https://gitea.com/6543) - 2023
- [John Olheiser](https://gitea.com/jolheiser) - 2023
- [John Olheiser](https://gitea.com/jolheiser) - 2023, 2024
- [Jason Song](https://gitea.com/wolfogre) - 2023
## Governance Compensation
@@ -176,18 +221,6 @@ These funds will come from community sources like the OpenCollective rather than
Only non-company members are eligible for this compensation, and if a member of the community TOC takes the responsibility of release manager, they would only be compensated for their TOC duties.
Gitea Ltd employees are not eligible to receive any funds from the OpenCollective unless it is reimbursement for a purchase made for the Gitea project itself.
## TOC & Working groups
With Gitea covering many projects outside of the main repository, several groups will be created to help focus on specific areas instead of requiring maintainers to be a jack-of-all-trades. Maintainers are of course more than welcome to be part of multiple groups should they wish to contribute in multiple places.
The currently proposed groups are:
- **Core Group**: maintain the primary Gitea repository
- **Integration Group**: maintain the Gitea ecosystem's related tools, including go-sdk/tea/changelog/bots etc.
- **Documentation Group**: maintain related documents and repositories
- **Translation Group**: coordinate with translators and maintain translations
- **Security Group**: managed by TOC directly, members are decided by TOC, maintains security patches/responsible for security items
## Roadmap
Each year a roadmap will be discussed with the entire Gitea maintainers team, and feedback will be solicited from various stakeholders.
-4
View File
@@ -64,7 +64,6 @@ type FindRunOptions struct {
Ref string // the commit/tag/… that caused this workflow
TriggerUserID int64
TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true
Status []Status
ConcurrencyGroup string
CommitSHA string
@@ -81,9 +80,6 @@ func (opts FindRunOptions) ToConds() builder.Cond {
if opts.TriggerUserID > 0 {
cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID})
}
if opts.Approved {
cond = cond.And(builder.Gt{"`action_run`.approved_by": 0})
}
if len(opts.Status) > 0 {
cond = cond.And(builder.In("`action_run`.status", opts.Status))
}
+27 -12
View File
@@ -399,6 +399,24 @@ func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_mo
}
func ifNeedApproval(ctx context.Context, run *actions_model.ActionRun, repo *repo_model.Repository, user *user_model.User) (bool, error) {
canWrite := func(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) {
perm, err := access_model.GetDoerRepoPermission(ctx, repo, user)
if err != nil {
return false, err
}
return perm.CanWrite(unit_model.TypeActions), nil
}
return ifNeedApprovalWith(ctx, run, repo, user, canWrite, issues_model.HasMergedPullRequestInRepo)
}
func ifNeedApprovalWith(
ctx context.Context,
run *actions_model.ActionRun,
repo *repo_model.Repository,
user *user_model.User,
canWriteActions func(context.Context, *repo_model.Repository, *user_model.User) (bool, error),
hasMergedPR func(context.Context, int64, int64) (bool, error),
) (bool, error) {
// 1. don't need approval if it's not a fork PR
// 2. don't need approval if the event is `pull_request_target` since the workflow will run in the context of base branch
// see https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
@@ -413,27 +431,24 @@ func ifNeedApproval(ctx context.Context, run *actions_model.ActionRun, repo *rep
}
// don't need approval if the user can write
if perm, err := access_model.GetDoerRepoPermission(ctx, repo, user); err != nil {
if ok, err := canWriteActions(ctx, repo, user); err != nil {
return false, fmt.Errorf("GetDoerRepoPermission: %w", err)
} else if perm.CanWrite(unit_model.TypeActions) {
} else if ok {
log.Trace("do not need approval because user %d can write", user.ID)
return false, nil
}
// don't need approval if the user has been approved before
if count, err := db.Count[actions_model.ActionRun](ctx, actions_model.FindRunOptions{
RepoID: repo.ID,
TriggerUserID: user.ID,
Approved: true,
}); err != nil {
return false, fmt.Errorf("CountRuns: %w", err)
} else if count > 0 {
log.Trace("do not need approval because user %d has been approved before", user.ID)
// trust the user only after a merged PR — matching GitHub Actions. Approving one
// fork PR's run must not implicitly trust later fork PRs that replace the workflow.
if merged, err := hasMergedPR(ctx, repo.ID, user.ID); err != nil {
return false, fmt.Errorf("HasMergedPullRequestInRepo: %w", err)
} else if merged {
log.Trace("do not need approval because user %d has a merged pull request in repo %d", user.ID, repo.ID)
return false, nil
}
// otherwise, need approval
log.Trace("need approval because it's the first time user %d triggered actions", user.ID)
log.Trace("need approval because user %d has no merged pull request in repo %d", user.ID, repo.ID)
return true, nil
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"context"
"errors"
"testing"
actions_model "gitea.dev/models/actions"
repo_model "gitea.dev/models/repo"
user_model "gitea.dev/models/user"
actions_module "gitea.dev/modules/actions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIfNeedApproval(t *testing.T) {
alwaysWrite := func(_ context.Context, _ *repo_model.Repository, _ *user_model.User) (bool, error) {
return true, nil
}
neverWrite := func(_ context.Context, _ *repo_model.Repository, _ *user_model.User) (bool, error) {
return false, nil
}
hasMerged := func(_ context.Context, _, _ int64) (bool, error) { return true, nil }
noMerged := func(_ context.Context, _, _ int64) (bool, error) { return false, nil }
errPerm := errors.New("perm error")
errMerge := errors.New("merge error")
forkRun := &actions_model.ActionRun{IsForkPullRequest: true, TriggerEvent: actions_module.GithubEventPullRequest}
nonForkRun := &actions_model.ActionRun{IsForkPullRequest: false, TriggerEvent: actions_module.GithubEventPullRequest}
prTargetRun := &actions_model.ActionRun{IsForkPullRequest: true, TriggerEvent: actions_module.GithubEventPullRequestTarget}
repo := &repo_model.Repository{ID: 1}
normalUser := &user_model.User{ID: 10}
restrictedUser := &user_model.User{ID: 11, IsRestricted: true}
t.Run("not a fork PR never needs approval", func(t *testing.T) {
need, err := ifNeedApprovalWith(t.Context(), nonForkRun, repo, normalUser, alwaysWrite, hasMerged)
require.NoError(t, err)
assert.False(t, need)
})
t.Run("pull_request_target never needs approval even when fork", func(t *testing.T) {
need, err := ifNeedApprovalWith(t.Context(), prTargetRun, repo, normalUser, alwaysWrite, hasMerged)
require.NoError(t, err)
assert.False(t, need)
})
t.Run("restricted user always needs approval", func(t *testing.T) {
need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, restrictedUser, alwaysWrite, hasMerged)
require.NoError(t, err)
assert.True(t, need)
})
t.Run("fork PR with write permission does not need approval", func(t *testing.T) {
need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, alwaysWrite, noMerged)
require.NoError(t, err)
assert.False(t, need)
})
t.Run("fork PR with merged PR but no write permission does not need approval", func(t *testing.T) {
need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, neverWrite, hasMerged)
require.NoError(t, err)
assert.False(t, need)
})
t.Run("fork PR with no write and no merged PR needs approval", func(t *testing.T) {
need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, neverWrite, noMerged)
require.NoError(t, err)
assert.True(t, need)
})
t.Run("canWriteActions error is propagated", func(t *testing.T) {
failWrite := func(_ context.Context, _ *repo_model.Repository, _ *user_model.User) (bool, error) {
return false, errPerm
}
_, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, failWrite, noMerged)
require.ErrorIs(t, err, errPerm)
})
t.Run("hasMergedPR error is propagated", func(t *testing.T) {
failMerge := func(_ context.Context, _, _ int64) (bool, error) { return false, errMerge }
_, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, neverWrite, failMerge)
require.ErrorIs(t, err, errMerge)
})
t.Run("restricted user skips permission check entirely", func(t *testing.T) {
// The perm and merge functions must not be called for a restricted user.
called := false
trackWrite := func(_ context.Context, _ *repo_model.Repository, _ *user_model.User) (bool, error) {
called = true
return true, nil
}
need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, restrictedUser, trackWrite, noMerged)
require.NoError(t, err)
assert.True(t, need)
assert.False(t, called, "permission check must not run for restricted user")
})
}
+27 -27
View File
@@ -22995,7 +22995,7 @@
"x-go-name": "SHA"
},
"state": {
"description": "State is the overall combined status state\npending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
"description": "State is the overall combined status state\npending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped",
"type": "string",
"enum": [
"pending",
@@ -23005,7 +23005,7 @@
"warning",
"skipped"
],
"x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
"x-go-enum-desc": "pending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped",
"x-go-name": "State"
},
"statuses": {
@@ -23260,7 +23260,7 @@
"x-go-name": "ID"
},
"status": {
"description": "State represents the status state (pending, success, error, failure)\npending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
"description": "State represents the status state (pending, success, error, failure)\npending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped",
"type": "string",
"enum": [
"pending",
@@ -23270,7 +23270,7 @@
"warning",
"skipped"
],
"x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
"x-go-enum-desc": "pending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped",
"x-go-name": "State"
},
"target_url": {
@@ -24313,7 +24313,7 @@
"REQUEST_CHANGES",
"REQUEST_REVIEW"
],
"x-go-enum-desc": "APPROVED ReviewStateApproved ReviewStateApproved pr is approved\nPENDING ReviewStatePending ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview ReviewStateRequestReview review is requested from user",
"x-go-enum-desc": "APPROVED ReviewStateApproved pr is approved\nPENDING ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview review is requested from user",
"x-go-name": "Event"
}
},
@@ -24492,7 +24492,7 @@
"x-go-name": "Description"
},
"state": {
"description": "State represents the status state to set (pending, success, error, failure)\npending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
"description": "State represents the status state to set (pending, success, error, failure)\npending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped",
"type": "string",
"enum": [
"pending",
@@ -24502,7 +24502,7 @@
"warning",
"skipped"
],
"x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
"x-go-enum-desc": "pending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped",
"x-go-name": "State"
},
"target_url": {
@@ -26811,7 +26811,7 @@
"open",
"closed"
],
"x-go-enum-desc": "open StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"x-go-enum-desc": "open StateOpen pr is opened\nclosed StateClosed pr is closed",
"x-go-name": "State"
},
"time_estimate": {
@@ -27169,19 +27169,19 @@
"type": "object",
"properties": {
"Context": {
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}",
"type": "string"
},
"Mode": {
"description": "Mode to render (markdown, comment, wiki, file)\n\nin: body",
"description": "Mode to render (markdown, comment, wiki, file)",
"type": "string"
},
"Text": {
"description": "Text markdown to render\n\nin: body",
"description": "Text markdown to render",
"type": "string"
},
"Wiki": {
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true\nin: body",
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true",
"type": "boolean"
}
},
@@ -27192,23 +27192,23 @@
"type": "object",
"properties": {
"Context": {
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}",
"type": "string"
},
"FilePath": {
"description": "File path for detecting extension in file mode\n\nin: body",
"description": "File path for detecting extension in file mode",
"type": "string"
},
"Mode": {
"description": "Mode to render (markdown, comment, wiki, file)\n\nin: body",
"description": "Mode to render (markdown, comment, wiki, file)",
"type": "string"
},
"Text": {
"description": "Text markup to render\n\nin: body",
"description": "Text markup to render",
"type": "string"
},
"Wiki": {
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true\nin: body",
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true",
"type": "boolean"
}
},
@@ -27445,13 +27445,13 @@
"x-go-name": "OpenIssues"
},
"state": {
"description": "State indicates if the milestone is open or closed\nopen StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"description": "State indicates if the milestone is open or closed\nopen StateOpen pr is opened\nclosed StateClosed pr is closed",
"type": "string",
"enum": [
"open",
"closed"
],
"x-go-enum-desc": "open StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"x-go-enum-desc": "open StateOpen pr is opened\nclosed StateClosed pr is closed",
"x-go-name": "State"
},
"title": {
@@ -27666,14 +27666,14 @@
"x-go-name": "LatestCommentURL"
},
"state": {
"description": "State indicates the current state of the notification subject\nopen NotifySubjectStateOpen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged NotifySubjectStateMerged is a merged pull request",
"description": "State indicates the current state of the notification subject\nopen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged is a merged pull request",
"type": "string",
"enum": [
"open",
"closed",
"merged"
],
"x-go-enum-desc": "open NotifySubjectStateOpen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged NotifySubjectStateMerged is a merged pull request",
"x-go-enum-desc": "open NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged is a merged pull request",
"x-go-name": "State"
},
"title": {
@@ -27682,7 +27682,7 @@
"x-go-name": "Title"
},
"type": {
"description": "Type indicates the type of the notification subject\nIssue NotifySubjectIssue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository NotifySubjectRepository a repository is subject of an notification",
"description": "Type indicates the type of the notification subject\nIssue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository a repository is subject of an notification",
"type": "string",
"enum": [
"Issue",
@@ -27690,7 +27690,7 @@
"Commit",
"Repository"
],
"x-go-enum-desc": "Issue NotifySubjectIssue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository NotifySubjectRepository a repository is subject of an notification",
"x-go-enum-desc": "Issue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository a repository is subject of an notification",
"x-go-name": "Type"
},
"url": {
@@ -28446,13 +28446,13 @@
"x-go-name": "ReviewComments"
},
"state": {
"description": "The current state of the pull request\nopen StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"description": "The current state of the pull request\nopen StateOpen pr is opened\nclosed StateClosed pr is closed",
"type": "string",
"enum": [
"open",
"closed"
],
"x-go-enum-desc": "open StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed",
"x-go-enum-desc": "open StateOpen pr is opened\nclosed StateClosed pr is closed",
"x-go-name": "State"
},
"title": {
@@ -28553,7 +28553,7 @@
"REQUEST_CHANGES",
"REQUEST_REVIEW"
],
"x-go-enum-desc": "APPROVED ReviewStateApproved ReviewStateApproved pr is approved\nPENDING ReviewStatePending ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview ReviewStateRequestReview review is requested from user",
"x-go-enum-desc": "APPROVED ReviewStateApproved pr is approved\nPENDING ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview review is requested from user",
"x-go-name": "State"
},
"submitted_at": {
@@ -29448,7 +29448,7 @@
"REQUEST_CHANGES",
"REQUEST_REVIEW"
],
"x-go-enum-desc": "APPROVED ReviewStateApproved ReviewStateApproved pr is approved\nPENDING ReviewStatePending ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview ReviewStateRequestReview review is requested from user",
"x-go-enum-desc": "APPROVED ReviewStateApproved pr is approved\nPENDING ReviewStatePending pr state is pending\nCOMMENT ReviewStateComment is a comment review\nREQUEST_CHANGES ReviewStateRequestChanges changes for pr are requested\nREQUEST_REVIEW ReviewStateRequestReview review is requested from user",
"x-go-name": "Event"
}
},
+18 -18
View File
@@ -3179,7 +3179,7 @@
"$ref": "#/components/schemas/CommitStatusState"
}
],
"description": "State is the overall combined status state\npending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped"
"description": "State is the overall combined status state\npending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped"
},
"statuses": {
"description": "Statuses contains all individual commit statuses",
@@ -3445,7 +3445,7 @@
"$ref": "#/components/schemas/CommitStatusState"
}
],
"description": "State represents the status state (pending, success, error, failure)\npending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped"
"description": "State represents the status state (pending, success, error, failure)\npending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped"
},
"target_url": {
"description": "TargetURL is the URL to link to for more details",
@@ -4673,7 +4673,7 @@
"$ref": "#/components/schemas/CommitStatusState"
}
],
"description": "State represents the status state to set (pending, success, error, failure)\npending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped"
"description": "State represents the status state to set (pending, success, error, failure)\npending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped is for when CommitStatus is Skipped"
},
"target_url": {
"description": "TargetURL is the URL to link to for more details",
@@ -7326,20 +7326,20 @@
"description": "MarkdownOption markdown options",
"properties": {
"Context": {
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}",
"type": "string"
},
"Mode": {
"description": "Mode to render (markdown, comment, wiki, file)\n\nin: body",
"description": "Mode to render (markdown, comment, wiki, file)",
"type": "string"
},
"Text": {
"description": "Text markdown to render\n\nin: body",
"description": "Text markdown to render",
"type": "string"
},
"Wiki": {
"deprecated": true,
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true\nin: body",
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true",
"type": "boolean"
}
},
@@ -7350,24 +7350,24 @@
"description": "MarkupOption markup options",
"properties": {
"Context": {
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}",
"type": "string"
},
"FilePath": {
"description": "File path for detecting extension in file mode\n\nin: body",
"description": "File path for detecting extension in file mode",
"type": "string"
},
"Mode": {
"description": "Mode to render (markdown, comment, wiki, file)\n\nin: body",
"description": "Mode to render (markdown, comment, wiki, file)",
"type": "string"
},
"Text": {
"description": "Text markup to render\n\nin: body",
"description": "Text markup to render",
"type": "string"
},
"Wiki": {
"deprecated": true,
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true\nin: body",
"description": "Is it a wiki page? (use mode=wiki instead)\n\nDeprecated: true",
"type": "boolean"
}
},
@@ -7610,7 +7610,7 @@
"$ref": "#/components/schemas/StateType"
}
],
"description": "State indicates if the milestone is open or closed\nopen StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed"
"description": "State indicates if the milestone is open or closed\nopen StateOpen pr is opened\nclosed StateClosed pr is closed"
},
"title": {
"description": "Title is the title of the milestone",
@@ -7827,14 +7827,14 @@
"x-go-name": "LatestCommentURL"
},
"state": {
"description": "State indicates the current state of the notification subject\nopen NotifySubjectStateOpen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged NotifySubjectStateMerged is a merged pull request",
"description": "State indicates the current state of the notification subject\nopen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged is a merged pull request",
"enum": [
"open",
"closed",
"merged"
],
"type": "string",
"x-go-enum-desc": "open NotifySubjectStateOpen NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged NotifySubjectStateMerged is a merged pull request",
"x-go-enum-desc": "open NotifySubjectStateOpen is an open subject\nclosed NotifySubjectStateClosed is a closed subject\nmerged NotifySubjectStateMerged is a merged pull request",
"x-go-name": "State"
},
"title": {
@@ -7843,7 +7843,7 @@
"x-go-name": "Title"
},
"type": {
"description": "Type indicates the type of the notification subject\nIssue NotifySubjectIssue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository NotifySubjectRepository a repository is subject of an notification",
"description": "Type indicates the type of the notification subject\nIssue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository a repository is subject of an notification",
"enum": [
"Issue",
"Pull",
@@ -7851,7 +7851,7 @@
"Repository"
],
"type": "string",
"x-go-enum-desc": "Issue NotifySubjectIssue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository NotifySubjectRepository a repository is subject of an notification",
"x-go-enum-desc": "Issue NotifySubjectIssue a issue is subject of an notification\nPull NotifySubjectPull a pull is subject of an notification\nCommit NotifySubjectCommit a commit is subject of an notification\nRepository NotifySubjectRepository a repository is subject of an notification",
"x-go-name": "Type"
},
"url": {
@@ -8626,7 +8626,7 @@
"$ref": "#/components/schemas/StateType"
}
],
"description": "The current state of the pull request\nopen StateOpen StateOpen pr is opened\nclosed StateClosed StateClosed pr is closed"
"description": "The current state of the pull request\nopen StateOpen pr is opened\nclosed StateClosed pr is closed"
},
"title": {
"description": "The title of the pull request",
+104
View File
@@ -139,3 +139,107 @@ jobs:
assert.Equal(t, actions_model.StatusWaiting, run2.Status)
})
}
// TestForkPullRequestApprovalNotBypassedByPriorApproval verifies that a single
// approval on a fork PR does not permanently trust the contributor: a subsequent
// fork PR from the same user must still be gated (Blocked / NeedApproval=true)
// until that user has had a pull request merged in the repo.
func TestForkPullRequestApprovalNotBypassedByPriorApproval(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user2Session := loginUser(t, user2.Name)
user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
user4Session := loginUser(t, user4.Name)
user4Token := getTokenForLoggedInUser(t, user4Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
apiBaseRepo := createActionsTestRepo(t, user2Token, "fork-approval-regression", false)
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID})
user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository)
defer doAPIDeleteRepository(user2APICtx)(t)
wfTreePath := ".gitea/workflows/ci.yml"
wfContent := `name: CI
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo ok
`
createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath,
getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "add ci", wfContent))
// user4 forks the repo
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name),
&api.CreateForkOption{Name: new("fork-approval-regression-fork")}).AddTokenAuth(user4Token)
resp := MakeRequest(t, req, http.StatusAccepted)
apiForkRepo := DecodeJSON(t, resp, &api.Repository{})
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID})
user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository)
defer doAPIDeleteRepository(user4APICtx)(t)
// PR #1: a benign change from user4's fork — first-time contributor, gate engages.
doAPICreateFile(user4APICtx, "first.txt", &api.CreateFileOptions{
FileOptions: api.FileOptions{
NewBranchName: "first",
Message: "first",
Author: api.Identity{Name: user4.Name, Email: user4.Email},
Committer: api.Identity{Name: user4.Name, Email: user4.Email},
Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()},
},
ContentBase64: base64.StdEncoding.EncodeToString([]byte("first")),
})(t)
pr1, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":first")(t)
assert.NoError(t, err)
run1 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, Ref: fmt.Sprintf("refs/pull/%d/head", pr1.Index)})
assert.True(t, run1.NeedApproval, "first fork PR must require approval")
assert.Equal(t, actions_model.StatusBlocked, run1.Status)
// user2 approves run1.
req = NewRequest(t, "POST", fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s", baseRepo.Link(), pr1.Head.Sha))
user2Session.MakeRequest(t, req, http.StatusOK)
run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run1.ID})
assert.False(t, run1.NeedApproval)
assert.Equal(t, user2.ID, run1.ApprovedBy)
// PR #2: same user, fresh branch. Pre-fix, this run was created with
// NeedApproval=false and dispatched immediately — the bypass path.
doAPICreateFile(user4APICtx, "second.txt", &api.CreateFileOptions{
FileOptions: api.FileOptions{
NewBranchName: "second",
Message: "second",
Author: api.Identity{Name: user4.Name, Email: user4.Email},
Committer: api.Identity{Name: user4.Name, Email: user4.Email},
Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()},
},
ContentBase64: base64.StdEncoding.EncodeToString([]byte("second")),
})(t)
pr2, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":second")(t)
assert.NoError(t, err)
run2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, Ref: fmt.Sprintf("refs/pull/%d/head", pr2.Index)})
assert.True(t, run2.NeedApproval, "second fork PR must still require approval — prior approval-to-run does not grant trust")
assert.Equal(t, actions_model.StatusBlocked, run2.Status)
assert.EqualValues(t, 0, run2.ApprovedBy)
// After merging PR #1, user4 becomes a known contributor and the gate lifts for a new PR.
doAPIMergePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, pr1.Index)(t)
doAPICreateFile(user4APICtx, "third.txt", &api.CreateFileOptions{
FileOptions: api.FileOptions{
NewBranchName: "third",
Message: "third",
Author: api.Identity{Name: user4.Name, Email: user4.Email},
Committer: api.Identity{Name: user4.Name, Email: user4.Email},
Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()},
},
ContentBase64: base64.StdEncoding.EncodeToString([]byte("third")),
})(t)
pr3, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":third")(t)
assert.NoError(t, err)
run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, Ref: fmt.Sprintf("refs/pull/%d/head", pr3.Index)})
assert.False(t, run3.NeedApproval, "fork PR from a user with a prior merged PR should not require approval")
})
}
@@ -486,14 +486,18 @@ jobs:
},
ContentBase64: base64.StdEncoding.EncodeToString([]byte("user4-fix2")),
})(t)
doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":do-not-cancel/ccc")(t)
// cannot fetch the task because cancel-in-progress is false
pr3, _ := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":do-not-cancel/ccc")(t)
// cannot fetch the task: approval still required (user4 has no merged PR) and cancel-in-progress is false
runner.fetchNoTask(t)
runner.execTask(t, pr2Task1, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
pr2Run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: pr2Run1.ID})
assert.Equal(t, actions_model.StatusSuccess, pr2Run1.Status)
// user2 approves the third PR's run (user4 still has no merged PR, approval still required)
pr3Run1Pending := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, Ref: fmt.Sprintf("refs/pull/%d/head", pr3.Index)})
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/approve", baseRepo.OwnerName, baseRepo.Name, pr3Run1Pending.ID))
user2Session.MakeRequest(t, req, http.StatusOK)
// fetch the task
pr3Task1 := runner.fetchTask(t)
_, _, pr3Run1 := getTaskAndJobAndRunByTaskID(t, pr3Task1.Id)