mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-11 15:04:02 +09:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d29364a4f6 | ||
|
|
998a06627e | ||
|
|
250d18d5f6 | ||
|
|
c26daf578f | ||
|
|
5371e788fe | ||
|
|
0001e58e6b | ||
|
|
79c4237cb2 | ||
|
|
20f73b2230 | ||
|
|
175bc89bf9 | ||
|
|
793e77b073 | ||
|
|
5018ae1b29 | ||
|
|
c60b13b2dd | ||
|
|
27bd022b34 |
No files matched your search
@@ -37,8 +37,15 @@ jobs:
|
||||
# retry snapcraft uploads which can be flaky
|
||||
- name: Publish snap
|
||||
run: |
|
||||
# part-gitea-pull.sh decides what is built, see the comments there:
|
||||
# * grade devel (main or a prerelease tag) goes to latest/edge
|
||||
# * grade stable (a new release tag) goes to latest/stable, and to latest/candidate which marks it as released
|
||||
channel=latest/edge
|
||||
if unsquashfs -cat "$SNAP" meta/snap.yaml | grep -qx 'grade: stable'; then
|
||||
channel=latest/stable,latest/candidate
|
||||
fi
|
||||
for attempt in 1 2 3 4 5; do
|
||||
snapcraft upload "$SNAP" --release latest/edge && exit 0
|
||||
snapcraft upload "$SNAP" --release "$channel" && exit 0
|
||||
echo "::warning::snap upload attempt $attempt failed, retrying in 15s"
|
||||
sleep 15
|
||||
done
|
||||
|
||||
@@ -76,6 +76,10 @@ jobs:
|
||||
run: |
|
||||
previous=$(git tag --list --sort=-v:refname | grep -xE 'v[0-9]+\.[0-9]+\.[0-9]+' | grep -A1 -xF "$GITHUB_REF_NAME" | tail -1) # highest stable version below this one
|
||||
echo "range=$previous..$GITHUB_SHA" >> "$GITHUB_OUTPUT"
|
||||
- name: skip reverts and reverted commits in changelog
|
||||
env:
|
||||
RANGE: ${{ steps.range.outputs.range }}
|
||||
run: ./tools/generate-cliffignore.sh "$RANGE" | sort -u > .cliffignore
|
||||
- uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
|
||||
with:
|
||||
args: --tag ${{ github.ref_name }} ${{ steps.range.outputs.range }}
|
||||
|
||||
@@ -6,7 +6,7 @@ SHASUM ?= shasum -a 256
|
||||
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1.67.4 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v4/cmd/editorconfig-checker@v4.0.2 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # 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.36.6 # renovate: datasource=go
|
||||
|
||||
@@ -810,7 +810,6 @@ LEVEL = Info
|
||||
;[git.config]
|
||||
;diff.algorithm = histogram
|
||||
;core.logAllRefUpdates = true
|
||||
;gc.reflogExpire = 90
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -1098,8 +1097,9 @@ LEVEL = Info
|
||||
;; Force ssh:// clone url instead of scp-style uri when default SSH port is used
|
||||
;USE_COMPAT_SSH_URI = false
|
||||
;;
|
||||
;; Value for the "go get" request returns the repository url as https or ssh, default is https
|
||||
;GO_GET_CLONE_URL_PROTOCOL = https
|
||||
;; Scheme of the returned URL for the "go get" response.
|
||||
;; Default is "https" if DISABLE_HTTP_GIT=false or SSH is disabled, otherwise "ssh".
|
||||
;GO_GET_CLONE_URL_PROTOCOL =
|
||||
;;
|
||||
;; Close issues as long as a commit on any branch marks it as fixed
|
||||
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
||||
|
||||
@@ -2,7 +2,7 @@ module gitea.dev
|
||||
|
||||
go 1.27
|
||||
|
||||
toolchain go1.27.1
|
||||
toolchain go1.27.2
|
||||
|
||||
require (
|
||||
connectrpc.com/connect v1.21.0
|
||||
@@ -98,7 +98,7 @@ require (
|
||||
golang.org/x/crypto v0.57.0
|
||||
golang.org/x/image v0.46.0
|
||||
golang.org/x/mod v0.41.0
|
||||
golang.org/x/net v0.59.0
|
||||
golang.org/x/net v0.60.0
|
||||
golang.org/x/oauth2 v0.37.0
|
||||
golang.org/x/sync v0.23.0
|
||||
golang.org/x/sys v0.48.0
|
||||
|
||||
@@ -714,8 +714,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
|
||||
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
||||
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
+20
-11
@@ -853,25 +853,34 @@ func cancelReusableCaller(ctx context.Context, caller *ActionRunJob, force bool)
|
||||
}
|
||||
|
||||
// Cancel descendants deepest-first, then the caller: a caller's status is aggregated from its children,
|
||||
// so each child must reach its final state before its parent caller is re-aggregated.
|
||||
// so each child must be cancelled before its parent caller is re-read.
|
||||
// A child's ID always exceeds its parent's, so descending ID is a valid deepest-first order.
|
||||
descendants := CollectAllDescendantJobs(caller, attemptJobs)
|
||||
slices.SortFunc(descendants, func(a, b *ActionRunJob) int { return cmp.Compare(b.ID, a.ID) })
|
||||
callersWithChildren := make(container.Set[int64])
|
||||
for _, d := range descendants {
|
||||
callersWithChildren.Add(d.ParentJobID)
|
||||
}
|
||||
|
||||
for _, c := range descendants {
|
||||
cancelled, err := cancelOneJob(ctx, c, force)
|
||||
for _, job := range append(descendants, caller) {
|
||||
if !callersWithChildren.Contains(job.ID) {
|
||||
cancelled, err := cancelOneJob(ctx, job, force)
|
||||
if err != nil {
|
||||
return cancelledJobs, err
|
||||
}
|
||||
if cancelled != nil {
|
||||
cancelledJobs = append(cancelledJobs, cancelled)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// the job is a caller and its children's cascade already re-aggregated it
|
||||
reloaded, err := GetRunJobByRunAndID(ctx, job.RunID, job.ID)
|
||||
if err != nil {
|
||||
return cancelledJobs, err
|
||||
}
|
||||
if cancelled != nil {
|
||||
cancelledJobs = append(cancelledJobs, cancelled)
|
||||
if reloaded.Status != job.Status {
|
||||
cancelledJobs = append(cancelledJobs, reloaded)
|
||||
}
|
||||
}
|
||||
|
||||
if c, err := cancelOneJob(ctx, caller, force); err != nil {
|
||||
return cancelledJobs, err
|
||||
} else if c != nil {
|
||||
cancelledJobs = append(cancelledJobs, c)
|
||||
}
|
||||
return cancelledJobs, nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
runnerv1 "gitea.dev/actionslib/runner/v1"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
@@ -397,3 +398,72 @@ func TestForceCancelJobs(t *testing.T) {
|
||||
assert.Equal(t, StatusCancelled, callerAfter.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCancelJobs_CallerWaitsForCancellingChild(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
run := &ActionRun{
|
||||
Title: "caller-cancelling-child",
|
||||
RepoID: 4,
|
||||
Index: 9811,
|
||||
OwnerID: 1,
|
||||
WorkflowID: "test.yaml",
|
||||
TriggerUserID: 1,
|
||||
Ref: "refs/heads/master",
|
||||
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||
Event: "push",
|
||||
TriggerEvent: "push",
|
||||
EventPayload: "{}",
|
||||
Status: StatusRunning,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, run))
|
||||
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusRunning}
|
||||
require.NoError(t, db.Insert(ctx, attempt))
|
||||
run.LatestAttemptID = attempt.ID
|
||||
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
|
||||
|
||||
newJob := func(name string, parentID int64, isCaller bool) *ActionRunJob {
|
||||
job := &ActionRunJob{
|
||||
RunID: run.ID,
|
||||
RunAttemptID: attempt.ID,
|
||||
RepoID: run.RepoID,
|
||||
OwnerID: run.OwnerID,
|
||||
CommitSHA: run.CommitSHA,
|
||||
Name: name,
|
||||
JobID: name,
|
||||
Attempt: 1,
|
||||
Status: StatusRunning,
|
||||
ParentJobID: parentID,
|
||||
IsReusableCaller: isCaller,
|
||||
IsExpanded: isCaller,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, job))
|
||||
return job
|
||||
}
|
||||
outer := newJob("outer", 0, true)
|
||||
inner := newJob("inner", outer.ID, true)
|
||||
child := newJob("child", inner.ID, false)
|
||||
|
||||
runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
|
||||
require.NoError(t, db.Insert(ctx, runner))
|
||||
task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
|
||||
require.NoError(t, db.Insert(ctx, task))
|
||||
child.TaskID = task.ID
|
||||
_, err := UpdateRunJob(ctx, child, nil, "task_id")
|
||||
require.NoError(t, err)
|
||||
|
||||
cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, cancelled, 3)
|
||||
for _, job := range []*ActionRunJob{outer, inner, child} {
|
||||
assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
||||
}
|
||||
|
||||
_, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
|
||||
require.NoError(t, err)
|
||||
for _, job := range []*ActionRunJob{outer, inner, child} {
|
||||
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
||||
}
|
||||
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
|
||||
}
|
||||
@@ -99,8 +99,8 @@ func (protectBranch *ProtectedBranch) loadGlob() {
|
||||
var err error
|
||||
protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/')
|
||||
if err != nil {
|
||||
log.Warn("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err)
|
||||
protectBranch.globRule = glob.MustCompile(glob.QuoteMeta(protectBranch.RuleName), '/')
|
||||
log.Debug("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err)
|
||||
protectBranch.globRule, _ = glob.Compile(glob.QuoteMeta(protectBranch.RuleName), '/')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,9 @@ func (protectBranch *ProtectedBranch) Match(branchName string) bool {
|
||||
if protectBranch.isPlainName {
|
||||
return strings.EqualFold(protectBranch.RuleName, branchName)
|
||||
}
|
||||
|
||||
if protectBranch.globRule == nil {
|
||||
return false // in case of invalid glob rule, we don't match anything
|
||||
}
|
||||
return protectBranch.globRule.Match(branchName)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/glob"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
)
|
||||
|
||||
@@ -55,10 +56,17 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB
|
||||
// FindAllMatchedBranches find all matched branches
|
||||
func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
|
||||
results := make([]string, 0, 10)
|
||||
rule, err := glob.Compile(ruleName)
|
||||
if err != nil {
|
||||
log.Debug("Failed to compile rule %s: %v", ruleName, err)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
const pageSize = 100
|
||||
for page := 1; ; page++ {
|
||||
brancheNames, err := FindBranchNames(ctx, FindBranchOptions{
|
||||
branchNames, err := FindBranchNames(ctx, FindBranchOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
PageSize: 100,
|
||||
PageSize: pageSize,
|
||||
Page: page,
|
||||
},
|
||||
RepoID: repoID,
|
||||
@@ -67,14 +75,12 @@ func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule := glob.MustCompile(ruleName)
|
||||
|
||||
for _, branch := range brancheNames {
|
||||
for _, branch := range branchNames {
|
||||
if rule.Match(branch) {
|
||||
results = append(results, branch)
|
||||
}
|
||||
}
|
||||
if len(brancheNames) < 100 {
|
||||
if len(branchNames) < pageSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -616,6 +616,15 @@ func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string {
|
||||
|
||||
// ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name.
|
||||
func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
|
||||
return composeSSHCloneURL(doer, ownerName, repoName, setting.Repository.UseCompatSSHURI)
|
||||
}
|
||||
|
||||
// ComposeSSHCloneURI is like ComposeSSHCloneURL but always returns the "ssh://" form, because "go get" rejects scp-style addresses
|
||||
func ComposeSSHCloneURI(doer *user_model.User, ownerName, repoName string) string {
|
||||
return composeSSHCloneURL(doer, ownerName, repoName, true)
|
||||
}
|
||||
|
||||
func composeSSHCloneURL(doer *user_model.User, ownerName, repoName string, useURI bool) string {
|
||||
sshUser := setting.SSH.User
|
||||
sshDomain := setting.SSH.Domain
|
||||
|
||||
@@ -642,7 +651,7 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin
|
||||
if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil {
|
||||
sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets
|
||||
}
|
||||
if setting.Repository.UseCompatSSHURI {
|
||||
if useURI {
|
||||
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||
}
|
||||
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||
|
||||
@@ -185,6 +185,8 @@ func TestComposeSSHCloneURL(t *testing.T) {
|
||||
assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
|
||||
setting.Repository.UseCompatSSHURI = true
|
||||
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
|
||||
setting.Repository.UseCompatSSHURI = false
|
||||
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURI(nil, "user", "repo"))
|
||||
// test SSH_DOMAIN while use non-standard SSH port
|
||||
setting.SSH.Port = 123
|
||||
setting.Repository.UseCompatSSHURI = false
|
||||
|
||||
@@ -151,16 +151,28 @@ func TestListEmails(t *testing.T) {
|
||||
|
||||
func TestEmailAddressValidate(t *testing.T) {
|
||||
cases := map[string]bool{
|
||||
"": false,
|
||||
"root@localhost": true,
|
||||
"user@[192.168.1.2]": true,
|
||||
"@a": false,
|
||||
"abc@gmail.com": true,
|
||||
"abc@gmail.com\n": false,
|
||||
"": false,
|
||||
"@a": false,
|
||||
|
||||
// "_" shouldn't appear in domain but can appear in hostname, since we can't stop site admins from doing so, just accept it
|
||||
"root@local_host": true,
|
||||
"root@localhost": true,
|
||||
"root@LOCALHOST": true,
|
||||
|
||||
"user@[192.168.1.2]": true,
|
||||
"user@[IPv6:FFff::1]": true,
|
||||
|
||||
"abc@gmail.com": true,
|
||||
"abc@gmail.com.": false,
|
||||
"abc@gmail.com-": false,
|
||||
"abc@gmail.com\n": false,
|
||||
"abc@gmail..com": false,
|
||||
"abc@gmail com": false,
|
||||
"abc@gmail*com": false,
|
||||
|
||||
"Foo <foo@bar.com>": false,
|
||||
"abc@gmail.com (x)": false,
|
||||
"jürgen@example.com": false,
|
||||
"a@foo_bar.com": false,
|
||||
"jürgen@example.com": false, // utf8 address is not supported yet
|
||||
}
|
||||
for tc, isValid := range cases {
|
||||
t.Run(tc, func(t *testing.T) {
|
||||
|
||||
@@ -532,7 +532,7 @@ func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool
|
||||
}
|
||||
|
||||
for _, val := range vals {
|
||||
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
|
||||
if matchActivityType(val, actions...) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
@@ -575,7 +575,7 @@ func matchPullRequestEvent(ctx context.Context, gitRepo *git.Repository, commit
|
||||
}
|
||||
log.Trace("matching pull_request %s with %v", action, vals)
|
||||
for _, val := range vals {
|
||||
if glob.MustCompile(val, '/').Match(string(action)) {
|
||||
if matchActivityType(val, string(action)) {
|
||||
activityTypeMatched = true
|
||||
matchTimes++
|
||||
break
|
||||
@@ -678,7 +678,7 @@ func matchIssueCommentEvent(issueCommentPayload *api.IssueCommentPayload, evt *j
|
||||
// NONE
|
||||
|
||||
for _, val := range vals {
|
||||
if glob.MustCompile(val, '/').Match(string(issueCommentPayload.Action)) {
|
||||
if matchActivityType(val, string(issueCommentPayload.Action)) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
@@ -718,7 +718,7 @@ func matchPullRequestReviewEvent(prPayload *api.PullRequestPayload, evt *jobpars
|
||||
}
|
||||
|
||||
for _, val := range vals {
|
||||
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
|
||||
if matchActivityType(val, actions...) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
@@ -758,7 +758,7 @@ func matchPullRequestReviewCommentEvent(prPayload *api.PullRequestPayload, evt *
|
||||
}
|
||||
|
||||
for _, val := range vals {
|
||||
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
|
||||
if matchActivityType(val, actions...) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
@@ -795,7 +795,7 @@ func matchReleaseEvent(payload *api.ReleasePayload, evt *jobparser.Event) bool {
|
||||
action = "edited"
|
||||
}
|
||||
for _, val := range vals {
|
||||
if glob.MustCompile(val, '/').Match(string(action)) {
|
||||
if matchActivityType(val, string(action)) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
@@ -832,7 +832,7 @@ func matchPackageEvent(payload *api.PackagePayload, evt *jobparser.Event) bool {
|
||||
action = "published"
|
||||
}
|
||||
for _, val := range vals {
|
||||
if glob.MustCompile(val, '/').Match(string(action)) {
|
||||
if matchActivityType(val, string(action)) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
@@ -857,7 +857,7 @@ func matchWorkflowRunEvent(payload *api.WorkflowRunPayload, evt *jobparser.Event
|
||||
case "types":
|
||||
action := payload.Action
|
||||
for _, val := range vals {
|
||||
if glob.MustCompile(val, '/').Match(action) {
|
||||
if matchActivityType(val, action) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
@@ -893,3 +893,13 @@ func matchWorkflowRunEvent(payload *api.WorkflowRunPayload, evt *jobparser.Event
|
||||
}
|
||||
return matchTimes == len(evt.Acts())
|
||||
}
|
||||
|
||||
// matchActivityType treats an invalid user-provided pattern as no match instead of panicking
|
||||
func matchActivityType(pattern string, actions ...string) bool {
|
||||
g, err := glob.Compile(pattern, '/')
|
||||
if err != nil {
|
||||
log.Debug("invalid activity type pattern %q: %v", pattern, err)
|
||||
return false
|
||||
}
|
||||
return slices.ContainsFunc(actions, g.Match)
|
||||
}
|
||||
@@ -205,6 +205,13 @@ func TestDetectMatched(t *testing.T) {
|
||||
yamlOn: "on:\n release:\n types: [published]",
|
||||
expected: detectMatched,
|
||||
},
|
||||
{
|
||||
desc: "HookEventRelease(release) doesn't match an invalid activity type pattern",
|
||||
triggedEvent: webhook_module.HookEventRelease,
|
||||
payload: &api.ReleasePayload{Action: api.HookReleasePublished},
|
||||
yamlOn: "on:\n release:\n types: [\"[\"]",
|
||||
expected: detectNotApplicable,
|
||||
},
|
||||
{
|
||||
desc: "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type",
|
||||
triggedEvent: webhook_module.HookEventPackage,
|
||||
|
||||
@@ -102,11 +102,11 @@ func NewEmbeddedFS(data []byte) fs.ReadDirFS {
|
||||
efs := &embeddedFS{data: data, files: make(map[string]*embeddedFileInfo)}
|
||||
efs.meta = sync.OnceValue(func() *EmbeddedMeta {
|
||||
var meta EmbeddedMeta
|
||||
p := bytes.LastIndexByte(data, '\n')
|
||||
if p < 0 {
|
||||
_, metaJSON, ok := bytes.CutLast(data, []byte{'\n'})
|
||||
if !ok {
|
||||
return &meta
|
||||
}
|
||||
if err := json.Unmarshal(data[p+1:], &meta); err != nil {
|
||||
if err := json.Unmarshal(metaJSON, &meta); err != nil {
|
||||
panic("embedded file is not valid")
|
||||
}
|
||||
return &meta
|
||||
|
||||
+11
-15
@@ -125,6 +125,11 @@ func syncGitConfig(ctx context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Gitea used to write "90", which git reads as a date in 1990
|
||||
if err := configUnsetAll(ctx, "gc.reflogExpire", "90"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply user's git config options last so they take precedence over builtin defaults
|
||||
for k, v := range setting.GitConfig.Options {
|
||||
if err = configSet(ctx, strings.ToLower(k), v); err != nil {
|
||||
@@ -162,7 +167,7 @@ func configSet(ctx context.Context, key, value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, _, err = gitcmd.NewCommand("config", "--global").
|
||||
if _, _, err = gitcmd.NewCommand("config", "--global", "--replace-all").
|
||||
AddDynamicArguments(key, value).
|
||||
RunStdString(ctx); err != nil {
|
||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
||||
@@ -190,7 +195,7 @@ func configSetNonExist(ctx context.Context, key, value string) error {
|
||||
}
|
||||
|
||||
func configAddNonExist(ctx context.Context, key, value string) error {
|
||||
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx)
|
||||
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, "^"+regexp.QuoteMeta(value)+"$").RunStdString(ctx)
|
||||
if err == nil {
|
||||
// already exist
|
||||
return nil
|
||||
@@ -207,18 +212,9 @@ func configAddNonExist(ctx context.Context, key, value string) error {
|
||||
}
|
||||
|
||||
func configUnsetAll(ctx context.Context, key, value string) error {
|
||||
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx)
|
||||
if err == nil {
|
||||
// exist, need to remove
|
||||
_, _, err = gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
||||
}
|
||||
return nil
|
||||
_, _, err := gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, "^"+regexp.QuoteMeta(value)+"$").RunStdString(ctx)
|
||||
if err != nil && !gitcmd.IsErrorExitCode(err, 5) { // 5: no such key or no matching value
|
||||
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
||||
}
|
||||
if gitcmd.IsErrorExitCode(err, 1) {
|
||||
// not exist
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||
return nil
|
||||
}
|
||||
@@ -32,7 +32,9 @@ func TestGitConfig(t *testing.T) {
|
||||
assert.NoError(t, configSetNonExist(ctx, "test.key-a", "val-a-changed"))
|
||||
assert.False(t, gitConfigContains("key-a = val-a-changed"))
|
||||
|
||||
assert.NoError(t, configAddNonExist(ctx, "test.key-a", "val-a2"))
|
||||
assert.NoError(t, configSet(ctx, "test.key-a", "val-a-changed"))
|
||||
assert.NoError(t, configUnsetAll(ctx, "test.key-a", "val-a"))
|
||||
assert.True(t, gitConfigContains("key-a = val-a-changed"))
|
||||
|
||||
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val-b"))
|
||||
@@ -41,6 +43,8 @@ func TestGitConfig(t *testing.T) {
|
||||
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val-2b"))
|
||||
assert.True(t, gitConfigContains("key-b = val-b"))
|
||||
assert.True(t, gitConfigContains("key-b = val-2b"))
|
||||
assert.NoError(t, configAddNonExist(ctx, "test.key-b", "val"))
|
||||
assert.True(t, gitConfigContains("key-b = val\n"))
|
||||
|
||||
assert.NoError(t, configUnsetAll(ctx, "test.key-b", "val-b"))
|
||||
assert.False(t, gitConfigContains("key-b = val-b"))
|
||||
@@ -60,8 +64,10 @@ func TestSyncGitConfig(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.GitConfig)()
|
||||
|
||||
assert.Empty(t, setting.GitConfig.Options)
|
||||
assert.NoError(t, configSet(t.Context(), "gc.reflogExpire", "90"))
|
||||
assert.NoError(t, syncGitConfig(t.Context()))
|
||||
assert.True(t, gitConfigContains("commitGraph = true")) // builtin default config
|
||||
assert.False(t, gitConfigContains("reflogExpire"))
|
||||
|
||||
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
||||
setting.GitConfig.Options["core.commitgraph"] = "false"
|
||||
|
||||
@@ -177,7 +177,7 @@ func initGlobCompiler(g *globCompiler, pattern string, separators []rune) (Glob,
|
||||
|
||||
regex, err := regexp.Compile(g.regexpPattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile regexp: %w", err)
|
||||
return nil, fmt.Errorf("failed to compile glob regexp: %w", err)
|
||||
}
|
||||
|
||||
g.regexp = regex
|
||||
|
||||
@@ -38,14 +38,14 @@ func ReloadTemplates(ctx context.Context) ResponseExtra {
|
||||
|
||||
// FlushOptions represents the options for the flush call
|
||||
type FlushOptions struct {
|
||||
Timeout time.Duration
|
||||
Timeout int64
|
||||
NonBlocking bool
|
||||
}
|
||||
|
||||
// FlushQueues calls the internal flush-queues function
|
||||
func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) ResponseExtra {
|
||||
reqURL := setting.LocalURL + "api/internal/manager/flush-queues"
|
||||
req := newInternalRequestAPI(ctx, reqURL, "POST", FlushOptions{Timeout: timeout, NonBlocking: nonBlocking})
|
||||
req := newInternalRequestAPI(ctx, reqURL, "POST", FlushOptions{Timeout: int64(timeout), NonBlocking: nonBlocking})
|
||||
if timeout > 0 {
|
||||
req.SetReadWriteTimeout(timeout + 10*time.Second)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ func loadGitFrom(rootCfg ConfigProvider) {
|
||||
GitConfig.Options = make(map[string]string)
|
||||
GitConfig.SetOption("diff.algorithm", "histogram")
|
||||
GitConfig.SetOption("core.logAllRefUpdates", "true")
|
||||
GitConfig.SetOption("gc.reflogExpire", "90")
|
||||
|
||||
secGitReflog := rootCfg.Section("git.reflog")
|
||||
if secGitReflog.HasKey("ENABLED") {
|
||||
@@ -97,8 +96,8 @@ func loadGitFrom(rootCfg ConfigProvider) {
|
||||
GitConfig.SetOption("core.logAllRefUpdates", secGitReflog.Key("ENABLED").In("true", []string{"true", "false"}))
|
||||
}
|
||||
if secGitReflog.HasKey("EXPIRATION") {
|
||||
deprecatedSetting(rootCfg, "git.reflog", "EXPIRATION", "git.config", "core.reflogExpire", "1.21")
|
||||
GitConfig.SetOption("gc.reflogExpire", secGitReflog.Key("EXPIRATION").String())
|
||||
deprecatedSetting(rootCfg, "git.reflog", "EXPIRATION", "git.config", "gc.reflogExpire", "1.21")
|
||||
GitConfig.SetOption("gc.reflogExpire", secGitReflog.Key("EXPIRATION").String()+".days")
|
||||
}
|
||||
|
||||
for _, key := range secGitConfig.Keys() {
|
||||
|
||||
@@ -38,8 +38,8 @@ diff.algorithm = other
|
||||
}
|
||||
|
||||
func TestGitReflog(t *testing.T) {
|
||||
defer test.MockVariableValue(&Git)
|
||||
defer test.MockVariableValue(&GitConfig)
|
||||
defer test.MockVariableValue(&Git)()
|
||||
defer test.MockVariableValue(&GitConfig)()
|
||||
|
||||
// default reflog config without legacy options
|
||||
cfg, err := NewConfigProviderFromData(``)
|
||||
@@ -47,7 +47,7 @@ func TestGitReflog(t *testing.T) {
|
||||
loadGitFrom(cfg)
|
||||
|
||||
assert.Equal(t, "true", GitConfig.GetOption("core.logAllRefUpdates"))
|
||||
assert.Equal(t, "90", GitConfig.GetOption("gc.reflogExpire"))
|
||||
assert.Empty(t, GitConfig.GetOption("gc.reflogExpire"))
|
||||
|
||||
// custom reflog config by legacy options
|
||||
cfg, err = NewConfigProviderFromData(`
|
||||
@@ -59,5 +59,5 @@ EXPIRATION = 123
|
||||
loadGitFrom(cfg)
|
||||
|
||||
assert.Equal(t, "false", GitConfig.GetOption("core.logAllRefUpdates"))
|
||||
assert.Equal(t, "123", GitConfig.GetOption("gc.reflogExpire"))
|
||||
assert.Equal(t, "123.days", GitConfig.GetOption("gc.reflogExpire"))
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("repository")
|
||||
Repository.DisableHTTPGit = sec.Key("DISABLE_HTTP_GIT").MustBool()
|
||||
Repository.UseCompatSSHURI = sec.Key("USE_COMPAT_SSH_URI").MustBool()
|
||||
Repository.GoGetCloneURLProtocol = sec.Key("GO_GET_CLONE_URL_PROTOCOL").MustString("https")
|
||||
Repository.GoGetCloneURLProtocol = sec.Key("GO_GET_CLONE_URL_PROTOCOL").String()
|
||||
// MAX_CREATION_LIMIT is a shortcut that sets the default for the two per-type limits below.
|
||||
// USER_/ORG_MAX_CREATION_LIMIT take precedence when explicitly set.
|
||||
Repository.MaxCreationLimit = sec.Key("MAX_CREATION_LIMIT").MustInt(-1) // FIXME: INI-MUST-SIDE-EFFECT
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
|
||||
"gitea.dev/modules/glob"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
type globalVarsStruct struct {
|
||||
@@ -24,6 +22,8 @@ type globalVarsStruct struct {
|
||||
invalidUsernamePattern *regexp.Regexp
|
||||
validBadgeSlugPattern *regexp.Regexp
|
||||
invalidBadgeSlugPattern *regexp.Regexp
|
||||
validEmailHostName *regexp.Regexp
|
||||
validEmailHostIP *regexp.Regexp
|
||||
}
|
||||
|
||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||
@@ -33,6 +33,8 @@ var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||
invalidUsernamePattern: regexp.MustCompile(`[-._]{2,}|[-._]$`), // No consecutive or trailing non-alphanumeric chars
|
||||
validBadgeSlugPattern: regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`),
|
||||
invalidBadgeSlugPattern: regexp.MustCompile(`[-._]{2,}|[-._]$`),
|
||||
validEmailHostName: regexp.MustCompile(`^[a-zA-Z0-9][-.\w]*$`),
|
||||
validEmailHostIP: regexp.MustCompile(`(?i)^\[([0-9.]+|ipv6:[0-9a-f:.]+)\]$`),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -124,10 +126,12 @@ func IsEmailAddressValid(email string) bool {
|
||||
return false
|
||||
}
|
||||
_, domain, _ := strings.Cut(email, "@")
|
||||
if strings.HasPrefix(domain, "[") {
|
||||
// address like "foo@[192.168.1.2]"
|
||||
return true
|
||||
if !globalVars().validEmailHostName.MatchString(domain) && !globalVars().validEmailHostIP.MatchString(domain) {
|
||||
return false
|
||||
}
|
||||
_, err = idna.Registration.ToASCII(domain)
|
||||
return err == nil
|
||||
if strings.HasPrefix(domain, "-") || strings.HasSuffix(domain, "-") ||
|
||||
strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -135,8 +135,8 @@ func shortenFilename(filename, fallback string) string {
|
||||
if filename == "" {
|
||||
return fallback
|
||||
}
|
||||
if lastIndex := strings.LastIndexByte(filename, '/'); lastIndex >= 0 {
|
||||
if secondLastIndex := strings.LastIndexByte(filename[:lastIndex], '/'); secondLastIndex >= 0 {
|
||||
if dir, _, ok := strings.CutLast(filename, "/"); ok {
|
||||
if secondLastIndex := strings.LastIndexByte(dir, '/'); secondLastIndex >= 0 {
|
||||
return filename[secondLastIndex+1:]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,9 +646,10 @@ func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) {
|
||||
Size: artifact.FileSize,
|
||||
}
|
||||
}
|
||||
for _, artifact := range table {
|
||||
if artifact != nil {
|
||||
list = append(list, artifact)
|
||||
for _, artifact := range artifacts {
|
||||
if item := table[artifact.ArtifactName]; item != nil {
|
||||
list = append(list, item)
|
||||
delete(table, artifact.ArtifactName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64, workflowID string)
|
||||
if actor := ctx.FormString("actor"); actor != "" {
|
||||
user, err := user_model.GetUserByName(ctx, actor)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
opts.TriggerUserID = user.ID
|
||||
|
||||
@@ -5,6 +5,7 @@ package private
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/graceful"
|
||||
@@ -34,7 +35,7 @@ func FlushQueues(ctx *context.PrivateContext) {
|
||||
// Save the hammer ctx here - as a new one is created each time you call this.
|
||||
baseCtx := graceful.GetManager().HammerContext()
|
||||
go func() {
|
||||
err := queue.GetManager().FlushAll(baseCtx, opts.Timeout)
|
||||
err := queue.GetManager().FlushAll(baseCtx, time.Duration(opts.Timeout))
|
||||
if err != nil {
|
||||
log.Error("Flushing request timed-out with error: %v", err)
|
||||
}
|
||||
@@ -44,7 +45,7 @@ func FlushQueues(ctx *context.PrivateContext) {
|
||||
})
|
||||
return
|
||||
}
|
||||
err := queue.GetManager().FlushAll(ctx, opts.Timeout)
|
||||
err := queue.GetManager().FlushAll(ctx, time.Duration(opts.Timeout))
|
||||
if err != nil {
|
||||
ctx.PrivateUserErrorf(http.StatusRequestTimeout, "%v", err)
|
||||
return
|
||||
|
||||
@@ -31,7 +31,6 @@ func OIDCWellKnown(ctx *context.Context) {
|
||||
"introspection_endpoint": oidcBaseUrl + "/login/oauth/introspect",
|
||||
"response_types_supported": []string{
|
||||
"code",
|
||||
"id_token",
|
||||
},
|
||||
"id_token_signing_alg_values_supported": []string{
|
||||
oauth2_provider.DefaultSigningKey.SigningMethod().Alg(),
|
||||
|
||||
@@ -69,13 +69,7 @@ func goGet(ctx *context.Context) {
|
||||
|
||||
goGetImport := context.ComposeGoGetImport(ctx, ownerName, trimmedRepoName)
|
||||
|
||||
var cloneURL string
|
||||
if setting.Repository.GoGetCloneURLProtocol == "ssh" {
|
||||
cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, ownerName, repoName)
|
||||
} else {
|
||||
cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, ownerName, repoName)
|
||||
}
|
||||
goImportContent := fmt.Sprintf("%s git %s", goGetImport, cloneURL /*CloneLink*/)
|
||||
goImportContent := fmt.Sprintf("%s git %s", goGetImport, context.ComposeGoGetCloneURL(ctx, ownerName, trimmedRepoName))
|
||||
goSourceContent := fmt.Sprintf("%s _ %s %s", goGetImport, prefix+"{/dir}" /*GoDocDirectory*/, prefix+"{/dir}/{file}#L{line}" /*GoDocFile*/)
|
||||
goGetCli := fmt.Sprintf("go get %s%s", insecure, goGetImport)
|
||||
|
||||
|
||||
@@ -853,7 +853,7 @@ func convertToViewModel(ctx context.Context, locale translation.Locale, cursors
|
||||
}
|
||||
|
||||
for _, cursor := range cursors {
|
||||
if !cursor.Expanded {
|
||||
if !cursor.Expanded || cursor.Step < 0 || cursor.Step >= len(steps) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,13 @@ func TestConvertToViewModel(t *testing.T) {
|
||||
viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, task)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("out of range cursor step", func(t *testing.T) {
|
||||
cursors := []LogCursor{{Step: -1, Expanded: true}, {Step: 3, Expanded: true}}
|
||||
_, logs, err := convertToViewModel(t.Context(), translation.MockLocale{}, cursors, task)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, logs)
|
||||
})
|
||||
|
||||
expectedViewJobs := []*ViewJobStep{
|
||||
{
|
||||
Summary: "Set up job",
|
||||
|
||||
@@ -197,7 +197,6 @@ func verifyAuthWithOptionsWeb(options *common.VerifyOptions) func(ctx *context.C
|
||||
ctx.HTTPError(http.StatusUnauthorized, ctx.Locale.TrString("auth.must_change_password"))
|
||||
return
|
||||
}
|
||||
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/actions/jobparser"
|
||||
"gitea.dev/modules/log"
|
||||
)
|
||||
|
||||
// CancelRun cancels a run's cancellable jobs and returns the run's post-cancellation state.
|
||||
@@ -52,6 +53,12 @@ func cancelRun(ctx context.Context, run *actions_model.ActionRun, jobs []*action
|
||||
if len(updatedJobs) > 0 || reloaded.Status != run.Status {
|
||||
NotifyWorkflowRunStatusUpdate(ctx, reloaded)
|
||||
}
|
||||
if len(updatedJobs) == 0 && reloaded.Status != run.Status {
|
||||
// the run's status was updated, so emit it for the emitter to check
|
||||
if err := EmitJobsIfReadyByRun(run.ID); err != nil {
|
||||
log.Error("Check jobs of run %d: %v", run.ID, err)
|
||||
}
|
||||
}
|
||||
return reloaded, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestForceCancelRun_SettledRunWakesConcurrencyWaiters(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
// a run left cancelling although its caller and child are all cancelled already
|
||||
run := &actions_model.ActionRun{
|
||||
Title: "settled-run",
|
||||
RepoID: 4,
|
||||
Index: 9821,
|
||||
OwnerID: 1,
|
||||
WorkflowID: "test.yaml",
|
||||
TriggerUserID: 1,
|
||||
Ref: "refs/heads/master",
|
||||
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||
Event: "push",
|
||||
TriggerEvent: "push",
|
||||
EventPayload: "{}",
|
||||
Status: actions_model.StatusCancelling,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, run))
|
||||
attempt := &actions_model.ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: actions_model.StatusCancelling}
|
||||
require.NoError(t, db.Insert(ctx, attempt))
|
||||
run.LatestAttemptID = attempt.ID
|
||||
require.NoError(t, actions_model.UpdateRun(ctx, run, "latest_attempt_id"))
|
||||
|
||||
newJob := func(name string, parentID int64, isCaller bool) *actions_model.ActionRunJob {
|
||||
job := &actions_model.ActionRunJob{
|
||||
RunID: run.ID,
|
||||
RunAttemptID: attempt.ID,
|
||||
RepoID: run.RepoID,
|
||||
OwnerID: run.OwnerID,
|
||||
CommitSHA: run.CommitSHA,
|
||||
Name: name,
|
||||
JobID: name,
|
||||
Attempt: 1,
|
||||
Status: actions_model.StatusCancelled,
|
||||
Stopped: timeutil.TimeStampNow(),
|
||||
ParentJobID: parentID,
|
||||
IsReusableCaller: isCaller,
|
||||
IsExpanded: isCaller,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, job))
|
||||
return job
|
||||
}
|
||||
caller := newJob("caller", 0, true)
|
||||
child := newJob("child", caller.ID, false)
|
||||
|
||||
var emitted []int64
|
||||
defer test.MockVariableValue(&EmitJobsIfReadyByRun, func(runID int64) error {
|
||||
emitted = append(emitted, runID)
|
||||
return nil
|
||||
})()
|
||||
|
||||
got, err := ForceCancelRun(ctx, run, []*actions_model.ActionRunJob{caller, child})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, actions_model.StatusCancelled, got.Status)
|
||||
assert.Equal(t, []int64{run.ID}, emitted)
|
||||
}
|
||||
@@ -188,7 +188,10 @@ func insertRunJob(ctx context.Context, run *actions_model.ActionRun, runAttempt
|
||||
if err := workflowJob.SetJob(id, job.EraseNeeds()); err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
payload, _ := workflowJob.Marshal()
|
||||
payload, err := workflowJob.Marshal()
|
||||
if err != nil {
|
||||
return nil, nil, false, fmt.Errorf("insertRunJob: marshal job %q: %w", id, err)
|
||||
}
|
||||
|
||||
isReusableWorkflowCaller := job.Uses != ""
|
||||
status := util.Iif(runAttempt.Status == actions_model.StatusBlocked || run.NeedApproval, actions_model.StatusBlocked, actions_model.StatusWaiting)
|
||||
|
||||
@@ -389,6 +389,15 @@ func ComposeGoGetImport(ctx context.Context, owner, repo string) string {
|
||||
return path.Join(curAppURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
|
||||
}
|
||||
|
||||
// ComposeGoGetCloneURL returns the clone URL for the go-import meta content.
|
||||
func ComposeGoGetCloneURL(ctx *Context, owner, repo string) string {
|
||||
useSSH := setting.Repository.GoGetCloneURLProtocol == "ssh" || (setting.Repository.DisableHTTPGit && !setting.SSH.Disabled)
|
||||
if useSSH {
|
||||
return repo_model.ComposeSSHCloneURI(ctx.Doer, owner, repo)
|
||||
}
|
||||
return repo_model.ComposeHTTPSCloneURL(ctx, owner, repo)
|
||||
}
|
||||
|
||||
// EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
|
||||
// if user does not have actual access to the requested repository,
|
||||
// or the owner or repository does not exist at all.
|
||||
@@ -402,13 +411,7 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var cloneURL string
|
||||
if setting.Repository.GoGetCloneURLProtocol == "ssh" {
|
||||
cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, username, reponame)
|
||||
} else {
|
||||
cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, username, reponame)
|
||||
}
|
||||
goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), cloneURL)
|
||||
goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), ComposeGoGetCloneURL(ctx, username, reponame))
|
||||
htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
|
||||
ctx.PlainText(http.StatusOK, htmlMeta)
|
||||
}
|
||||
|
||||
@@ -65,6 +65,6 @@ type AdminEditUserForm struct {
|
||||
// AdminDashboardForm form for admin dashboard operations
|
||||
type AdminDashboardForm struct {
|
||||
middleware.FormDefaultValidator
|
||||
Op string `binding:"required"`
|
||||
Op string `binding:"Required"`
|
||||
From string
|
||||
}
|
||||
@@ -571,12 +571,6 @@ type AddTimeManuallyForm struct {
|
||||
Minutes int `binding:"Range(0,1000)"`
|
||||
}
|
||||
|
||||
// SaveTopicForm form for save topics for repository
|
||||
type SaveTopicForm struct {
|
||||
middleware.FormDefaultValidator
|
||||
Topics []string `binding:"topics;Required;"`
|
||||
}
|
||||
|
||||
// AddDeployTokenForm form for adding a deploy token to a repository
|
||||
type AddDeployTokenForm struct {
|
||||
middleware.FormDefaultValidator
|
||||
|
||||
@@ -201,9 +201,12 @@ func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload
|
||||
|
||||
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
|
||||
// https://core.telegram.org/bots/api#formatting-options
|
||||
sanitized := strings.TrimSpace(string(markup.Sanitize(msgHTML)))
|
||||
// Rich messages collapse bare newlines like HTML, so keep line breaks as <br>.
|
||||
sanitized = strings.NewReplacer("\r\n", "<br>", "\r", "<br>", "\n", "<br>").Replace(sanitized)
|
||||
return TelegramPayload{
|
||||
RichMessage: InputRichMessage{
|
||||
HTML: strings.TrimSpace(string(markup.Sanitize(msgHTML))),
|
||||
HTML: sanitized,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,12 @@ func TestTelegramPayload(t *testing.T) {
|
||||
}, p)
|
||||
})
|
||||
|
||||
t.Run("Line breaks are kept in rich messages", func(t *testing.T) {
|
||||
// Rich messages collapse bare newlines, so they must become <br>.
|
||||
p := createTelegramPayloadHTML("first line\r\nsecond line\nthird line")
|
||||
assert.Equal(t, "first line<br>second line<br>third line", p.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
p := createTestPayload()
|
||||
|
||||
@@ -60,9 +66,7 @@ func TestTelegramPayload(t *testing.T) {
|
||||
pl, err := tc.Push(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, pl.RichMessage.HTML)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits<br>[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1<br>[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Issue", func(t *testing.T) {
|
||||
@@ -72,9 +76,7 @@ func TestTelegramPayload(t *testing.T) {
|
||||
pl, err := tc.Issue(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue opened: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
|
||||
issue body`, pl.RichMessage.HTML)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue opened: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a><br><br>issue body`, pl.RichMessage.HTML)
|
||||
|
||||
p.Action = api.HookIssueClosed
|
||||
pl, err = tc.Issue(p)
|
||||
@@ -89,8 +91,7 @@ issue body`, pl.RichMessage.HTML)
|
||||
pl, err := tc.IssueComment(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on issue <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
more info needed`, pl.RichMessage.HTML)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on issue <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a><br>more info needed`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("PullRequest", func(t *testing.T) {
|
||||
@@ -99,8 +100,7 @@ more info needed`, pl.RichMessage.HTML)
|
||||
pl, err := tc.PullRequest(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Pull request opened: <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
fixes bug #2`, pl.RichMessage.HTML)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Pull request opened: <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a><br>fixes bug #2`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("PullRequestComment", func(t *testing.T) {
|
||||
@@ -109,8 +109,7 @@ fixes bug #2`, pl.RichMessage.HTML)
|
||||
pl, err := tc.IssueComment(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on pull request <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
changes requested`, pl.RichMessage.HTML)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on pull request <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a><br>changes requested`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Review", func(t *testing.T) {
|
||||
@@ -120,8 +119,7 @@ changes requested`, pl.RichMessage.HTML)
|
||||
pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
|
||||
good job`, pl.RichMessage.HTML)
|
||||
assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug<br>good job`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Repository", func(t *testing.T) {
|
||||
@@ -206,7 +204,5 @@ func TestTelegramJSONPayload(t *testing.T) {
|
||||
var body TelegramPayload
|
||||
err = json.NewDecoder(req.Body).Decode(&body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, body.RichMessage.HTML)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits<br>[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1<br>[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, body.RichMessage.HTML)
|
||||
}
|
||||
@@ -16,13 +16,14 @@ else
|
||||
fi
|
||||
|
||||
# How it works:
|
||||
# * snapcraft.io checks out the default branch (e.g.: main during 1.27 dev period)
|
||||
# * release-nightly-snapcraft.yml builds the snap on every push to the default branch (e.g.: main during 1.27 dev period)
|
||||
# * "override-pull" step gets the latest tag by date (e.g.: v1.26.1)
|
||||
# * use "snap info gitea" to get the latest released tag
|
||||
# * if the latest tag is not released to stable, checkout that tag and build it for "stable"
|
||||
# * otherwise, build the main branch for "devel"
|
||||
# * use "snap info gitea" to get the latest released tag, which is the version in the "latest/candidate" channel
|
||||
# * if the latest tag is not released yet, checkout that tag and build it with grade "stable",
|
||||
# the workflow publishes it to "latest/stable" and "latest/candidate"
|
||||
# * otherwise, build the main branch with grade "devel", the workflow publishes it to "latest/edge"
|
||||
# * "override-build" step uses build script from the checked out commit to build
|
||||
# This approach highly depends on the "main" branch's push.
|
||||
# This approach highly depends on the "main" branch's push: a new tag is only published by the next push after it.
|
||||
|
||||
# To debug the logic:
|
||||
# * last_committed_tag=v1.26.1 last_released_tag=v1.26.0 ./snap/part-gitea-pull.sh
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
<div class="navbar-right">
|
||||
{{if and .IsSigned .MustChangePassword}}
|
||||
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
|
||||
<span class="text">
|
||||
{{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
|
||||
<span class="flex-text-block">
|
||||
{{ctx.AvatarUtils.Avatar .SignedUser 24}}
|
||||
<span class="only-mobile">{{.SignedUser.Name}}</span>
|
||||
<span class="not-mobile flex-text-block">{{svg "octicon-triangle-down"}}</span>
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{- $itemExtraClass := .ItemExtraClass -}}
|
||||
{{- $data := .PageGlobalData -}}
|
||||
{{if and $data $data.IsSigned}}{{/* data may not exist, for example: rendering 503 page before the PageGlobalData middleware */}}
|
||||
{{if and $data $data.IsSigned (not ctx.RootData.MustChangePassword)}}{{/* data may not exist, for example: rendering 503 page before the PageGlobalData middleware */}}
|
||||
{{- $activeStopwatch := call $data.GetActiveStopwatch -}}
|
||||
{{- $notificationUnreadCount := call $data.GetNotificationUnreadCount -}}
|
||||
{{/* always rendered so a real-time push can reveal it without a reload */}}
|
||||
|
||||
@@ -682,7 +682,7 @@ jobs:
|
||||
taskToken3 := task3.Context.GetFields()["gitea_runtime_token"].GetStringValue()
|
||||
|
||||
// the new attempt inherits what the previous attempt uploaded
|
||||
assert.ElementsMatch(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
|
||||
assert.Equal(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
|
||||
assert.Equal(t, strings.Repeat("A", 32), downloadArtifactContentV4ByTask(t, run.ID, job3.ID, taskToken3, "job1-only"))
|
||||
assert.Contains(t, listArtifactNamesForRun(t, run.ID, taskToken3), "job1-v3")
|
||||
|
||||
@@ -699,7 +699,7 @@ jobs:
|
||||
inheritedSharedID := listArtifactIDForRunV4(t, run.ID, job3.ID, taskToken3, "job1-shared")
|
||||
require.Len(t, listArtifactsByIDV4(t, run.ID, job3.ID, inheritedSharedID, taskToken3), 1)
|
||||
uploadTestArtifactFileV4(t, run.ID, job3.ID, taskToken3, "job1-shared", strings.Repeat("C", 32))
|
||||
assert.ElementsMatch(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
|
||||
assert.Equal(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
|
||||
assert.Equal(t, strings.Repeat("C", 32), downloadArtifactContentV4ByTask(t, run.ID, job3.ID, taskToken3, "job1-shared"))
|
||||
|
||||
// a shadowed artifact is not listed by id either: a download resolves by name
|
||||
|
||||
@@ -899,7 +899,7 @@ func testActionRunAttemptArtifactV4(t *testing.T, repo *repo_model.Repository, s
|
||||
uploadTestArtifactFileV4(t, run.ID, job1.ID, taskToken1, "artifact-attempt-1", strings.Repeat("A", 32))
|
||||
uploadTestArtifactFileV4(t, run.ID, job1.ID, taskToken1, "artifact-shared", strings.Repeat("C", 32))
|
||||
attempt1Names := listArtifactNamesForRunV4(t, run.ID, job1.ID, taskToken1)
|
||||
assert.ElementsMatch(t, []string{"artifact-attempt-1", "artifact-shared"}, attempt1Names)
|
||||
assert.Equal(t, []string{"artifact-attempt-1", "artifact-shared"}, attempt1Names)
|
||||
|
||||
runner.execTask(t, task1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
|
||||
@@ -915,7 +915,7 @@ func testActionRunAttemptArtifactV4(t *testing.T, repo *repo_model.Repository, s
|
||||
uploadTestArtifactFileV4(t, run.ID, job2.ID, taskToken2, "artifact-attempt-2", strings.Repeat("B", 32))
|
||||
uploadTestArtifactFileV4(t, run.ID, job2.ID, taskToken2, "artifact-shared", strings.Repeat("D", 32))
|
||||
attempt2Names := listArtifactNamesForRunV4(t, run.ID, job2.ID, taskToken2)
|
||||
assert.ElementsMatch(t, []string{"artifact-attempt-2", "artifact-shared"}, attempt2Names)
|
||||
assert.Equal(t, []string{"artifact-attempt-2", "artifact-shared"}, attempt2Names)
|
||||
assert.NotContains(t, attempt2Names, "artifact-attempt-1")
|
||||
|
||||
// "artifact-attempt-1" belongs to the first attempt, so the rerun token cannot access it
|
||||
|
||||
@@ -69,6 +69,12 @@ func testAPIActionsGetWorkflowRun(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("ListRunsUnknownActor", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs?actor=no-such-user", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("GetJobSteps", func(t *testing.T) {
|
||||
// Insert task steps for task_id 53 (job 198) so the API can return them once the backend loads them
|
||||
_, err := db.GetEngine(t.Context()).Insert(&actions_model.ActionTaskStep{
|
||||
|
||||
@@ -58,6 +58,11 @@ func TestGoGetForSSH(t *testing.T) {
|
||||
</html>`, setting.AppDomain, setting.HTTPPort, setting.AppURL, setting.SSH.Domain, setting.SSH.Port)
|
||||
|
||||
assert.Equal(t, expected, resp.Body.String())
|
||||
|
||||
// go rejects scp-style addresses, so the standard port must still produce an ssh:// URL
|
||||
defer test.MockVariableValue(&setting.SSH.Port, 22)()
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
assert.Contains(t, resp.Body.String(), fmt.Sprintf(`git ssh://git@%s/blah/glah.git">`, setting.SSH.Domain))
|
||||
}
|
||||
|
||||
// TestGoGetPrivateRepoBranchNotLeaked ensures the go-get meta endpoint does not disclose a
|
||||
|
||||
@@ -108,6 +108,7 @@ func TestOAuth2(t *testing.T) {
|
||||
t.Run("AuthorizeNoClientID", testAuthorizeNoClientID)
|
||||
t.Run("AuthorizeUnregisteredRedirect", testAuthorizeUnregisteredRedirect)
|
||||
t.Run("AuthorizeUnsupportedResponseType", testAuthorizeUnsupportedResponseType)
|
||||
t.Run("AuthorizeUnsupportedResponseTypeIDToken", testAuthorizeUnsupportedResponseTypeIDToken)
|
||||
t.Run("AuthorizeUnsupportedCodeChallengeMethod", testAuthorizeUnsupportedCodeChallengeMethod)
|
||||
t.Run("AuthorizeLoginRedirect", testAuthorizeLoginRedirect)
|
||||
t.Run("AuthorizeShow", testAuthorizeShow)
|
||||
@@ -163,6 +164,16 @@ func testAuthorizeUnsupportedResponseType(t *testing.T) {
|
||||
assert.Equal(t, "Only code response type is supported.", u.Query().Get("error_description"))
|
||||
}
|
||||
|
||||
func testAuthorizeUnsupportedResponseTypeIDToken(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https://example.com&response_type=id_token&state=thestate")
|
||||
ctx := loginUser(t, "user1")
|
||||
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
||||
u, err := resp.Result().Location()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unsupported_response_type", u.Query().Get("error"))
|
||||
assert.Equal(t, "Only code response type is supported.", u.Query().Get("error_description"))
|
||||
}
|
||||
|
||||
func testAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https://example.com&response_type=code&state=thestate&code_challenge_method=UNEXPECTED")
|
||||
ctx := loginUser(t, "user1")
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Prints the revert commits in the given range and the commits they revert, for git-cliff's .cliffignore
|
||||
range="${1:?usage: $0 <revision-range>}"
|
||||
commits=$(git log --format='%H %s' "$range")
|
||||
|
||||
shopt -s nocasematch
|
||||
while read -r sha subject; do
|
||||
[[ "$subject" == revert* ]] || continue
|
||||
echo "$sha"
|
||||
for pr in $(git show -s --format=%B "$sha" | grep -i revert | grep -oE '(#|/pull/)[0-9]+' | grep -oE '[0-9]+' || true); do
|
||||
grep -F "(#$pr)" <<< "$commits" | cut -d' ' -f1 || true
|
||||
done
|
||||
done <<< "$commits"
|
||||
@@ -125,7 +125,8 @@
|
||||
.markup ol,
|
||||
.markup dl,
|
||||
.markup table,
|
||||
.markup pre {
|
||||
.markup pre,
|
||||
.markup math[display="block" i] {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user