mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into sync-issue-pr-and-more
This commit is contained in:
commit
71dfb6778c
|
@ -1,54 +0,0 @@
|
|||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 14
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- status/blocked
|
||||
- kind/security
|
||||
- lgtm/done
|
||||
- reviewed/confirmed
|
||||
- priority/critical
|
||||
- kind/proposal
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had recent activity.
|
||||
I am here to help clear issues left open even if solved or waiting for more insight.
|
||||
This issue will be closed if no further activity occurs during the next 2 weeks.
|
||||
If the issue is still valid just add a comment to keep it alive.
|
||||
Thank you for your contributions.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
closeComment: >
|
||||
This issue has been automatically closed because of inactivity.
|
||||
You can re-open it if needed.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 1
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
pulls:
|
||||
daysUntilStale: 60
|
||||
daysUntilClose: 60
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs during the next 2 months. Thank you
|
||||
for your contributions.
|
||||
closeComment: >
|
||||
This pull request has been automatically closed because of inactivity.
|
||||
You can re-open it if needed.
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
if: github.repository == 'go-gitea/gitea'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
|
|
@ -17,6 +17,6 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'go-gitea/gitea'
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
issue-inactive-days: 45
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -70,7 +70,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -87,7 +87,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -102,7 +102,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -130,7 +130,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -175,7 +175,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
- "9000:9000"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -115,7 +115,7 @@ jobs:
|
|||
- "9000:9000"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -165,7 +165,7 @@ jobs:
|
|||
- "993:993"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -198,7 +198,7 @@ jobs:
|
|||
- "1433:1433"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
@ -101,7 +101,7 @@ jobs:
|
|||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
commands-show-output: false
|
||||
fenced-code-language: false
|
||||
first-line-h1: false
|
||||
header-increment: false
|
||||
heading-increment: false
|
||||
line-length: {code_blocks: false, tables: false, stern: true, line_length: -1}
|
||||
no-alt-text: false
|
||||
no-bare-urls: false
|
||||
no-blanks-blockquote: false
|
||||
no-emphasis-as-header: false
|
||||
no-emphasis-as-heading: false
|
||||
no-empty-links: false
|
||||
no-hard-tabs: {code_blocks: false}
|
||||
no-inline-html: false
|
||||
|
|
|
@ -74,7 +74,7 @@ rules:
|
|||
keyframe-declaration-no-important: true
|
||||
keyframe-selector-notation: null
|
||||
keyframes-name-pattern: null
|
||||
length-zero-no-unit: true
|
||||
length-zero-no-unit: [true, ignore: [custom-properties], ignoreFunctions: [var]]
|
||||
max-nesting-depth: null
|
||||
media-feature-name-allowed-list: null
|
||||
media-feature-name-disallowed-list: null
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Build stage
|
||||
FROM docker.io/library/golang:1.21-alpine3.18 AS build-env
|
||||
FROM docker.io/library/golang:1.21-alpine3.19 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
|
@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
|||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM docker.io/library/alpine:3.18
|
||||
FROM docker.io/library/alpine:3.19
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 22 3000
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Build stage
|
||||
FROM docker.io/library/golang:1.21-alpine3.18 AS build-env
|
||||
FROM docker.io/library/golang:1.21-alpine3.19 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
|
@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
|||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM docker.io/library/alpine:3.18
|
||||
FROM docker.io/library/alpine:3.19
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
|
|
@ -62,11 +62,16 @@ painless way of setting up a self-hosted Git service.
|
|||
As Gitea is written in Go, it works across **all** the platforms and
|
||||
architectures that are supported by Go, including Linux, macOS, and
|
||||
Windows on x86, amd64, ARM and PowerPC architectures.
|
||||
You can try it out using [the online demo](https://try.gitea.io/).
|
||||
This project has been
|
||||
[forked](https://blog.gitea.com/welcome-to-gitea/) from
|
||||
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
|
||||
|
||||
For online demonstrations, you can visit [try.gitea.io](https://try.gitea.io).
|
||||
|
||||
For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
|
||||
|
||||
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
|
||||
|
||||
## Building
|
||||
|
||||
From the root of the source tree, run:
|
||||
|
|
|
@ -58,7 +58,11 @@
|
|||
|
||||
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86,amd64,还包括 ARM 和 PowerPC。
|
||||
|
||||
如果您想试用一下,请访问 [在线Demo](https://try.gitea.io/)!
|
||||
如果你想试用在线演示,请访问 [try.gitea.io](https://try.gitea.io/)。
|
||||
|
||||
如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。
|
||||
|
||||
如果你想在 Gitea Cloud 上快速部署你自己独享的 Gitea 实例,请访问 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
|
||||
|
||||
## 提示
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -15,9 +15,8 @@ import (
|
|||
var (
|
||||
// CmdActions represents the available actions sub-commands.
|
||||
CmdActions = &cli.Command{
|
||||
Name: "actions",
|
||||
Usage: "",
|
||||
Description: "Commands for managing Gitea Actions",
|
||||
Name: "actions",
|
||||
Usage: "Manage Gitea Actions",
|
||||
Subcommands: []*cli.Command{
|
||||
subcmdActionsGenRunnerToken,
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@ var (
|
|||
// CmdAdmin represents the available admin sub-command.
|
||||
CmdAdmin = &cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Command line interface to perform common administrative operations",
|
||||
Usage: "Perform common administrative operations",
|
||||
Subcommands: []*cli.Command{
|
||||
subcmdUser,
|
||||
subcmdRepoSyncReleases,
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/migrations"
|
||||
migrate_base "code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/doctor"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -22,6 +23,19 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// CmdDoctor represents the available doctor sub-command.
|
||||
var CmdDoctor = &cli.Command{
|
||||
Name: "doctor",
|
||||
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
|
||||
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||
|
||||
Subcommands: []*cli.Command{
|
||||
cmdDoctorCheck,
|
||||
cmdRecreateTable,
|
||||
cmdDoctorConvert,
|
||||
},
|
||||
}
|
||||
|
||||
var cmdDoctorCheck = &cli.Command{
|
||||
Name: "check",
|
||||
Usage: "Diagnose and optionally fix problems",
|
||||
|
@ -60,19 +74,6 @@ var cmdDoctorCheck = &cli.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// CmdDoctor represents the available doctor sub-command.
|
||||
var CmdDoctor = &cli.Command{
|
||||
Name: "doctor",
|
||||
Usage: "Diagnose and optionally fix problems",
|
||||
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||
|
||||
Subcommands: []*cli.Command{
|
||||
cmdDoctorCheck,
|
||||
cmdRecreateTable,
|
||||
cmdDoctorConvert,
|
||||
},
|
||||
}
|
||||
|
||||
var cmdRecreateTable = &cli.Command{
|
||||
Name: "recreate-table",
|
||||
Usage: "Recreate tables from XORM definitions and copy the data.",
|
||||
|
@ -177,6 +178,7 @@ func runDoctorCheck(ctx *cli.Context) error {
|
|||
if ctx.IsSet("list") {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
||||
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
|
||||
doctor.SortChecks(doctor.Checks)
|
||||
for _, check := range doctor.Checks {
|
||||
if check.IsDefault {
|
||||
_, _ = w.Write([]byte{'*'})
|
||||
|
@ -192,26 +194,20 @@ func runDoctorCheck(ctx *cli.Context) error {
|
|||
|
||||
var checks []*doctor.Check
|
||||
if ctx.Bool("all") {
|
||||
checks = doctor.Checks
|
||||
checks = make([]*doctor.Check, len(doctor.Checks))
|
||||
copy(checks, doctor.Checks)
|
||||
} else if ctx.IsSet("run") {
|
||||
addDefault := ctx.Bool("default")
|
||||
names := ctx.StringSlice("run")
|
||||
for i, name := range names {
|
||||
names[i] = strings.ToLower(strings.TrimSpace(name))
|
||||
}
|
||||
|
||||
runNamesSet := container.SetOf(ctx.StringSlice("run")...)
|
||||
for _, check := range doctor.Checks {
|
||||
if addDefault && check.IsDefault {
|
||||
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
|
||||
checks = append(checks, check)
|
||||
continue
|
||||
}
|
||||
for _, name := range names {
|
||||
if name == check.Name {
|
||||
checks = append(checks, check)
|
||||
break
|
||||
}
|
||||
runNamesSet.Remove(check.Name)
|
||||
}
|
||||
}
|
||||
if len(runNamesSet) > 0 {
|
||||
return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
|
||||
}
|
||||
} else {
|
||||
for _, check := range doctor.Checks {
|
||||
if check.IsDefault {
|
||||
|
@ -219,6 +215,5 @@ func runDoctorCheck(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/doctor"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestDoctorRun(t *testing.T) {
|
||||
doctor.Register(&doctor.Check{
|
||||
Title: "Test Check",
|
||||
Name: "test-check",
|
||||
Run: func(ctx context.Context, logger log.Logger, autofix bool) error { return nil },
|
||||
|
||||
SkipDatabaseInitialization: true,
|
||||
})
|
||||
app := cli.NewApp()
|
||||
app.Commands = []*cli.Command{cmdDoctorCheck}
|
||||
err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
|
||||
assert.NoError(t, err)
|
||||
err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
|
||||
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
||||
err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
|
||||
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
|
||||
}
|
|
@ -18,7 +18,7 @@ var (
|
|||
// CmdGenerate represents the available generate sub-command.
|
||||
CmdGenerate = &cli.Command{
|
||||
Name: "generate",
|
||||
Usage: "Command line interface for running generators",
|
||||
Usage: "Generate Gitea's secrets/keys/tokens",
|
||||
Subcommands: []*cli.Command{
|
||||
subcmdSecret,
|
||||
},
|
||||
|
|
11
cmd/hook.go
11
cmd/hook.go
|
@ -31,8 +31,8 @@ var (
|
|||
// CmdHook represents the available hooks sub-command.
|
||||
CmdHook = &cli.Command{
|
||||
Name: "hook",
|
||||
Usage: "Delegate commands to corresponding Git hooks",
|
||||
Description: "This should only be called by Git",
|
||||
Usage: "(internal) Should only be called by Git",
|
||||
Description: "Delegate commands to corresponding Git hooks",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Subcommands: []*cli.Command{
|
||||
subcmdHookPreReceive,
|
||||
|
@ -376,7 +376,9 @@ Gitea or set your environment appropriately.`, "")
|
|||
oldCommitIDs[count] = string(fields[0])
|
||||
newCommitIDs[count] = string(fields[1])
|
||||
refFullNames[count] = git.RefName(fields[2])
|
||||
if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
|
||||
|
||||
commitID, _ := git.NewIDFromString(newCommitIDs[count])
|
||||
if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
|
||||
masterPushed = true
|
||||
}
|
||||
count++
|
||||
|
@ -669,7 +671,8 @@ Gitea or set your environment appropriately.`, "")
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.OldOID != git.EmptySHA {
|
||||
commitID, _ := git.NewIDFromString(rs.OldOID)
|
||||
if !commitID.IsZero() {
|
||||
err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID))
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -16,10 +16,11 @@ import (
|
|||
|
||||
// CmdKeys represents the available keys sub-command
|
||||
var CmdKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Action: runKeys,
|
||||
Name: "keys",
|
||||
Usage: "(internal) Should only be called by SSH server",
|
||||
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Action: runKeys,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "expected",
|
||||
|
|
21
cmd/main.go
21
cmd/main.go
|
@ -10,12 +10,12 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// cmdHelp is our own help subcommand with more information
|
||||
// Keep in mind that the "./gitea help"(subcommand) is different from "./gitea --help"(flag), the flag doesn't parse the config or output "DEFAULT CONFIGURATION:" information
|
||||
func cmdHelp() *cli.Command {
|
||||
c := &cli.Command{
|
||||
Name: "help",
|
||||
|
@ -47,16 +47,10 @@ DEFAULT CONFIGURATION:
|
|||
return c
|
||||
}
|
||||
|
||||
var helpFlag = cli.HelpFlag
|
||||
|
||||
func init() {
|
||||
// cli.HelpFlag = nil TODO: after https://github.com/urfave/cli/issues/1794 we can use this
|
||||
}
|
||||
|
||||
func appGlobalFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
// make the builtin flags at the top
|
||||
helpFlag,
|
||||
cli.HelpFlag,
|
||||
|
||||
// shared configuration flags, they are for global and for each sub-command at the same time
|
||||
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
|
||||
|
@ -121,20 +115,22 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
|
|||
func NewMainApp(version, versionExtra string) *cli.App {
|
||||
app := cli.NewApp()
|
||||
app.Name = "Gitea"
|
||||
app.HelpName = "gitea"
|
||||
app.Usage = "A painless self-hosted Git service"
|
||||
app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
|
||||
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
|
||||
app.Version = version + versionExtra
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
// these sub-commands need to use config file
|
||||
subCmdWithConfig := []*cli.Command{
|
||||
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
|
||||
CmdWeb,
|
||||
CmdServ,
|
||||
CmdHook,
|
||||
CmdKeys,
|
||||
CmdDump,
|
||||
CmdAdmin,
|
||||
CmdMigrate,
|
||||
CmdKeys,
|
||||
CmdDoctor,
|
||||
CmdManager,
|
||||
CmdEmbedded,
|
||||
|
@ -142,13 +138,8 @@ func NewMainApp(version, versionExtra string) *cli.App {
|
|||
CmdDumpRepository,
|
||||
CmdRestoreRepository,
|
||||
CmdActions,
|
||||
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
|
||||
}
|
||||
|
||||
cmdConvert := util.ToPointer(*cmdDoctorConvert)
|
||||
cmdConvert.Hidden = true // still support the legacy "./gitea doctor" by the hidden sub-command, remove it in next release
|
||||
subCmdWithConfig = append(subCmdWithConfig, cmdConvert)
|
||||
|
||||
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
|
||||
subCmdStandalone := []*cli.Command{
|
||||
CmdCert,
|
||||
|
|
|
@ -42,7 +42,7 @@ const (
|
|||
// CmdServ represents the available serv sub-command.
|
||||
var CmdServ = &cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Usage: "(internal) Should only be called by SSH shell",
|
||||
Description: "Serv provides access auth for repositories",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Action: runServ,
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/go-github/v53/github"
|
||||
"github.com/google/go-github/v57/github"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
|
|
@ -234,7 +234,7 @@ RUN_USER = ; git
|
|||
;MINIMUM_KEY_SIZE_CHECK = false
|
||||
;;
|
||||
;; Disable CDN even in "prod" mode
|
||||
;OFFLINE_MODE = false
|
||||
;OFFLINE_MODE = true
|
||||
;;
|
||||
;; TLS Settings: Either ACME or manual
|
||||
;; (Other common TLS configuration are found before)
|
||||
|
@ -492,6 +492,11 @@ INTERNAL_TOKEN=
|
|||
;; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations.
|
||||
;; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
|
||||
;SUCCESSFUL_TOKENS_CACHE_SIZE = 20
|
||||
;;
|
||||
;; Reject API tokens sent in URL query string (Accept Header-based API tokens only). This avoids security vulnerabilities
|
||||
;; stemming from cached/logged plain-text API tokens.
|
||||
;; In future releases, this will become the default behavior
|
||||
;DISABLE_QUERY_AUTH_TOKEN = false
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1153,15 +1158,9 @@ LEVEL = Info
|
|||
;; enable cors headers (disabled by default)
|
||||
;ENABLED = false
|
||||
;;
|
||||
;; scheme of allowed requests
|
||||
;SCHEME = http
|
||||
;;
|
||||
;; list of requesting domains that are allowed
|
||||
;; list of requesting origins that are allowed, eg: "https://*.example.com"
|
||||
;ALLOW_DOMAIN = *
|
||||
;;
|
||||
;; allow subdomains of headers listed above to request
|
||||
;ALLOW_SUBDOMAIN = false
|
||||
;;
|
||||
;; list of methods allowed to request
|
||||
;METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
|
||||
;;
|
||||
|
@ -1207,6 +1206,9 @@ LEVEL = Info
|
|||
;; Max size of files to be displayed (default is 8MiB)
|
||||
;MAX_DISPLAY_FILE_SIZE = 8388608
|
||||
;;
|
||||
;; Detect ambiguous unicode characters in file contents and show warnings on the UI
|
||||
;AMBIGUOUS_UNICODE_DETECTION = true
|
||||
;;
|
||||
;; Whether the email of the user should be shown in the Explore Users page
|
||||
;SHOW_USER_EMAIL = true
|
||||
;;
|
||||
|
@ -1242,6 +1244,10 @@ LEVEL = Info
|
|||
;; Change the sort type of the explore pages.
|
||||
;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest".
|
||||
;EXPLORE_PAGING_DEFAULT_SORT = recentupdate
|
||||
;;
|
||||
;; The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`.
|
||||
;; `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago).
|
||||
;PREFERRED_TIMESTAMP_TENSE = mixed
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1697,9 +1703,6 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; if the cache enabled
|
||||
;ENABLED = true
|
||||
;;
|
||||
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
|
||||
;ADAPTER = memory
|
||||
;;
|
||||
|
@ -1724,8 +1727,6 @@ LEVEL = Info
|
|||
;[cache.last_commit]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; if the cache enabled
|
||||
;ENABLED = true
|
||||
;;
|
||||
;; Time to keep items in cache if not used, default is 8760 hours.
|
||||
;; Setting it to -1 disables caching
|
||||
|
@ -2575,7 +2576,7 @@ LEVEL = Info
|
|||
;;
|
||||
;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
|
||||
;DEFAULT_ACTIONS_URL = github
|
||||
;; Default artifact retention time in days, default is 90 days
|
||||
;; Default artifact retention time in days. Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
|
||||
;ARTIFACT_RETENTION_DAYS = 90
|
||||
;; Timeout to stop the task which have running status, but haven't been updated for a long time
|
||||
;ZOMBIE_TASK_TIMEOUT = 10m
|
||||
|
|
|
@ -19,10 +19,10 @@ Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Poli
|
|||
|
||||
## Getting Pages
|
||||
|
||||
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/`. For example, to add Privacy Policy:
|
||||
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/assets/`. For example, to add Privacy Policy:
|
||||
|
||||
```
|
||||
wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
|
||||
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
|
||||
```
|
||||
|
||||
Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation.
|
||||
|
|
|
@ -19,10 +19,10 @@ menu:
|
|||
|
||||
## 获取页面
|
||||
|
||||
Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/` 目录下。例如,如果要添加隐私政策:
|
||||
Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/assets/` 目录下。例如,如果要添加隐私政策:
|
||||
|
||||
```
|
||||
wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
|
||||
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
|
||||
```
|
||||
|
||||
现在,你需要编辑该页面以满足你的需求。特别是,你必须更改电子邮件地址、网址以及与 "Your Gitea Instance" 相关的引用,以匹配你的情况。
|
||||
|
|
|
@ -196,9 +196,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
|||
## CORS (`cors`)
|
||||
|
||||
- `ENABLED`: **false**: enable cors headers (disabled by default)
|
||||
- `SCHEME`: **http**: scheme of allowed requests
|
||||
- `ALLOW_DOMAIN`: **\***: list of requesting domains that are allowed
|
||||
- `ALLOW_SUBDOMAIN`: **false**: allow subdomains of headers listed above to request
|
||||
- `ALLOW_DOMAIN`: **\***: list of requesting origins that are allowed, eg: "https://*.example.com"
|
||||
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: list of methods allowed to request
|
||||
- `MAX_AGE`: **10m**: max time to cache response
|
||||
- `ALLOW_CREDENTIALS`: **false**: allow request with credentials
|
||||
|
@ -220,6 +218,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
|||
- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: All available themes. Allow users select personalized themes.
|
||||
regardless of the value of `DEFAULT_THEME`.
|
||||
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
|
||||
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
|
||||
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
|
||||
Values can be emoji alias (:smile:) or a unicode emoji.
|
||||
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
||||
|
@ -232,6 +231,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
|||
- `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
|
||||
A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).
|
||||
- `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest"
|
||||
- `PREFERRED_TIMESTAMP_TENSE`: **mixed**: The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`. `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago).
|
||||
|
||||
### UI - Admin (`ui.admin`)
|
||||
|
||||
|
@ -356,7 +356,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
|||
- `SSH_PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to SSH connections.
|
||||
- `MINIMUM_KEY_SIZE_CHECK`: **true**: Indicate whether to check minimum key size with corresponding type.
|
||||
|
||||
- `OFFLINE_MODE`: **false**: Disables use of CDN for static files and Gravatar for profile pictures.
|
||||
- `OFFLINE_MODE`: **true**: Disables use of CDN for static files and Gravatar for profile pictures.
|
||||
- `CERT_FILE`: **https/cert.pem**: Cert file path used for HTTPS. When chaining, the server certificate must come first, then intermediate CA certificates (if any). This is ignored if `ENABLE_ACME=true`. Paths are relative to `CUSTOM_PATH`.
|
||||
- `KEY_FILE`: **https/key.pem**: Key file path used for HTTPS. This is ignored if `ENABLE_ACME=true`. Paths are relative to `CUSTOM_PATH`.
|
||||
- `STATIC_ROOT_PATH`: **_`StaticRootPath`_**: Upper level of template and static files path.
|
||||
|
@ -572,6 +572,7 @@ And the following unique queues:
|
|||
- off - do not check password complexity
|
||||
- `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed.
|
||||
- `SUCCESSFUL_TOKENS_CACHE_SIZE`: **20**: Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
|
||||
- `DISABLE_QUERY_AUTH_TOKEN`: **false**: Reject API tokens sent in URL query string (Accept Header-based API tokens only). This setting will default to `true` in Gitea 1.23 and be deprecated in Gitea 1.24.
|
||||
|
||||
## Camo (`camo`)
|
||||
|
||||
|
@ -761,7 +762,6 @@ and
|
|||
|
||||
## Cache (`cache`)
|
||||
|
||||
- `ENABLED`: **true**: Enable the cache.
|
||||
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, `redis-cluster`, `twoqueue` or `memcache`. (`twoqueue` represents a size limited LRU cache.)
|
||||
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory and twoqueue cache only.
|
||||
- `HOST`: **_empty_**: Connection string for `redis`, `redis-cluster` and `memcache`. For `twoqueue` sets configuration for the queue.
|
||||
|
@ -773,7 +773,6 @@ and
|
|||
|
||||
## Cache - LastCommitCache settings (`cache.last_commit`)
|
||||
|
||||
- `ENABLED`: **true**: Enable the cache.
|
||||
- `ITEM_TTL`: **8760h**: Time to keep items in cache if not used, Setting it to -1 disables caching.
|
||||
- `COMMITS_COUNT`: **1000**: Only enable the cache when repository's commits count great than.
|
||||
|
||||
|
@ -1392,15 +1391,15 @@ PROXY_HOSTS = *.github.com
|
|||
- `DEFAULT_ACTIONS_URL`: **github**: Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
|
||||
- `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||
- `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
|
||||
- `ARTIFACT_RETENTION_DAYS`: **90**: Number of days to keep artifacts. Set to 0 to disable artifact retention. Default is 90 days if not set.
|
||||
- `ARTIFACT_RETENTION_DAYS`: **90**: Default number of days to keep artifacts. Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
|
||||
- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
|
||||
- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time
|
||||
- `ABANDONED_JOB_TIMEOUT`: **24h**: Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
|
||||
- `SKIP_WORKFLOW_STRINGS`: **[skip ci],[ci skip],[no ci],[skip actions],[actions skip]**: Strings committers can place inside a commit message to skip executing the corresponding actions workflow
|
||||
|
||||
`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
|
||||
For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`.
|
||||
And it can be changed to `self` to make it `root_url_of_your_gitea/actions/checkout@v3`.
|
||||
For example, `uses: actions/checkout@v4` means `https://github.com/actions/checkout@v4` since the value of `DEFAULT_ACTIONS_URL` is `github`.
|
||||
And it can be changed to `self` to make it `root_url_of_your_gitea/actions/checkout@v4`.
|
||||
|
||||
Please note that using `self` is not recommended for most cases, as it could make names globally ambiguous.
|
||||
Additionally, it requires you to mirror all the actions you need to your Gitea instance, which may not be worth it.
|
||||
|
@ -1409,7 +1408,7 @@ Therefore, please use `self` only if you understand what you are doing.
|
|||
In earlier versions (`<= 1.19`), `DEFAULT_ACTIONS_URL` could be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`.
|
||||
However, later updates removed those options, and now the only options are `github` and `self`, with the default value being `github`.
|
||||
However, if you want to use actions from other git server, you can use a complete URL in `uses` field, it's supported by Gitea (but not GitHub).
|
||||
Like `uses: https://gitea.com/actions/checkout@v3` or `uses: http://your-git-server/actions/checkout@v3`.
|
||||
Like `uses: https://gitea.com/actions/checkout@v4` or `uses: http://your-git-server/actions/checkout@v4`.
|
||||
|
||||
## Other (`other`)
|
||||
|
||||
|
|
|
@ -195,9 +195,7 @@ menu:
|
|||
## 跨域 (`cors`)
|
||||
|
||||
- `ENABLED`: **false**: 启用 CORS 头部(默认禁用)
|
||||
- `SCHEME`: **http**: 允许请求的协议
|
||||
- `ALLOW_DOMAIN`: **\***: 允许请求的域名列表
|
||||
- `ALLOW_SUBDOMAIN`: **false**: 允许上述列出的头部的子域名发出请求。
|
||||
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: 允许发起的请求方式列表
|
||||
- `MAX_AGE`: **10m**: 缓存响应的最大时间
|
||||
- `ALLOW_CREDENTIALS`: **false**: 允许带有凭据的请求
|
||||
|
@ -346,7 +344,7 @@ menu:
|
|||
- `SSH_PER_WRITE_TIMEOUT`: **30s**:对 SSH 连接的任何写入设置超时。(将其设置为 -1 可以禁用所有超时。)
|
||||
- `SSH_PER_WRITE_PER_KB_TIMEOUT`: **10s**:对写入 SSH 连接的每 KB 设置超时。
|
||||
- `MINIMUM_KEY_SIZE_CHECK`: **true**:指示是否检查最小密钥大小与相应类型。
|
||||
- `OFFLINE_MODE`: **false**:禁用 CDN 用于静态文件和 Gravatar 用于个人资料图片。
|
||||
- `OFFLINE_MODE`: **true**:禁用 CDN 用于静态文件和 Gravatar 用于个人资料图片。
|
||||
- `CERT_FILE`: **https/cert.pem**:用于 HTTPS 的证书文件路径。在链接时,服务器证书必须首先出现,然后是中间 CA 证书(如果有)。如果 `ENABLE_ACME=true`,则此设置会被忽略。路径相对于 `CUSTOM_PATH`。
|
||||
- `KEY_FILE`: **https/key.pem**:用于 HTTPS 的密钥文件路径。如果 `ENABLE_ACME=true`,则此设置会被忽略。路径相对于 `CUSTOM_PATH`。
|
||||
- `STATIC_ROOT_PATH`: **_`StaticRootPath`_**:模板和静态文件路径的上一级。
|
||||
|
@ -721,7 +719,6 @@ Gitea 创建以下非唯一队列:
|
|||
|
||||
## 缓存 (`cache`)
|
||||
|
||||
- `ENABLED`: **true**: 是否启用缓存。
|
||||
- `ADAPTER`: **memory**: 缓存引擎,可以为 `memory`, `redis`, `redis-cluster`, `twoqueue` 和 `memcache`. (`twoqueue` 代表缓冲区固定的LRU缓存)
|
||||
- `INTERVAL`: **60**: 垃圾回收间隔(秒),只对`memory`和`towqueue`有效。
|
||||
- `HOST`: **_empty_**: 缓存配置。`redis`, `redis-cluster`,`memcache`配置连接字符串;`twoqueue` 设置队列参数
|
||||
|
@ -733,7 +730,6 @@ Gitea 创建以下非唯一队列:
|
|||
|
||||
### 缓存 - 最后提交缓存设置 (`cache.last_commit`)
|
||||
|
||||
- `ENABLED`: **true**:是否启用缓存。
|
||||
- `ITEM_TTL`: **8760h**:如果未使用,保持缓存中的项目的时间,将其设置为 -1 会禁用缓存。
|
||||
- `COMMITS_COUNT`: **1000**:仅在存储库的提交计数大于时启用缓存。
|
||||
|
||||
|
@ -991,7 +987,7 @@ Gitea 创建以下非唯一队列:
|
|||
- `LAST_UPDATED_MORE_THAN_AGO`: **72h**: 只会尝试回收超过此时间(默认3天)没有尝试过回收的 LFSMetaObject。
|
||||
- `NUMBER_TO_CHECK_PER_REPO`: **100**: 每个仓库要检查的过期 LFSMetaObject 的最小数量。设置为 `0` 以始终检查所有。
|
||||
|
||||
# Git (`git`)
|
||||
## Git (`git`)
|
||||
|
||||
- `PATH`: **""**: Git可执行文件的路径。如果为空,Gitea将在PATH环境中搜索。
|
||||
- `HOME_PATH`: **%(APP_DATA_PATH)s/home**: Git的HOME目录。
|
||||
|
@ -1039,10 +1035,11 @@ Gitea 创建以下非唯一队列:
|
|||
|
||||
## API (`api`)
|
||||
|
||||
- `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 (`/api/swagger`, `/api/v1/swagger`, …)。
|
||||
- `MAX_RESPONSE_ITEMS`: **50**: 单个页面的最大 Feed.
|
||||
- `ENABLE_OPENID_SIGNIN`: **false**: 允许使用OpenID登录,当设置为`true`时可以通过 `/user/login` 页面进行OpenID登录。
|
||||
- `DISABLE_REGISTRATION`: **false**: 关闭用户注册。
|
||||
- `ENABLE_SWAGGER`: **true**: 启用API文档接口 (`/api/swagger`, `/api/v1/swagger`, …). True or false。
|
||||
- `MAX_RESPONSE_ITEMS`: **50**: API分页的最大单页项目数。
|
||||
- `DEFAULT_PAGING_NUM`: **30**: API分页的默认分页数。
|
||||
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Git trees API的默认单页项目数。
|
||||
- `DEFAULT_MAX_BLOB_SIZE`: **10485760** (10MiB): blobs API的默认最大文件大小。
|
||||
|
||||
## OAuth2 (`oauth2`)
|
||||
|
||||
|
@ -1336,8 +1333,8 @@ PROXY_HOSTS = *.github.com
|
|||
- `MINIO_BASE_PATH`: **actions_log/**:Minio存储桶上的基本路径,仅在`STORAGE_TYPE`为`minio`时可用。
|
||||
|
||||
`DEFAULT_ACTIONS_URL` 指示 Gitea 操作运行程序应该在哪里找到带有相对路径的操作。
|
||||
例如,`uses: actions/checkout@v3` 表示 `https://github.com/actions/checkout@v3`,因为 `DEFAULT_ACTIONS_URL` 的值为 `github`。
|
||||
它可以更改为 `self`,以使其成为 `root_url_of_your_gitea/actions/checkout@v3`。
|
||||
例如,`uses: actions/checkout@v4` 表示 `https://github.com/actions/checkout@v4`,因为 `DEFAULT_ACTIONS_URL` 的值为 `github`。
|
||||
它可以更改为 `self`,以使其成为 `root_url_of_your_gitea/actions/checkout@v4`。
|
||||
|
||||
请注意,对于大多数情况,不建议使用 `self`,因为它可能使名称在全局范围内产生歧义。
|
||||
此外,它要求您将所有所需的操作镜像到您的 Gitea 实例,这可能不值得。
|
||||
|
@ -1346,7 +1343,7 @@ PROXY_HOSTS = *.github.com
|
|||
在早期版本(`<= 1.19`)中,`DEFAULT_ACTIONS_URL` 可以设置为任何自定义 URL,例如 `https://gitea.com` 或 `http://your-git-server,https://gitea.com`,默认值为 `https://gitea.com`。
|
||||
然而,后来的更新删除了这些选项,现在唯一的选项是 `github` 和 `self`,默认值为 `github`。
|
||||
但是,如果您想要使用其他 Git 服务器中的操作,您可以在 `uses` 字段中使用完整的 URL,Gitea 支持此功能(GitHub 不支持)。
|
||||
例如 `uses: https://gitea.com/actions/checkout@v3` 或 `uses: http://your-git-server/actions/checkout@v3`。
|
||||
例如 `uses: https://gitea.com/actions/checkout@v4` 或 `uses: http://your-git-server/actions/checkout@v4`。
|
||||
|
||||
## 其他 (`other`)
|
||||
|
||||
|
|
|
@ -42,11 +42,11 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
|
|||
|
||||
将自定义的公共文件(比如页面和图片)作为 webroot 放在 `custom/public/` 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。
|
||||
|
||||
举例说明:`image.png` 存放在 `custom/public/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
|
||||
举例说明:`image.png` 存放在 `custom/public/assets/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
|
||||
|
||||
## 修改默认头像
|
||||
|
||||
替换以下目录中的 png 图片: `custom/public/img/avatar\_default.png`
|
||||
替换以下目录中的 png 图片: `custom/public/assets/img/avatar\_default.png`
|
||||
|
||||
## 自定义 Gitea 页面
|
||||
|
||||
|
|
|
@ -194,7 +194,7 @@ ALLOW_DATA_URI_IMAGES = true
|
|||
}
|
||||
```
|
||||
|
||||
将您的样式表添加到自定义目录中,例如 `custom/public/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
|
||||
将您的样式表添加到自定义目录中,例如 `custom/public/assets/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="{{AppSubUrl}}/assets/css/my-style-XXXXX.css" />
|
||||
|
|
|
@ -362,7 +362,7 @@ If you are receiving errors on upgrade of Gitea using MySQL that read:
|
|||
|
||||
> `ORM engine initialization failed: migrate: do migrate: Error: 1118: Row size too large...`
|
||||
|
||||
Please run `gitea convert` or run `ALTER TABLE table_name ROW_FORMAT=dynamic;` for each table in the database.
|
||||
Please run `gitea doctor convert` or run `ALTER TABLE table_name ROW_FORMAT=dynamic;` for each table in the database.
|
||||
|
||||
The underlying problem is that the space allocated for indices by the default row format
|
||||
is too small. Gitea requires that the `ROWFORMAT` for its tables is `DYNAMIC`.
|
||||
|
@ -385,7 +385,7 @@ Unfortunately MySQL's `utf8` charset does not completely allow all possible UTF-
|
|||
They created a new charset and collation called `utf8mb4` that allows for emoji to be stored but tables which use
|
||||
the `utf8` charset, and connections which use the `utf8` charset will not use this.
|
||||
|
||||
Please run `gitea convert`, or run `ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
||||
Please run `gitea doctor convert`, or run `ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
||||
for the database_name and run `ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
||||
for each table in the database.
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ Gitea 目前支持三个官方主题,分别是 `gitea-light`、`gitea-dark`
|
|||
|
||||
假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到)
|
||||
|
||||
将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/css`文件夹中
|
||||
将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/assets/css`文件夹中
|
||||
|
||||
通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题
|
||||
|
||||
|
@ -366,7 +366,7 @@ Gitea 提供了一个子命令`gitea migrate`来初始化数据库,然后您
|
|||
|
||||
> `ORM engine initialization failed: migrate: do migrate: Error: 1118: Row size too large...`
|
||||
|
||||
请运行`gitea convert`或对数据库中的每个表运行`ALTER TABLE table_name ROW_FORMAT=dynamic;`。
|
||||
请运行 `gitea doctor convert` 或对数据库中的每个表运行 `ALTER TABLE table_name ROW_FORMAT=dynamic;`。
|
||||
|
||||
潜在问题是默认行格式分配给每个表的索引空间
|
||||
太小。Gitea 要求其表的`ROWFORMAT`为`DYNAMIC`。
|
||||
|
@ -389,9 +389,8 @@ SET GLOBAL innodb_large_prefix=1;
|
|||
他们创建了一个名为 `utf8mb4`的字符集和校对规则,允许存储 Emoji,但使用
|
||||
utf8 字符集的表和连接将不会使用它。
|
||||
|
||||
请运行 `gitea convert` 或对数据库运行`ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
||||
并对每个表运行
|
||||
`ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`。
|
||||
请运行 `gitea doctor convert` 或对数据库运行 `ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
||||
并对每个表运行 `ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`。
|
||||
|
||||
您还需要将`app.ini`文件中的数据库字符集设置为`CHARSET=utf8mb4`。
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ menu:
|
|||
|
||||
# Database Preparation
|
||||
|
||||
You need a database to use Gitea. Gitea supports PostgreSQL (>= 12), MySQL (>= 8.0), MariaDB, SQLite, and MSSQL (>= 2012 SP4). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter.
|
||||
You need a database to use Gitea. Gitea supports PostgreSQL (>= 12), MySQL (>= 8.0), MariaDB (>= 10.4), SQLite (builtin), and MSSQL (>= 2012 SP4). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter.
|
||||
|
||||
If you use an unsupported database version, please [get in touch](/help/support) with us for information on our Extended Support Contracts. We can provide testing and support for older databases and integrate those fixes into the Gitea codebase.
|
||||
|
||||
|
|
|
@ -22,13 +22,17 @@ Even though Gitea Actions is designed to be compatible with GitHub Actions, ther
|
|||
### Absolute action URLs
|
||||
|
||||
Gitea Actions supports defining actions via absolute URL, which means that you can use actions from any git repository.
|
||||
Like `uses: https://github.com/actions/checkout@v3` or `uses: http://your_gitea.com/owner/repo@branch`.
|
||||
Like `uses: https://github.com/actions/checkout@v4` or `uses: http://your_gitea.com/owner/repo@branch`.
|
||||
|
||||
### Actions written in Go
|
||||
|
||||
Gitea Actions supports writing actions in Go.
|
||||
See [Creating Go Actions](https://blog.gitea.com/creating-go-actions/).
|
||||
|
||||
### Support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly on schedule
|
||||
|
||||
Github Actions doesn't support that. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
|
||||
|
||||
## Unsupported workflows syntax
|
||||
|
||||
### `concurrency`
|
||||
|
@ -110,6 +114,10 @@ It's ignored by Gitea Actions now.
|
|||
|
||||
Pre and Post steps don't have their own section in the job log user interface.
|
||||
|
||||
### Services steps
|
||||
|
||||
Services steps don't have their own section in the job log user interface.
|
||||
|
||||
## Different behavior
|
||||
|
||||
### Downloading actions
|
||||
|
@ -117,9 +125,9 @@ Pre and Post steps don't have their own section in the job log user interface.
|
|||
Previously (Pre 1.21.0), `[actions].DEFAULT_ACTIONS_URL` defaulted to `https://gitea.com`.
|
||||
We have since restricted this option to only allow two values (`github` and `self`).
|
||||
When set to `github`, the new default, Gitea will download non-fully-qualified actions from `https://github.com`.
|
||||
For example, if you use `uses: actions/checkout@v3`, it will download the checkout repository from `https://github.com/actions/checkout.git`.
|
||||
For example, if you use `uses: actions/checkout@v4`, it will download the checkout repository from `https://github.com/actions/checkout.git`.
|
||||
|
||||
If you want to download an action from another git hoster, you can use an absolute URL, e.g. `uses: https://gitea.com/actions/checkout@v3`.
|
||||
If you want to download an action from another git hoster, you can use an absolute URL, e.g. `uses: https://gitea.com/actions/checkout@v4`.
|
||||
|
||||
If your Gitea instance is in an intranet or a restricted area, you can set the URL to `self` to only download actions from your own instance by default.
|
||||
Of course, you can still use absolute URLs in workflows.
|
||||
|
|
|
@ -22,13 +22,17 @@ menu:
|
|||
### Action URL绝对路径
|
||||
|
||||
Gitea Actions支持通过URL绝对路径定义actions,这意味着您可以使用来自任何Git存储库的Actions。
|
||||
例如,`uses: https://github.com/actions/checkout@v3`或`uses: http://your_gitea.com/owner/repo@branch`。
|
||||
例如,`uses: https://github.com/actions/checkout@v4`或`uses: http://your_gitea.com/owner/repo@branch`。
|
||||
|
||||
### 使用Go编写Actions
|
||||
|
||||
Gitea Actions支持使用Go编写Actions。
|
||||
请参阅[创建Go Actions](https://blog.gitea.com/creating-go-actions/)。
|
||||
|
||||
### 支持非标准的调度语法 @yearly, @monthly, @weekly, @daily, @hourly
|
||||
|
||||
Github Actions 不支持这些语法,详见: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
|
||||
|
||||
## 不支持的工作流语法
|
||||
|
||||
### `concurrency`
|
||||
|
@ -116,15 +120,19 @@ Gitea Actions目前不支持此功能。
|
|||
|
||||
预处理和后处理步骤在Job日志用户界面中没有自己的用户界面。
|
||||
|
||||
### 服务步骤
|
||||
|
||||
服务步骤在Job日志用户界面中没有自己的用户界面。
|
||||
|
||||
## 不一样的行为
|
||||
|
||||
### 下载Actions
|
||||
|
||||
当 `[actions].DEFAULT_ACTIONS_URL` 保持默认值为 `github` 时,Gitea将会从 https://github.com 下载相对路径的actions。比如:
|
||||
如果你使用 `uses: actions/checkout@v3`,Gitea将会从 https://github.com/actions/checkout.git 下载这个 actions 项目。
|
||||
如果你想要从另外一个 Git服务下载actions,你只需要使用绝对URL `uses: https://gitea.com/actions/checkout@v3` 来下载。
|
||||
如果你使用 `uses: actions/checkout@v4`,Gitea将会从 https://github.com/actions/checkout.git 下载这个 actions 项目。
|
||||
如果你想要从另外一个 Git服务下载actions,你只需要使用绝对URL `uses: https://gitea.com/actions/checkout@v4` 来下载。
|
||||
|
||||
如果你的 Gitea 实例是部署在一个互联网限制的网络中,有可以使用绝对地址来下载 actions。你也可以讲配置项修改为 `[actions].DEFAULT_ACTIONS_URL = self`。这样所有的相对路径的actions引用,将不再会从 github.com 去下载,而会从这个 Gitea 实例自己的仓库中去下载。例如: `uses: actions/checkout@v3` 将会从 `[server].ROOT_URL`/actions/checkout.git 这个地址去下载 actions。
|
||||
如果你的 Gitea 实例是部署在一个互联网限制的网络中,有可以使用绝对地址来下载 actions。你也可以讲配置项修改为 `[actions].DEFAULT_ACTIONS_URL = self`。这样所有的相对路径的actions引用,将不再会从 github.com 去下载,而会从这个 Gitea 实例自己的仓库中去下载。例如: `uses: actions/checkout@v4` 将会从 `[server].ROOT_URL`/actions/checkout.git 这个地址去下载 actions。
|
||||
|
||||
设置`[actions].DEFAULT_ACTIONS_URL`进行配置。请参阅[配置备忘单](administration/config-cheat-sheet.md#actions-actions)。
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ The act runner must be able to connect to Gitea to receive tasks and send back t
|
|||
### Connection 2, job containers to Gitea instance
|
||||
|
||||
The job containers have different network namespaces than the runner, even if they are on the same machine.
|
||||
They need to connect to Gitea to fetch codes if there is `actions/checkout@v3` in the workflow, for example.
|
||||
They need to connect to Gitea to fetch codes if there is `actions/checkout@v4` in the workflow, for example.
|
||||
Fetching code is not always necessary to run some jobs, but it is required in most cases.
|
||||
|
||||
If you use a loopback address to register a runner, the runner can connect to Gitea when it is on the same machine.
|
||||
|
@ -103,7 +103,7 @@ However, if a job container tries to fetch code from localhost, it will fail bec
|
|||
|
||||
### Connection 3, act runner to internet
|
||||
|
||||
When you use some actions like `actions/checkout@v3`, the act runner downloads the scripts, not the job containers.
|
||||
When you use some actions like `actions/checkout@v4`, the act runner downloads the scripts, not the job containers.
|
||||
By default, it downloads from [gitea.com](http://gitea.com/), so it requires access to the internet.
|
||||
It also downloads some docker images from Docker Hub by default, which also requires internet access.
|
||||
|
||||
|
@ -116,7 +116,7 @@ And [Gitea Container Registry](usage/packages/container.md) can be used as a Doc
|
|||
|
||||
### Connection 4, job containers to internet
|
||||
|
||||
When using actions such as `actions/setup-go@v4`, it may be necessary to download resources from the internet to set up the Go language environment in job containers.
|
||||
When using actions such as `actions/setup-go@v5`, it may be necessary to download resources from the internet to set up the Go language environment in job containers.
|
||||
Therefore, access to the internet is required for the successful completion of these actions.
|
||||
|
||||
However, it is optional as well.
|
||||
|
|
|
@ -96,7 +96,7 @@ act runner 必须能够连接到Gitea以接收任务并发送执行结果回来
|
|||
### 连接 2,Job容器到Gitea实例
|
||||
|
||||
即使Job容器位于同一台机器上,它们的网络命名空间与Runner不同。
|
||||
举个例子,如果工作流中包含 `actions/checkout@v3`,Job容器需要连接到Gitea来获取代码。
|
||||
举个例子,如果工作流中包含 `actions/checkout@v4`,Job容器需要连接到Gitea来获取代码。
|
||||
获取代码并不总是运行某些Job所必需的,但在大多数情况下是必需的。
|
||||
|
||||
如果您使用回环地址注册Runner,当Runner与Gitea在同一台机器上时,Runner可以连接到Gitea。
|
||||
|
@ -104,7 +104,7 @@ act runner 必须能够连接到Gitea以接收任务并发送执行结果回来
|
|||
|
||||
### 连接 3,act runner到互联网
|
||||
|
||||
当您使用诸如 `actions/checkout@v3` 的一些Actions时,act runner下载的是脚本,而不是Job容器。
|
||||
当您使用诸如 `actions/checkout@v4` 的一些Actions时,act runner下载的是脚本,而不是Job容器。
|
||||
默认情况下,它从[gitea.com](http://gitea.com/)下载,因此需要访问互联网。
|
||||
它还默认从Docker Hub下载一些Docker镜像,这也需要互联网访问。
|
||||
|
||||
|
@ -117,7 +117,7 @@ act runner 必须能够连接到Gitea以接收任务并发送执行结果回来
|
|||
|
||||
### 连接 4,Job容器到互联网
|
||||
|
||||
当使用诸如`actions/setup-go@v4`的Actions时,可能需要从互联网下载资源,以设置Job容器中的Go语言环境。
|
||||
当使用诸如`actions/setup-go@v5`的Actions时,可能需要从互联网下载资源,以设置Job容器中的Go语言环境。
|
||||
因此,成功完成这些Actions需要访问互联网。
|
||||
|
||||
然而,这也是可选的。
|
||||
|
|
|
@ -43,10 +43,10 @@ Still, this is completely optional since both options have the same effect at th
|
|||
Not yet.
|
||||
It is technically possible to implement, but we need to discuss whether it is necessary.
|
||||
|
||||
## Where will the runner download scripts when using actions such as `actions/checkout@v3`?
|
||||
## Where will the runner download scripts when using actions such as `actions/checkout@v4`?
|
||||
|
||||
You may be aware that there are tens of thousands of [marketplace actions](https://github.com/marketplace?type=actions) in GitHub.
|
||||
However, when you write `uses: actions/checkout@v3`, it actually downloads the scripts from [gitea.com/actions/checkout](http://gitea.com/actions/checkout) by default (not GitHub).
|
||||
However, when you write `uses: actions/checkout@v4`, it actually downloads the scripts from [gitea.com/actions/checkout](http://gitea.com/actions/checkout) by default (not GitHub).
|
||||
This is a mirror of [github.com/actions/checkout](http://github.com/actions/checkout), but it's impossible to mirror all of them.
|
||||
That's why you may encounter failures when trying to use some actions that haven't been mirrored.
|
||||
|
||||
|
|
|
@ -43,10 +43,10 @@ DEFAULT_REPO_UNITS = ...,repo.actions
|
|||
目前还不可以。
|
||||
从技术上讲是可以实现的,但我们需要讨论是否有必要。
|
||||
|
||||
## 使用`actions/checkout@v3`等Actions时,Job容器会从何处下载脚本?
|
||||
## 使用`actions/checkout@v4`等Actions时,Job容器会从何处下载脚本?
|
||||
|
||||
您可能知道GitHub上有成千上万个[Actions市场](https://github.com/marketplace?type=actions)。
|
||||
然而,当您编写`uses: actions/checkout@v3`时,它实际上默认从[gitea.com/actions/checkout](http://gitea.com/actions/checkout)下载脚本(而不是从GitHub下载)。
|
||||
然而,当您编写`uses: actions/checkout@v4`时,它实际上默认从[gitea.com/actions/checkout](http://gitea.com/actions/checkout)下载脚本(而不是从GitHub下载)。
|
||||
这是[github.com/actions/checkout](http://github.com/actions/checkout)的镜像,但无法将它们全部镜像。
|
||||
这就是为什么在尝试使用尚未镜像的某些Actions时可能会遇到失败的原因。
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ To avoid confusion, we have clarified the spelling here:
|
|||
- "Gitea Actions" (with an "s", both words capitalized) is the name of the Gitea feature.
|
||||
- "GitHub Actions" is the name of the GitHub feature.
|
||||
- "Actions" could refer to either of the above, depending on the context. So it refers to "Gitea Actions" in this document.
|
||||
- "action" or "actions" refer to some scripts/plugins to be used, like "actions/checkout@v3" or "actions/cache@v3".
|
||||
- "action" or "actions" refer to some scripts/plugins to be used, like "actions/checkout@v4" or "actions/cache@v3".
|
||||
|
||||
## Runners
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ Gitea Actions与[GitHub Actions](https://github.com/features/actions)相似且
|
|||
- "Gitea Actions"(两个单词都大写且带有"s")是Gitea功能的名称。
|
||||
- "GitHub Actions"是GitHub功能的名称。
|
||||
- "Actions"根据上下文的不同可以指代以上任意一个。在本文档中指代的是"Gitea Actions"。
|
||||
- "action"或"actions"指代一些要使用的脚本/插件,比如"actions/checkout@v3"或"actions/cache@v3"。
|
||||
- "action"或"actions"指代一些要使用的脚本/插件,比如"actions/checkout@v4"或"actions/cache@v3"。
|
||||
|
||||
## Runner
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ jobs:
|
|||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||
- name: List files in the repository
|
||||
|
|
|
@ -112,7 +112,7 @@ jobs:
|
|||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||
- name: List files in the repository
|
||||
|
|
|
@ -145,25 +145,25 @@ Adds the following fields:
|
|||
|
||||
Uses the following fields:
|
||||
|
||||
- Group Search Base (optional)
|
||||
- Group Search Base DN (optional)
|
||||
|
||||
- The LDAP DN used for groups.
|
||||
- Example: `ou=group,dc=mydomain,dc=com`
|
||||
|
||||
- Group Name Filter (optional)
|
||||
- Group Attribute Containing List Of Users (optional)
|
||||
- The attribute of the group object that lists/contains the group members.
|
||||
- Example: `memberUid` or `member`
|
||||
|
||||
- An LDAP filter declaring how to find valid groups in the above DN.
|
||||
- Example: `(|(cn=gitea_users)(cn=admins))`
|
||||
|
||||
- User Attribute in Group (optional)
|
||||
- User Attribute Listed in Group (optional)
|
||||
|
||||
- The user attribute that is used to reference a user in the group object.
|
||||
- Example: `uid` if the group objects contains a `member: bender` and the user object contains a `uid: bender`.
|
||||
- Example: `dn` if the group object contains a `member: uid=bender,ou=users,dc=planetexpress,dc=com`.
|
||||
|
||||
- Group Attribute for User (optional)
|
||||
- The attribute of the group object that lists/contains the group members.
|
||||
- Example: `memberUid` or `member`
|
||||
- Verify group membership in LDAP (optional)
|
||||
|
||||
- An LDAP filter declaring how to find valid groups in the above DN.
|
||||
- Example: `(|(cn=gitea_users)(cn=admins))`
|
||||
|
||||
## PAM (Pluggable Authentication Module)
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ The following examples use `apt`.
|
|||
To register the Debian registry add the url to the list of known apt sources:
|
||||
|
||||
```shell
|
||||
echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
| Placeholder | Description |
|
||||
|
@ -39,13 +39,13 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {
|
|||
If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication):
|
||||
|
||||
```shell
|
||||
echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
The Debian registry files are signed with a PGP key which must be known to apt:
|
||||
|
||||
```shell
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/keyrings/gitea-{owner}.asc
|
||||
```
|
||||
|
||||
Afterwards update the local package index:
|
||||
|
|
|
@ -27,7 +27,7 @@ menu:
|
|||
要注册 Debian 注册表,请将 URL 添加到已知 `apt` 源列表中:
|
||||
|
||||
```shell
|
||||
echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
| 占位符 | 描述 |
|
||||
|
@ -39,13 +39,13 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {
|
|||
如果注册表是私有的,请在 URL 中提供凭据。您可以使用密码或[个人访问令牌](development/api-usage.md#通过-api-认证):
|
||||
|
||||
```shell
|
||||
echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
Debian 注册表文件使用 PGP 密钥进行签名,`apt` 必须知道该密钥:
|
||||
|
||||
```shell
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/keyrings/gitea-{owner}.asc
|
||||
```
|
||||
|
||||
然后更新本地软件包索引:
|
||||
|
|
|
@ -58,7 +58,7 @@ The repository now gets mirrored periodically to the remote repository. You can
|
|||
|
||||
To set up a mirror from Gitea to GitHub, you need to follow these steps:
|
||||
|
||||
1. Create a [GitHub personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the *public_repo* box checked.
|
||||
1. Create a [GitHub personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the *public_repo* box checked. Also check the **workflow** checkbox in case your repo using act for continuous integration.
|
||||
2. Create a repository with that name on GitHub. Unlike Gitea, GitHub does not support creating repositories by pushing to the remote. You can also use an existing remote repo if it has the same commit history as your Gitea repo.
|
||||
3. In the settings of your Gitea repo, fill in the **Git Remote Repository URL**: `https://github.com/<your_github_group>/<your_github_project>.git`.
|
||||
4. Fill in the **Authorization** fields with your GitHub username and the personal access token as **Password**.
|
||||
|
|
170
go.mod
170
go.mod
|
@ -5,7 +5,7 @@ go 1.21
|
|||
require (
|
||||
code.gitea.io/actions-proto-go v0.3.1
|
||||
code.gitea.io/gitea-vet v0.2.3
|
||||
code.gitea.io/sdk/gitea v0.16.0
|
||||
code.gitea.io/sdk/gitea v0.17.0
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669
|
||||
gitea.com/go-chi/cache v0.2.0
|
||||
|
@ -17,12 +17,12 @@ require (
|
|||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/alecthomas/chroma/v2 v2.10.0
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.3.10
|
||||
github.com/bufbuild/connect-go v1.10.0
|
||||
github.com/buildkite/terminal-to-html/v3 v3.9.1
|
||||
github.com/caddyserver/certmagic v0.19.2
|
||||
github.com/buildkite/terminal-to-html/v3 v3.10.0
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/denisenkom/go-mssqldb v0.12.3
|
||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
|
||||
|
@ -35,13 +35,13 @@ require (
|
|||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/ethantkoenig/rupture v1.0.1
|
||||
github.com/felixge/fgprof v0.9.3
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46
|
||||
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-co-op/gocron v1.31.1
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.8.6
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
|
@ -50,34 +50,34 @@ require (
|
|||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/go-swagger/go-swagger v0.30.5
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.9.0
|
||||
github.com/go-webauthn/webauthn v0.8.6
|
||||
github.com/go-webauthn/webauthn v0.9.4
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||
github.com/google/go-github/v53 v53.2.0
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/google/go-github/v57 v57.0.0
|
||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/gorilla/feeds v1.1.2
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huandu/xstrings v1.4.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
|
||||
github.com/jhillyerd/enmime v1.0.1
|
||||
github.com/jhillyerd/enmime v1.1.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
||||
github.com/klauspost/compress v1.17.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/klauspost/cpuid/v2 v2.2.6
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/markbates/goth v1.78.0
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/meilisearch/meilisearch-go v0.25.1
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/meilisearch/meilisearch-go v0.26.0
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/minio/minio-go/v7 v7.0.63
|
||||
github.com/minio/minio-go/v7 v7.0.66
|
||||
github.com/minio/sha256-simd v1.0.1
|
||||
github.com/msteinert/pam v1.2.0
|
||||
github.com/nektos/act v0.2.52
|
||||
|
@ -89,7 +89,7 @@ require (
|
|||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/quasoft/websspi v1.1.2
|
||||
github.com/redis/go-redis/v9 v9.2.1
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
||||
github.com/sassoftware/go-rpmutils v0.2.0
|
||||
|
@ -99,21 +99,21 @@ require (
|
|||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tstranex/u2f v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
github.com/xanzy/go-gitlab v0.93.1
|
||||
github.com/urfave/cli/v2 v2.26.0
|
||||
github.com/xanzy/go-gitlab v0.95.2
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.5.6
|
||||
github.com/yuin/goldmark v1.6.0
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/image v0.13.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
golang.org/x/sys v0.13.0
|
||||
golang.org/x/text v0.13.0
|
||||
golang.org/x/tools v0.14.0
|
||||
google.golang.org/grpc v1.58.3
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/image v0.14.0
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/oauth2 v0.15.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.16.1
|
||||
google.golang.org/grpc v1.60.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
@ -121,37 +121,37 @@ require (
|
|||
mvdan.cc/xurls/v2 v2.5.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.4
|
||||
xorm.io/xorm v1.3.6
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.23.1 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/ClickHouse/ch-go v0.58.2 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.14.3 // indirect
|
||||
github.com/ClickHouse/ch-go v0.61.0 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.16.0 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
|
||||
github.com/RoaringBitmap/roaring v1.6.0 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.7.0 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.9.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.0.6 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.12.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.1.4 // indirect
|
||||
github.com/blevesearch/geo v0.1.18 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.5 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
|
@ -165,9 +165,9 @@ require (
|
|||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/cloudflare/circl v1.3.6 // indirect
|
||||
github.com/couchbase/go-couchbase v0.1.1 // indirect
|
||||
github.com/couchbase/gomemcached v0.2.1 // indirect
|
||||
github.com/couchbase/gomemcached v0.3.0 // indirect
|
||||
github.com/couchbase/goutils v0.1.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
|
@ -175,28 +175,28 @@ require (
|
|||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.6.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
||||
github.com/go-openapi/errors v0.20.4 // indirect
|
||||
github.com/go-openapi/analysis v0.21.5 // indirect
|
||||
github.com/go-openapi/errors v0.21.0 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/loads v0.21.2 // indirect
|
||||
github.com/go-openapi/runtime v0.26.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.7 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-openapi/validate v0.22.1 // indirect
|
||||
github.com/go-webauthn/x v0.1.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.3 // indirect
|
||||
github.com/go-openapi/loads v0.21.3 // indirect
|
||||
github.com/go-openapi/runtime v0.26.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.12 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.10 // indirect
|
||||
github.com/go-openapi/swag v0.22.5 // indirect
|
||||
github.com/go-openapi/validate v0.22.4 // indirect
|
||||
github.com/go-webauthn/x v0.1.5 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
|
@ -207,12 +207,12 @@ require (
|
|||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/go-tpm v0.9.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
|
@ -228,9 +228,9 @@ require (
|
|||
github.com/markbates/going v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.56 // indirect
|
||||
github.com/miekg/dns v1.1.57 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
|
@ -244,19 +244,19 @@ require (
|
|||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/paulmach/orb v0.10.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.19 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rhysd/actionlint v1.6.26 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
|
@ -264,37 +264,37 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.17.0 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
||||
github.com/unknwon/com v1.0.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.50.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/fastjson v1.6.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.1 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.8 // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
|
|
@ -90,19 +90,6 @@ func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath stri
|
|||
return &art, nil
|
||||
}
|
||||
|
||||
// GetArtifactByID returns an artifact by id
|
||||
func GetArtifactByID(ctx context.Context, id int64) (*ActionArtifact, error) {
|
||||
var art ActionArtifact
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(&art)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, util.ErrNotExist
|
||||
}
|
||||
|
||||
return &art, nil
|
||||
}
|
||||
|
||||
// UpdateArtifactByID updates an artifact by id
|
||||
func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) error {
|
||||
art.ID = id
|
||||
|
|
|
@ -51,6 +51,11 @@ type ActionRunner struct {
|
|||
Deleted timeutil.TimeStamp `xorm:"deleted"`
|
||||
}
|
||||
|
||||
const (
|
||||
RunnerOfflineTime = time.Minute
|
||||
RunnerIdleTime = 10 * time.Second
|
||||
)
|
||||
|
||||
// BelongsToOwnerName before calling, should guarantee that all attributes are loaded
|
||||
func (r *ActionRunner) BelongsToOwnerName() string {
|
||||
if r.RepoID != 0 {
|
||||
|
@ -76,11 +81,12 @@ func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
|
|||
return types.OwnerTypeSystemGlobal
|
||||
}
|
||||
|
||||
// if the logic here changed, you should also modify FindRunnerOptions.ToCond
|
||||
func (r *ActionRunner) Status() runnerv1.RunnerStatus {
|
||||
if time.Since(r.LastOnline.AsTime()) > time.Minute {
|
||||
if time.Since(r.LastOnline.AsTime()) > RunnerOfflineTime {
|
||||
return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
|
||||
}
|
||||
if time.Since(r.LastActive.AsTime()) > 10*time.Second {
|
||||
if time.Since(r.LastActive.AsTime()) > RunnerIdleTime {
|
||||
return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
|
||||
}
|
||||
return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
|
||||
|
@ -153,6 +159,7 @@ type FindRunnerOptions struct {
|
|||
OwnerID int64
|
||||
Sort string
|
||||
Filter string
|
||||
IsOnline util.OptionalBool
|
||||
WithAvailable bool // not only runners belong to, but also runners can be used
|
||||
}
|
||||
|
||||
|
@ -178,6 +185,12 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
|
|||
if opts.Filter != "" {
|
||||
cond = cond.And(builder.Like{"name", opts.Filter})
|
||||
}
|
||||
|
||||
if opts.IsOnline.IsTrue() {
|
||||
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
} else if opts.IsOnline.IsFalse() {
|
||||
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
@ -241,7 +254,7 @@ func DeleteRunner(ctx context.Context, id int64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Delete(&ActionRunner{ID: id})
|
||||
_, err := db.DeleteByID[ActionRunner](ctx, id)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -31,8 +31,8 @@ func init() {
|
|||
}
|
||||
|
||||
func (v *ActionVariable) Validate() error {
|
||||
if v.OwnerID == 0 && v.RepoID == 0 {
|
||||
return errors.New("the variable is not bound to any scope")
|
||||
if v.OwnerID != 0 && v.RepoID != 0 {
|
||||
return errors.New("a variable should not be bound to an owner and a repository at the same time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -58,12 +58,8 @@ type FindVariablesOpts struct {
|
|||
|
||||
func (opts FindVariablesOpts) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
return cond
|
||||
}
|
||||
|
||||
|
|
|
@ -59,8 +59,8 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
|
|||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// Mock time
|
||||
timeutil.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
defer timeutil.Unset()
|
||||
timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
defer timeutil.MockUnset()
|
||||
|
||||
for _, tc := range testCases {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID})
|
||||
|
|
|
@ -107,8 +107,9 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
|
|||
// VerificationToken returns token for the user that will be valid in minutes (time)
|
||||
func VerificationToken(user *user_model.User, minutes int) string {
|
||||
return base.EncodeSha256(
|
||||
time.Now().Truncate(1*time.Minute).Add(time.Duration(minutes)*time.Minute).Format(time.RFC1123Z) + ":" +
|
||||
user.CreatedUnix.FormatLong() + ":" +
|
||||
time.Now().Truncate(1*time.Minute).Add(time.Duration(minutes)*time.Minute).Format(
|
||||
time.RFC1123Z) + ":" +
|
||||
user.CreatedUnix.Format(time.RFC1123Z) + ":" +
|
||||
user.Name + ":" +
|
||||
user.Email + ":" +
|
||||
strconv.FormatInt(user.ID, 10))
|
||||
|
|
|
@ -227,16 +227,6 @@ func UpdatePublicKeyUpdated(ctx context.Context, id int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeletePublicKeys does the actual key deletion but does not update authorized_keys file.
|
||||
func DeletePublicKeys(ctx context.Context, keyIDs ...int64) error {
|
||||
if len(keyIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).In("id", keyIDs).Delete(new(PublicKey))
|
||||
return err
|
||||
}
|
||||
|
||||
// PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key
|
||||
func PublicKeysAreExternallyManaged(ctx context.Context, keys []*PublicKey) ([]bool, error) {
|
||||
sources := make([]*auth.Source, 0, 5)
|
||||
|
@ -322,8 +312,8 @@ func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) (bool, erro
|
|||
log.Error("SearchPublicKeyByContent: %v", err)
|
||||
continue
|
||||
}
|
||||
if err = DeletePublicKeys(ctx, key.ID); err != nil {
|
||||
log.Error("deletePublicKeys: %v", err)
|
||||
if _, err = db.DeleteByID[PublicKey](ctx, key.ID); err != nil {
|
||||
log.Error("DeleteByID[PublicKey]: %v", err)
|
||||
continue
|
||||
}
|
||||
sshKeysNeedUpdate = true
|
||||
|
|
|
@ -30,10 +30,15 @@ func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signat
|
|||
return "", ErrKeyNotExist{}
|
||||
}
|
||||
|
||||
if err := sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea"); err != nil {
|
||||
log.Error("Unable to validate token signature. Error: %v", err)
|
||||
return "", ErrSSHInvalidTokenSignature{
|
||||
Fingerprint: key.Fingerprint,
|
||||
err = sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea")
|
||||
if err != nil {
|
||||
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
|
||||
// see https://github.com/PowerShell/PowerShell/issues/5974
|
||||
if sshsig.Verify(bytes.NewBuffer([]byte(token+"\r\n")), []byte(signature), []byte(key.Content), "gitea") != nil {
|
||||
log.Error("Unable to validate token signature. Error: %v", err)
|
||||
return "", ErrSSHInvalidTokenSignature{
|
||||
Fingerprint: key.Fingerprint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,12 +41,15 @@ func ReadSession(ctx context.Context, key string) (*Session, error) {
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
session, exist, err := db.Get[Session](ctx, builder.Eq{"key": key})
|
||||
session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !exist {
|
||||
session.Expiry = timeutil.TimeStampNow()
|
||||
if err := db.Insert(ctx, &session); err != nil {
|
||||
session = &Session{
|
||||
Key: key,
|
||||
Expiry: timeutil.TimeStampNow(),
|
||||
}
|
||||
if err := db.Insert(ctx, session); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +59,7 @@ func ReadSession(ctx context.Context, key string) (*Session, error) {
|
|||
|
||||
// ExistSession checks if a session exists
|
||||
func ExistSession(ctx context.Context, key string) (bool, error) {
|
||||
return db.Exist[Session](ctx, builder.Eq{"key": key})
|
||||
return db.Exist[Session](ctx, builder.Eq{"`key`": key})
|
||||
}
|
||||
|
||||
// DestroySession destroys a session
|
||||
|
@ -75,13 +78,13 @@ func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, er
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
if has, err := db.Exist[Session](ctx, builder.Eq{"key": newKey}); err != nil {
|
||||
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil {
|
||||
return nil, err
|
||||
} else if has {
|
||||
return nil, fmt.Errorf("session Key: %s already exists", newKey)
|
||||
}
|
||||
|
||||
if has, err := db.Exist[Session](ctx, builder.Eq{"key": oldKey}); err != nil {
|
||||
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
if err := db.Insert(ctx, &Session{
|
||||
|
@ -96,7 +99,7 @@ func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, er
|
|||
return nil, err
|
||||
}
|
||||
|
||||
s, _, err := db.Get[Session](ctx, builder.Eq{"key": newKey})
|
||||
s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey})
|
||||
if err != nil {
|
||||
// is not exist, it should be impossible
|
||||
return nil, err
|
||||
|
|
|
@ -261,16 +261,12 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
|
|||
// IsSSPIEnabled returns true if there is at least one activated login
|
||||
// source of type LoginSSPI
|
||||
func IsSSPIEnabled(ctx context.Context) bool {
|
||||
if !db.HasEngine {
|
||||
return false
|
||||
}
|
||||
|
||||
exist, err := db.Exist[Source](ctx, FindSourcesOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
LoginType: SSPI,
|
||||
}.ToConds())
|
||||
if err != nil {
|
||||
log.Error("Active SSPI Sources: %v", err)
|
||||
log.Error("IsSSPIEnabled: failed to query active SSPI sources: %v", err)
|
||||
return false
|
||||
}
|
||||
return exist
|
||||
|
|
|
@ -5,6 +5,8 @@ package avatars
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
|
@ -13,7 +15,6 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -90,7 +91,9 @@ func DefaultAvatarLink() string {
|
|||
|
||||
// HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/
|
||||
func HashEmail(email string) string {
|
||||
return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
|
||||
m := md5.New()
|
||||
_, _ = m.Write([]byte(strings.ToLower(strings.TrimSpace(email))))
|
||||
return hex.EncodeToString(m.Sum(nil))
|
||||
}
|
||||
|
||||
// GetEmailForHash converts a provided md5sum to the email
|
||||
|
|
|
@ -175,7 +175,7 @@ func Exec(ctx context.Context, sqlAndArgs ...any) (sql.Result, error) {
|
|||
|
||||
func Get[T any](ctx context.Context, cond builder.Cond) (object *T, exist bool, err error) {
|
||||
if !cond.IsValid() {
|
||||
return nil, false, ErrConditionRequired{}
|
||||
panic("cond is invalid in db.Get(ctx, cond). This should not be possible.")
|
||||
}
|
||||
|
||||
var bean T
|
||||
|
@ -201,7 +201,7 @@ func GetByID[T any](ctx context.Context, id int64) (object *T, exist bool, err e
|
|||
|
||||
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
|
||||
if !cond.IsValid() {
|
||||
return false, ErrConditionRequired{}
|
||||
panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
|
||||
}
|
||||
|
||||
var bean T
|
||||
|
@ -213,16 +213,36 @@ func ExistByID[T any](ctx context.Context, id int64) (bool, error) {
|
|||
return GetEngine(ctx).ID(id).NoAutoCondition().Exist(&bean)
|
||||
}
|
||||
|
||||
// DeleteByID deletes the given bean with the given ID
|
||||
func DeleteByID[T any](ctx context.Context, id int64) (int64, error) {
|
||||
var bean T
|
||||
return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(&bean)
|
||||
}
|
||||
|
||||
func DeleteByIDs[T any](ctx context.Context, ids ...int64) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var bean T
|
||||
_, err := GetEngine(ctx).In("id", ids).NoAutoCondition().NoAutoTime().Delete(&bean)
|
||||
return err
|
||||
}
|
||||
|
||||
func Delete[T any](ctx context.Context, opts FindOptions) (int64, error) {
|
||||
if opts == nil || !opts.ToConds().IsValid() {
|
||||
panic("opts are empty or invalid in db.Delete(ctx, opts). This should not be possible.")
|
||||
}
|
||||
|
||||
var bean T
|
||||
return GetEngine(ctx).Where(opts.ToConds()).NoAutoCondition().NoAutoTime().Delete(&bean)
|
||||
}
|
||||
|
||||
// DeleteByBean deletes all records according non-empty fields of the bean as conditions.
|
||||
func DeleteByBean(ctx context.Context, bean any) (int64, error) {
|
||||
return GetEngine(ctx).Delete(bean)
|
||||
}
|
||||
|
||||
// DeleteByID deletes the given bean with the given ID
|
||||
func DeleteByID(ctx context.Context, id int64, bean any) (int64, error) {
|
||||
return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(bean)
|
||||
}
|
||||
|
||||
// FindIDs finds the IDs for the given table name satisfying the given condition
|
||||
// By passing a different value than "id" for "idCol", you can query for foreign IDs, i.e. the repo IDs which satisfy the condition
|
||||
func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([]int64, error) {
|
||||
|
|
|
@ -27,9 +27,6 @@ var (
|
|||
x *xorm.Engine
|
||||
tables []any
|
||||
initFuncs []func() error
|
||||
|
||||
// HasEngine specifies if we have a xorm.Engine
|
||||
HasEngine bool
|
||||
)
|
||||
|
||||
// Engine represents a xorm engine or session.
|
||||
|
|
|
@ -72,21 +72,3 @@ func (err ErrNotExist) Error() string {
|
|||
func (err ErrNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrConditionRequired represents an error which require condition.
|
||||
type ErrConditionRequired struct{}
|
||||
|
||||
// IsErrConditionRequired checks if an error is an ErrConditionRequired
|
||||
func IsErrConditionRequired(err error) bool {
|
||||
_, ok := err.(ErrConditionRequired)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrConditionRequired) Error() string {
|
||||
return "condition is required"
|
||||
}
|
||||
|
||||
// Unwrap unwraps this as a ErrNotExist err
|
||||
func (err ErrConditionRequired) Unwrap() error {
|
||||
return util.ErrInvalidArgument
|
||||
}
|
||||
|
|
|
@ -205,10 +205,9 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64
|
|||
})
|
||||
}
|
||||
|
||||
// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information
|
||||
// If it doest not exist, insert a new record into database
|
||||
func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error {
|
||||
cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
|
||||
// UpdateBranch updates the branch information in the database.
|
||||
func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
|
||||
Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
|
||||
Update(&Branch{
|
||||
CommitID: commit.ID.String(),
|
||||
|
@ -217,21 +216,6 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string
|
|||
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
|
||||
IsDeleted: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return db.Insert(ctx, &Branch{
|
||||
RepoID: repoID,
|
||||
Name: branchName,
|
||||
CommitID: commit.ID.String(),
|
||||
CommitMessage: commit.Summary(),
|
||||
PusherID: pusherID,
|
||||
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
|
||||
})
|
||||
}
|
||||
|
||||
// AddDeletedBranch adds a deleted branch to the database
|
||||
|
@ -308,6 +292,17 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
|||
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
var branch Branch
|
||||
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !exist || branch.IsDeleted {
|
||||
return ErrBranchNotExist{
|
||||
RepoID: repo.ID,
|
||||
BranchName: from,
|
||||
}
|
||||
}
|
||||
|
||||
// 1. update branch in database
|
||||
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
|
||||
Name: to,
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type BranchList []*Branch
|
||||
|
@ -73,7 +72,7 @@ type FindBranchOptions struct {
|
|||
Keyword string
|
||||
}
|
||||
|
||||
func (opts *FindBranchOptions) Cond() builder.Cond {
|
||||
func (opts FindBranchOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
|
@ -91,41 +90,30 @@ func (opts *FindBranchOptions) Cond() builder.Cond {
|
|||
return cond
|
||||
}
|
||||
|
||||
func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{})
|
||||
}
|
||||
|
||||
func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
|
||||
func (opts FindBranchOptions) ToOrders() string {
|
||||
orderBy := opts.OrderBy
|
||||
if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end
|
||||
sess = sess.OrderBy("is_deleted ASC")
|
||||
if orderBy != "" {
|
||||
orderBy += ", "
|
||||
}
|
||||
orderBy += "is_deleted ASC"
|
||||
}
|
||||
|
||||
if opts.OrderBy == "" {
|
||||
if orderBy == "" {
|
||||
// the commit_time might be the same, so add the "name" to make sure the order is stable
|
||||
opts.OrderBy = "commit_time DESC, name ASC"
|
||||
return "commit_time DESC, name ASC"
|
||||
}
|
||||
return sess.OrderBy(opts.OrderBy)
|
||||
}
|
||||
|
||||
func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) {
|
||||
sess := db.GetEngine(ctx).Where(opts.Cond())
|
||||
if opts.PageSize > 0 && !opts.IsListAll() {
|
||||
sess = db.SetSessionPagination(sess, &opts.ListOptions)
|
||||
}
|
||||
sess = orderByBranches(sess, opts)
|
||||
|
||||
var branches []*Branch
|
||||
return branches, sess.Find(&branches)
|
||||
return orderBy
|
||||
}
|
||||
|
||||
func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) {
|
||||
sess := db.GetEngine(ctx).Select("name").Where(opts.Cond())
|
||||
sess := db.GetEngine(ctx).Select("name").Where(opts.ToConds())
|
||||
if opts.PageSize > 0 && !opts.IsListAll() {
|
||||
sess = db.SetSessionPagination(sess, &opts.ListOptions)
|
||||
}
|
||||
sess = orderByBranches(sess, opts)
|
||||
|
||||
var branches []string
|
||||
if err := sess.Table("branch").Find(&branches); err != nil {
|
||||
if err := sess.Table("branch").OrderBy(opts.ToOrders()).Find(&branches); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return branches, nil
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
func TestAddDeletedBranch(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.EqualValues(t, git.Sha1ObjectFormat.Name(), repo.ObjectFormatName)
|
||||
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
|
||||
|
||||
assert.True(t, firstBranch.IsDeleted)
|
||||
|
@ -37,7 +38,7 @@ func TestAddDeletedBranch(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit)
|
||||
_, err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -45,10 +46,8 @@ func TestGetDeletedBranches(t *testing.T) {
|
|||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
branches, err := git_model.FindBranches(db.DefaultContext, git_model.FindBranchOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
branches, err := db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
RepoID: repo.ID,
|
||||
IsDeletedBranch: util.OptionalBoolTrue,
|
||||
})
|
||||
|
|
|
@ -114,7 +114,8 @@ WHEN NOT MATCHED
|
|||
|
||||
// GetNextCommitStatusIndex retried 3 times to generate a resource index
|
||||
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||
if !git.IsValidSHAPattern(sha) {
|
||||
_, err := git.NewIDFromString(sha)
|
||||
if err != nil {
|
||||
return 0, git.ErrInvalidSHA{SHA: sha}
|
||||
}
|
||||
|
||||
|
@ -323,7 +324,9 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA
|
|||
Select("max( id ) as id, repo_id").
|
||||
GroupBy("context_hash, repo_id").OrderBy("max( id ) desc")
|
||||
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
if !listOptions.IsListAll() {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
}
|
||||
|
||||
err := sess.Find(&results)
|
||||
if err != nil {
|
||||
|
@ -423,7 +426,7 @@ func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, befor
|
|||
type NewCommitStatusOptions struct {
|
||||
Repo *repo_model.Repository
|
||||
Creator *user_model.User
|
||||
SHA string
|
||||
SHA git.ObjectID
|
||||
CommitStatus *CommitStatus
|
||||
}
|
||||
|
||||
|
@ -438,10 +441,6 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error {
|
|||
return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
|
||||
}
|
||||
|
||||
if _, err := git.NewIDFromString(opts.SHA); err != nil {
|
||||
return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err)
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", opts.Repo.ID, opts.Creator.ID, opts.SHA, err)
|
||||
|
@ -449,7 +448,7 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error {
|
|||
defer committer.Close()
|
||||
|
||||
// Get the next Status Index
|
||||
idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA)
|
||||
idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate commit status index failed: %w", err)
|
||||
}
|
||||
|
@ -457,7 +456,7 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error {
|
|||
opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
|
||||
opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
|
||||
opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
|
||||
opts.CommitStatus.SHA = opts.SHA
|
||||
opts.CommitStatus.SHA = opts.SHA.String()
|
||||
opts.CommitStatus.CreatorID = opts.Creator.ID
|
||||
opts.CommitStatus.RepoID = opts.Repo.ID
|
||||
opts.CommitStatus.Index = idx
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
@ -127,7 +126,7 @@ func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, user *use
|
|||
return writeAccess
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.WhitelistUserIDs, user.ID) {
|
||||
if slices.Contains(protectBranch.WhitelistUserIDs, user.ID) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -150,7 +149,7 @@ func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch,
|
|||
return permissionInRepo.CanWrite(unit.TypeCode)
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) {
|
||||
if slices.Contains(protectBranch.MergeWhitelistUserIDs, userID) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -182,7 +181,7 @@ func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch,
|
|||
return writeAccess, nil
|
||||
}
|
||||
|
||||
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
|
||||
if slices.Contains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@ package git
|
|||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
|
@ -76,7 +76,7 @@ func DeleteProtectedTag(ctx context.Context, pt *ProtectedTag) error {
|
|||
|
||||
// IsUserAllowedModifyTag returns true if the user is allowed to modify the tag
|
||||
func IsUserAllowedModifyTag(ctx context.Context, pt *ProtectedTag, userID int64) (bool, error) {
|
||||
if base.Int64sContains(pt.AllowlistUserIDs, userID) {
|
||||
if slices.Contains(pt.AllowlistUserIDs, userID) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,10 @@ func TestUpdateAssignee(t *testing.T) {
|
|||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// Fake issue with assignees
|
||||
issue, err := issues_model.GetIssueWithAttrsByID(db.DefaultContext, 1)
|
||||
issue, err := issues_model.GetIssueByID(db.DefaultContext, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = issue.LoadAttributes(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Assign multiple users
|
||||
|
|
|
@ -900,15 +900,15 @@ func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Is
|
|||
// newDeadline = 0 means deleting
|
||||
if newDeadlineUnix == 0 {
|
||||
commentType = CommentTypeRemovedDeadline
|
||||
content = issue.DeadlineUnix.Format("2006-01-02")
|
||||
content = issue.DeadlineUnix.FormatDate()
|
||||
} else if issue.DeadlineUnix == 0 {
|
||||
// Check if the new date was added or modified
|
||||
// If the actual deadline is 0 => deadline added
|
||||
commentType = CommentTypeAddedDeadline
|
||||
content = newDeadlineUnix.Format("2006-01-02")
|
||||
content = newDeadlineUnix.FormatDate()
|
||||
} else { // Otherwise modified
|
||||
commentType = CommentTypeModifiedDeadline
|
||||
content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02")
|
||||
content = newDeadlineUnix.FormatDate() + "|" + issue.DeadlineUnix.FormatDate()
|
||||
}
|
||||
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
|
@ -1162,14 +1162,9 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
|
|||
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
|
||||
func UpdateCommentsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
|
||||
_, err := db.GetEngine(ctx).Table("comment").
|
||||
Where(builder.In("issue_id",
|
||||
builder.Select("issue.id").
|
||||
From("issue").
|
||||
InnerJoin("repository", "issue.repo_id = repository.id").
|
||||
Where(builder.Eq{
|
||||
"repository.original_service_type": tp,
|
||||
}),
|
||||
)).
|
||||
Join("INNER", "issue", "issue.id = comment.issue_id").
|
||||
Join("INNER", "repository", "issue.repo_id = repository.id").
|
||||
Where("repository.original_service_type = ?", tp).
|
||||
And("comment.original_author_id = ?", originalAuthorID).
|
||||
Update(map[string]any{
|
||||
"poster_id": posterID,
|
||||
|
|
|
@ -534,15 +534,6 @@ func GetIssueByID(ctx context.Context, id int64) (*Issue, error) {
|
|||
return issue, nil
|
||||
}
|
||||
|
||||
// GetIssueWithAttrsByID returns an issue with attributes by given ID.
|
||||
func GetIssueWithAttrsByID(ctx context.Context, id int64) (*Issue, error) {
|
||||
issue, err := GetIssueByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return issue, issue.LoadAttributes(ctx)
|
||||
}
|
||||
|
||||
// GetIssuesByIDs return issues with the given IDs.
|
||||
// If keepOrder is true, the order of the returned issues will be the same as the given IDs.
|
||||
func GetIssuesByIDs(ctx context.Context, issueIDs []int64, keepOrder ...bool) (IssueList, error) {
|
||||
|
|
|
@ -455,26 +455,6 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Sess
|
|||
)
|
||||
}
|
||||
|
||||
// GetRepoIDsForIssuesOptions find all repo ids for the given options
|
||||
func GetRepoIDsForIssuesOptions(ctx context.Context, opts *IssuesOptions, user *user_model.User) ([]int64, error) {
|
||||
repoIDs := make([]int64, 0, 5)
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
||||
|
||||
applyConditions(sess, opts)
|
||||
|
||||
accessCond := repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)
|
||||
if err := sess.Where(accessCond).
|
||||
Distinct("issue.repo_id").
|
||||
Table("issue").
|
||||
Find(&repoIDs); err != nil {
|
||||
return nil, fmt.Errorf("unable to GetRepoIDsForIssuesOptions: %w", err)
|
||||
}
|
||||
|
||||
return repoIDs, nil
|
||||
}
|
||||
|
||||
// Issues returns a list of issues by given conditions.
|
||||
func Issues(ctx context.Context, opts *IssuesOptions) (IssueList, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
|
|
|
@ -216,36 +216,6 @@ func TestIssue_loadTotalTimes(t *testing.T) {
|
|||
assert.Equal(t, int64(3682), ms.TotalTrackedTime)
|
||||
}
|
||||
|
||||
func TestGetRepoIDsForIssuesOptions(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
for _, test := range []struct {
|
||||
Opts issues_model.IssuesOptions
|
||||
ExpectedRepoIDs []int64
|
||||
}{
|
||||
{
|
||||
issues_model.IssuesOptions{
|
||||
AssigneeID: 2,
|
||||
},
|
||||
[]int64{3, 32},
|
||||
},
|
||||
{
|
||||
issues_model.IssuesOptions{
|
||||
RepoCond: builder.In("repo_id", 1, 2),
|
||||
},
|
||||
[]int64{1, 2},
|
||||
},
|
||||
} {
|
||||
repoIDs, err := issues_model.GetRepoIDsForIssuesOptions(db.DefaultContext, &test.Opts, user)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, repoIDs, len(test.ExpectedRepoIDs)) {
|
||||
for i, repoID := range repoIDs {
|
||||
assert.EqualValues(t, test.ExpectedRepoIDs[i], repoID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue {
|
||||
var newIssue issues_model.Issue
|
||||
t.Run(title, func(t *testing.T) {
|
||||
|
@ -279,11 +249,11 @@ func TestIssue_InsertIssue(t *testing.T) {
|
|||
|
||||
// there are 5 issues and max index is 5 on repository 1, so this one should 6
|
||||
issue := testInsertIssue(t, "my issue1", "special issue's comments?", 6)
|
||||
_, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(issues_model.Issue))
|
||||
_, err := db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
issue = testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?", 7)
|
||||
_, err = db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(issues_model.Issue))
|
||||
_, err = db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ import (
|
|||
// IssueUser represents an issue-user relation.
|
||||
type IssueUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX"` // User ID.
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID.
|
||||
IssueID int64 `xorm:"INDEX unique(uid_to_issue)"`
|
||||
IsRead bool
|
||||
IsMentioned bool
|
||||
}
|
||||
|
|
|
@ -235,41 +235,43 @@ func UpdateLabel(ctx context.Context, l *Label) error {
|
|||
|
||||
// DeleteLabel delete a label
|
||||
func DeleteLabel(ctx context.Context, id, labelID int64) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
l, err := GetLabelByID(ctx, labelID)
|
||||
if err != nil {
|
||||
if IsErrLabelNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if l.BelongsToOrg() && l.OrgID != id {
|
||||
return nil
|
||||
}
|
||||
if l.BelongsToRepo() && l.RepoID != id {
|
||||
l, err := GetLabelByID(ctx, labelID)
|
||||
if err != nil {
|
||||
if IsErrLabelNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.ID(labelID).Delete(new(Label)); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if _, err = sess.
|
||||
Where("label_id = ?", labelID).
|
||||
Delete(new(IssueLabel)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete comments about now deleted label_id
|
||||
if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{}); err != nil {
|
||||
return err
|
||||
}
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if l.BelongsToOrg() && l.OrgID != id {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if l.BelongsToRepo() && l.RepoID != id {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = db.DeleteByID[Label](ctx, labelID); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.
|
||||
Where("label_id = ?", labelID).
|
||||
Delete(new(IssueLabel)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete comments about now deleted label_id
|
||||
if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// GetLabelByID returns a label by given ID.
|
||||
|
@ -487,22 +489,6 @@ func GetLabelInOrgByID(ctx context.Context, orgID, labelID int64) (*Label, error
|
|||
return l, nil
|
||||
}
|
||||
|
||||
// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given
|
||||
// organization.
|
||||
func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) {
|
||||
if orgID <= 0 {
|
||||
return nil, ErrOrgLabelNotExist{0, orgID}
|
||||
}
|
||||
labelIDs := make([]int64, 0, len(labelNames))
|
||||
|
||||
return labelIDs, db.GetEngine(ctx).Table("label").
|
||||
Where("org_id = ?", orgID).
|
||||
In("name", labelNames).
|
||||
Asc("name").
|
||||
Cols("id").
|
||||
Find(&labelIDs)
|
||||
}
|
||||
|
||||
// GetLabelsInOrgByIDs returns a list of labels by IDs in given organization,
|
||||
// it silently ignores label IDs that do not belong to the organization.
|
||||
func GetLabelsInOrgByIDs(ctx context.Context, orgID int64, labelIDs []int64) ([]*Label, error) {
|
||||
|
|
|
@ -164,30 +164,6 @@ func TestGetLabelInOrgByName(t *testing.T) {
|
|||
assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
|
||||
}
|
||||
|
||||
func TestGetLabelInOrgByNames(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
labelIDs, err := issues_model.GetLabelIDsInOrgByNames(db.DefaultContext, 3, []string{"orglabel3", "orglabel4"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, labelIDs, 2)
|
||||
|
||||
assert.Equal(t, int64(3), labelIDs[0])
|
||||
assert.Equal(t, int64(4), labelIDs[1])
|
||||
}
|
||||
|
||||
func TestGetLabelInOrgByNamesDiscardsNonExistentLabels(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
// orglabel99 doesn't exists.. See labels.yml
|
||||
labelIDs, err := issues_model.GetLabelIDsInOrgByNames(db.DefaultContext, 3, []string{"orglabel3", "orglabel4", "orglabel99"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, labelIDs, 2)
|
||||
|
||||
assert.Equal(t, int64(3), labelIDs[0])
|
||||
assert.Equal(t, int64(4), labelIDs[1])
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetLabelInOrgByID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
label, err := issues_model.GetLabelInOrgByID(db.DefaultContext, 3, 3)
|
||||
|
|
|
@ -88,7 +88,7 @@ func (m *Milestone) AfterLoad() {
|
|||
return
|
||||
}
|
||||
|
||||
m.DeadlineString = m.DeadlineUnix.Format("2006-01-02")
|
||||
m.DeadlineString = m.DeadlineUnix.FormatDate()
|
||||
if m.IsClosed {
|
||||
m.IsOverdue = m.ClosedDateUnix >= m.DeadlineUnix
|
||||
} else {
|
||||
|
@ -291,22 +291,19 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if _, err = sess.ID(m.ID).Delete(new(Milestone)); err != nil {
|
||||
if _, err = db.DeleteByID[Milestone](ctx, m.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
numMilestones, err := CountMilestones(ctx, GetMilestonesOption{
|
||||
numMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
|
||||
RepoID: repo.ID,
|
||||
State: api.StateAll,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numClosedMilestones, err := CountMilestones(ctx, GetMilestonesOption{
|
||||
RepoID: repo.ID,
|
||||
State: api.StateClosed,
|
||||
numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
|
||||
RepoID: repo.ID,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -314,7 +311,7 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
|
|||
repo.NumMilestones = int(numMilestones)
|
||||
repo.NumClosedMilestones = int(numClosedMilestones)
|
||||
|
||||
if _, err = sess.ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
|
||||
if _, err = db.GetEngine(ctx).ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
@ -25,31 +24,31 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
|
|||
return ids
|
||||
}
|
||||
|
||||
// GetMilestonesOption contain options to get milestones
|
||||
type GetMilestonesOption struct {
|
||||
// FindMilestoneOptions contain options to get milestones
|
||||
type FindMilestoneOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
State api.StateType
|
||||
IsClosed util.OptionalBool
|
||||
Name string
|
||||
SortType string
|
||||
RepoCond builder.Cond
|
||||
RepoIDs []int64
|
||||
}
|
||||
|
||||
func (opts GetMilestonesOption) toCond() builder.Cond {
|
||||
func (opts FindMilestoneOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
|
||||
switch opts.State {
|
||||
case api.StateClosed:
|
||||
cond = cond.And(builder.Eq{"is_closed": true})
|
||||
case api.StateAll:
|
||||
break
|
||||
// api.StateOpen:
|
||||
default:
|
||||
cond = cond.And(builder.Eq{"is_closed": false})
|
||||
if opts.IsClosed != util.OptionalBoolNone {
|
||||
cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.IsTrue()})
|
||||
}
|
||||
if opts.RepoCond != nil && opts.RepoCond.IsValid() {
|
||||
cond = cond.And(builder.In("repo_id", builder.Select("id").From("repository").Where(opts.RepoCond)))
|
||||
}
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
cond = cond.And(builder.In("repo_id", opts.RepoIDs))
|
||||
}
|
||||
|
||||
if len(opts.Name) != 0 {
|
||||
cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name))
|
||||
}
|
||||
|
@ -57,34 +56,23 @@ func (opts GetMilestonesOption) toCond() builder.Cond {
|
|||
return cond
|
||||
}
|
||||
|
||||
// GetMilestones returns milestones filtered by GetMilestonesOption's
|
||||
func GetMilestones(ctx context.Context, opts GetMilestonesOption) (MilestoneList, int64, error) {
|
||||
sess := db.GetEngine(ctx).Where(opts.toCond())
|
||||
|
||||
if opts.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
}
|
||||
|
||||
func (opts FindMilestoneOptions) ToOrders() string {
|
||||
switch opts.SortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
return "deadline_unix DESC"
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
return "completeness ASC"
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
return "completeness DESC"
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
return "num_issues ASC"
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
return "num_issues DESC"
|
||||
case "id":
|
||||
sess.Asc("id")
|
||||
return "id ASC"
|
||||
default:
|
||||
sess.Asc("deadline_unix").Asc("id")
|
||||
return "deadline_unix ASC, id ASC"
|
||||
}
|
||||
|
||||
miles := make([]*Milestone, 0, opts.PageSize)
|
||||
total, err := sess.FindAndCount(&miles)
|
||||
return miles, total, err
|
||||
}
|
||||
|
||||
// GetMilestoneIDsByNames returns a list of milestone ids by given names.
|
||||
|
@ -99,49 +87,6 @@ func GetMilestoneIDsByNames(ctx context.Context, names []string) ([]int64, error
|
|||
Find(&ids)
|
||||
}
|
||||
|
||||
// SearchMilestones search milestones
|
||||
func SearchMilestones(ctx context.Context, repoCond builder.Cond, page int, isClosed bool, sortType, keyword string) (MilestoneList, error) {
|
||||
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
|
||||
sess := db.GetEngine(ctx).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
if page > 0 {
|
||||
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
|
||||
}
|
||||
|
||||
switch sortType {
|
||||
case "furthestduedate":
|
||||
sess.Desc("deadline_unix")
|
||||
case "leastcomplete":
|
||||
sess.Asc("completeness")
|
||||
case "mostcomplete":
|
||||
sess.Desc("completeness")
|
||||
case "leastissues":
|
||||
sess.Asc("num_issues")
|
||||
case "mostissues":
|
||||
sess.Desc("num_issues")
|
||||
default:
|
||||
sess.Asc("deadline_unix")
|
||||
}
|
||||
return miles, sess.Find(&miles)
|
||||
}
|
||||
|
||||
// GetMilestonesByRepoIDs returns a list of milestones of given repositories and status.
|
||||
func GetMilestonesByRepoIDs(ctx context.Context, repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) {
|
||||
return SearchMilestones(
|
||||
ctx,
|
||||
builder.In("repo_id", repoIDs),
|
||||
page,
|
||||
isClosed,
|
||||
sortType,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
|
||||
func (milestones MilestoneList) LoadTotalTrackedTimes(ctx context.Context) error {
|
||||
type totalTimesByMilestone struct {
|
||||
|
@ -183,47 +128,9 @@ func (milestones MilestoneList) LoadTotalTrackedTimes(ctx context.Context) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// CountMilestones returns number of milestones in given repository with other options
|
||||
func CountMilestones(ctx context.Context, opts GetMilestonesOption) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.toCond()).
|
||||
Count(new(Milestone))
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCond(ctx context.Context, repoCond builder.Cond, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(ctx).Where("is_closed = ?", isClosed)
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
}, 0, 10)
|
||||
if err := sess.GroupBy("repo_id").
|
||||
Select("repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("milestone").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
for _, c := range countsSlice {
|
||||
countMap[c.RepoID] = c.Count
|
||||
}
|
||||
return countMap, nil
|
||||
}
|
||||
|
||||
// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
|
||||
func CountMilestonesByRepoCondAndKw(ctx context.Context, repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(ctx).Where("is_closed = ?", isClosed)
|
||||
if len(keyword) > 0 {
|
||||
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
|
||||
}
|
||||
if repoCond.IsValid() {
|
||||
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
|
||||
}
|
||||
func CountMilestonesMap(ctx context.Context, opts FindMilestoneOptions) (map[int64]int64, error) {
|
||||
sess := db.GetEngine(ctx).Where(opts.ToConds())
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
|
@ -253,32 +160,6 @@ func (m MilestonesStats) Total() int64 {
|
|||
return m.OpenCount + m.ClosedCount
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions.
|
||||
func GetMilestonesStatsByRepoCond(ctx context.Context, repoCond builder.Cond) (*MilestonesStats, error) {
|
||||
var err error
|
||||
stats := &MilestonesStats{}
|
||||
|
||||
sess := db.GetEngine(ctx).Where("is_closed = ?", false)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.OpenCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess = db.GetEngine(ctx).Where("is_closed = ?", true)
|
||||
if repoCond.IsValid() {
|
||||
sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond)))
|
||||
}
|
||||
stats.ClosedCount, err = sess.Count(new(Milestone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword.
|
||||
func GetMilestonesStatsByRepoCondAndKw(ctx context.Context, repoCond builder.Cond, keyword string) (*MilestonesStats, error) {
|
||||
var err error
|
||||
|
|
|
@ -14,9 +14,9 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestMilestone_State(t *testing.T) {
|
||||
|
@ -39,10 +39,15 @@ func TestGetMilestoneByRepoID(t *testing.T) {
|
|||
func TestGetMilestonesByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
test := func(repoID int64, state api.StateType) {
|
||||
var isClosed util.OptionalBool
|
||||
switch state {
|
||||
case api.StateClosed, api.StateOpen:
|
||||
isClosed = util.OptionalBoolOf(state == api.StateClosed)
|
||||
}
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||
milestones, _, err := issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
RepoID: repo.ID,
|
||||
State: state,
|
||||
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: repo.ID,
|
||||
IsClosed: isClosed,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -77,9 +82,9 @@ func TestGetMilestonesByRepoID(t *testing.T) {
|
|||
test(3, api.StateClosed)
|
||||
test(3, api.StateAll)
|
||||
|
||||
milestones, _, err := issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
RepoID: unittest.NonexistentID,
|
||||
State: api.StateOpen,
|
||||
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: unittest.NonexistentID,
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, milestones, 0)
|
||||
|
@ -90,13 +95,13 @@ func TestGetMilestones(t *testing.T) {
|
|||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
test := func(sortType string, sortCond func(*issues_model.Milestone) int) {
|
||||
for _, page := range []int{0, 1} {
|
||||
milestones, _, err := issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoID: repo.ID,
|
||||
State: api.StateOpen,
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
SortType: sortType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@ -107,13 +112,13 @@ func TestGetMilestones(t *testing.T) {
|
|||
}
|
||||
assert.True(t, sort.IntsAreSorted(values))
|
||||
|
||||
milestones, _, err = issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
milestones, err = db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoID: repo.ID,
|
||||
State: api.StateClosed,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
Name: "",
|
||||
SortType: sortType,
|
||||
})
|
||||
|
@ -150,9 +155,8 @@ func TestCountRepoMilestones(t *testing.T) {
|
|||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
test := func(repoID int64) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||
count, err := issues_model.CountMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: repoID,
|
||||
State: api.StateAll,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo.NumMilestones, count)
|
||||
|
@ -161,9 +165,8 @@ func TestCountRepoMilestones(t *testing.T) {
|
|||
test(2)
|
||||
test(3)
|
||||
|
||||
count, err := issues_model.CountMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: unittest.NonexistentID,
|
||||
State: api.StateAll,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, count)
|
||||
|
@ -173,9 +176,9 @@ func TestCountRepoClosedMilestones(t *testing.T) {
|
|||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
test := func(repoID int64) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||
count, err := issues_model.CountMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
RepoID: repoID,
|
||||
State: api.StateClosed,
|
||||
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: repoID,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo.NumClosedMilestones, count)
|
||||
|
@ -184,9 +187,9 @@ func TestCountRepoClosedMilestones(t *testing.T) {
|
|||
test(2)
|
||||
test(3)
|
||||
|
||||
count, err := issues_model.CountMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
|
||||
RepoID: unittest.NonexistentID,
|
||||
State: api.StateClosed,
|
||||
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: unittest.NonexistentID,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, count)
|
||||
|
@ -201,12 +204,19 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
|
|||
repo1OpenCount, repo1ClosedCount := milestonesCount(1)
|
||||
repo2OpenCount, repo2ClosedCount := milestonesCount(2)
|
||||
|
||||
openCounts, err := issues_model.CountMilestonesByRepoCond(db.DefaultContext, builder.In("repo_id", []int64{1, 2}), false)
|
||||
openCounts, err := issues_model.CountMilestonesMap(db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoIDs: []int64{1, 2},
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo1OpenCount, openCounts[1])
|
||||
assert.EqualValues(t, repo2OpenCount, openCounts[2])
|
||||
|
||||
closedCounts, err := issues_model.CountMilestonesByRepoCond(db.DefaultContext, builder.In("repo_id", []int64{1, 2}), true)
|
||||
closedCounts, err := issues_model.CountMilestonesMap(db.DefaultContext,
|
||||
issues_model.FindMilestoneOptions{
|
||||
RepoIDs: []int64{1, 2},
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo1ClosedCount, closedCounts[1])
|
||||
assert.EqualValues(t, repo2ClosedCount, closedCounts[2])
|
||||
|
@ -218,7 +228,15 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
|
|||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
test := func(sortType string, sortCond func(*issues_model.Milestone) int) {
|
||||
for _, page := range []int{0, 1} {
|
||||
openMilestones, err := issues_model.GetMilestonesByRepoIDs(db.DefaultContext, []int64{repo1.ID, repo2.ID}, page, false, sortType)
|
||||
openMilestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoIDs: []int64{repo1.ID, repo2.ID},
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
SortType: sortType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, openMilestones, repo1.NumOpenMilestones+repo2.NumOpenMilestones)
|
||||
values := make([]int, len(openMilestones))
|
||||
|
@ -227,7 +245,16 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
|
|||
}
|
||||
assert.True(t, sort.IntsAreSorted(values))
|
||||
|
||||
closedMilestones, err := issues_model.GetMilestonesByRepoIDs(db.DefaultContext, []int64{repo1.ID, repo2.ID}, page, true, sortType)
|
||||
closedMilestones, err := db.Find[issues_model.Milestone](db.DefaultContext,
|
||||
issues_model.FindMilestoneOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoIDs: []int64{repo1.ID, repo2.ID},
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
SortType: sortType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, closedMilestones, repo1.NumClosedMilestones+repo2.NumClosedMilestones)
|
||||
values = make([]int, len(closedMilestones))
|
||||
|
@ -257,34 +284,6 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestGetMilestonesStats(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
test := func(repoID int64) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||
stats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.And(builder.Eq{"repo_id": repoID}))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, stats.OpenCount)
|
||||
assert.EqualValues(t, repo.NumClosedMilestones, stats.ClosedCount)
|
||||
}
|
||||
test(1)
|
||||
test(2)
|
||||
test(3)
|
||||
|
||||
stats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.And(builder.Eq{"repo_id": unittest.NonexistentID}))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, stats.OpenCount)
|
||||
assert.EqualValues(t, 0, stats.ClosedCount)
|
||||
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
|
||||
milestoneStats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.In("repo_id", []int64{repo1.ID, repo2.ID}))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo1.NumOpenMilestones+repo2.NumOpenMilestones, milestoneStats.OpenCount)
|
||||
assert.EqualValues(t, repo1.NumClosedMilestones+repo2.NumClosedMilestones, milestoneStats.ClosedCount)
|
||||
}
|
||||
|
||||
func TestNewMilestone(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
milestone := &issues_model.Milestone{
|
||||
|
|
|
@ -78,24 +78,6 @@ func (err ErrPullRequestAlreadyExists) Unwrap() error {
|
|||
return util.ErrAlreadyExist
|
||||
}
|
||||
|
||||
// ErrPullRequestHeadRepoMissing represents a "ErrPullRequestHeadRepoMissing" error
|
||||
type ErrPullRequestHeadRepoMissing struct {
|
||||
ID int64
|
||||
HeadRepoID int64
|
||||
}
|
||||
|
||||
// IsErrErrPullRequestHeadRepoMissing checks if an error is a ErrPullRequestHeadRepoMissing.
|
||||
func IsErrErrPullRequestHeadRepoMissing(err error) bool {
|
||||
_, ok := err.(ErrPullRequestHeadRepoMissing)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Error does pretty-printing :D
|
||||
func (err ErrPullRequestHeadRepoMissing) Error() string {
|
||||
return fmt.Sprintf("pull request head repo missing [id: %d, head_repo_id: %d]",
|
||||
err.ID, err.HeadRepoID)
|
||||
}
|
||||
|
||||
// ErrPullWasClosed is used close a closed pull request
|
||||
type ErrPullWasClosed struct {
|
||||
ID int64
|
||||
|
@ -758,18 +740,6 @@ func (pr *PullRequest) IsSameRepo() bool {
|
|||
return pr.BaseRepoID == pr.HeadRepoID
|
||||
}
|
||||
|
||||
// GetPullRequestsByHeadBranch returns all prs by head branch
|
||||
// Since there could be multiple prs with the same head branch, this function returns a slice of prs
|
||||
func GetPullRequestsByHeadBranch(ctx context.Context, headBranch string, headRepoID int64) ([]*PullRequest, error) {
|
||||
log.Trace("GetPullRequestsByHeadBranch: headBranch: '%s', headRepoID: '%d'", headBranch, headRepoID)
|
||||
prs := make([]*PullRequest, 0, 2)
|
||||
if err := db.GetEngine(ctx).Where(builder.Eq{"head_branch": headBranch, "head_repo_id": headRepoID}).
|
||||
Find(&prs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return prs, nil
|
||||
}
|
||||
|
||||
// GetBaseBranchLink returns the relative URL of the base branch
|
||||
func (pr *PullRequest) GetBaseBranchLink(ctx context.Context) string {
|
||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||
|
|
|
@ -6,6 +6,7 @@ package issues
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -15,7 +16,6 @@ import (
|
|||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
@ -280,7 +280,7 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
|
|||
return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil
|
||||
}
|
||||
|
||||
return base.Int64sContains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil
|
||||
return slices.Contains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil
|
||||
}
|
||||
|
||||
// CreateReview creates a new review based on opts
|
||||
|
@ -447,7 +447,7 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
|
|||
continue
|
||||
}
|
||||
|
||||
if _, err := sess.ID(teamReviewRequest.ID).NoAutoCondition().Delete(teamReviewRequest); err != nil {
|
||||
if _, err := db.DeleteByID[Review](ctx, teamReviewRequest.ID); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
@ -461,8 +461,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
|
|||
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) {
|
||||
review := new(Review)
|
||||
|
||||
has, err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))",
|
||||
issueID, userID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
|
||||
has, err := db.GetEngine(ctx).Where(
|
||||
builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
|
||||
And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})).
|
||||
Desc("id").
|
||||
Get(review)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -476,13 +478,13 @@ func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*R
|
|||
}
|
||||
|
||||
// GetTeamReviewerByIssueIDAndTeamID get the latest review request of reviewer team for a pull request
|
||||
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (review *Review, err error) {
|
||||
review = new(Review)
|
||||
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (*Review, error) {
|
||||
review := new(Review)
|
||||
|
||||
var has bool
|
||||
if has, err = db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)",
|
||||
issueID, teamID).
|
||||
Get(review); err != nil {
|
||||
has, err := db.GetEngine(ctx).Where(builder.Eq{"issue_id": issueID, "reviewer_team_id": teamID}).
|
||||
Desc("id").
|
||||
Get(review)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -961,7 +963,6 @@ func DeleteReview(ctx context.Context, r *Review) error {
|
|||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if r.ID == 0 {
|
||||
return fmt.Errorf("review is not allowed to be 0")
|
||||
|
@ -977,7 +978,7 @@ func DeleteReview(ctx context.Context, r *Review) error {
|
|||
ReviewID: r.ID,
|
||||
}
|
||||
|
||||
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
|
||||
if _, err := db.Delete[Comment](ctx, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -987,7 +988,7 @@ func DeleteReview(ctx context.Context, r *Review) error {
|
|||
ReviewID: r.ID,
|
||||
}
|
||||
|
||||
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
|
||||
if _, err := db.Delete[Comment](ctx, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -997,11 +998,11 @@ func DeleteReview(ctx context.Context, r *Review) error {
|
|||
ReviewID: r.ID,
|
||||
}
|
||||
|
||||
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
|
||||
if _, err := db.Delete[Comment](ctx, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.ID(r.ID).Delete(new(Review)); err != nil {
|
||||
if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -29,20 +29,6 @@ func (err ErrIssueStopwatchNotExist) Unwrap() error {
|
|||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist
|
||||
type ErrIssueStopwatchAlreadyExist struct {
|
||||
UserID int64
|
||||
IssueID int64
|
||||
}
|
||||
|
||||
func (err ErrIssueStopwatchAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("issue stopwatch already exists[uid: %d, issue_id: %d", err.UserID, err.IssueID)
|
||||
}
|
||||
|
||||
func (err ErrIssueStopwatchAlreadyExist) Unwrap() error {
|
||||
return util.ErrAlreadyExist
|
||||
}
|
||||
|
||||
// Stopwatch represents a stopwatch for time tracking.
|
||||
type Stopwatch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
-
|
||||
id: 1
|
||||
uid: 1
|
||||
issue_id: 1
|
||||
is_read: true
|
||||
is_mentioned: false
|
||||
|
||||
-
|
||||
id: 2
|
||||
uid: 2
|
||||
issue_id: 1
|
||||
is_read: true
|
||||
is_mentioned: false
|
||||
|
||||
-
|
||||
id: 3
|
||||
uid: 2
|
||||
issue_id: 1 # duplicated with id 2
|
||||
is_read: false
|
||||
is_mentioned: true
|
|
@ -550,6 +550,8 @@ var migrations = []Migration{
|
|||
NewMigration("Add auth_token table", v1_22.CreateAuthTokenTable),
|
||||
// v282 -> v283
|
||||
NewMigration("Add Index to pull_auto_merge.doer_id", v1_22.AddIndexToPullAutoMergeDoerID),
|
||||
// v283 -> v284
|
||||
NewMigration("Add combined Index to issue_user.uid and issue_id", v1_22.AddCombinedIndexToIssueUser),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
base.MainTest(m)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddCombinedIndexToIssueUser(x *xorm.Engine) error {
|
||||
type OldIssueUser struct {
|
||||
IssueID int64
|
||||
UID int64
|
||||
Cnt int64
|
||||
}
|
||||
|
||||
var duplicatedIssueUsers []OldIssueUser
|
||||
if err := x.SQL("select * from (select issue_id, uid, count(1) as cnt from issue_user group by issue_id, uid) a where a.cnt > 1").
|
||||
Find(&duplicatedIssueUsers); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, issueUser := range duplicatedIssueUsers {
|
||||
if _, err := x.Exec("delete from issue_user where id in (SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?)", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type IssueUser struct {
|
||||
UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID.
|
||||
IssueID int64 `xorm:"INDEX unique(uid_to_issue)"`
|
||||
}
|
||||
|
||||
return x.Sync(&IssueUser{})
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_AddCombinedIndexToIssueUser(t *testing.T) {
|
||||
type IssueUser struct { // old struct
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX"` // User ID.
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
IsRead bool
|
||||
IsMentioned bool
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(IssueUser))
|
||||
defer deferable()
|
||||
|
||||
assert.NoError(t, AddCombinedIndexToIssueUser(x))
|
||||
}
|
|
@ -57,7 +57,7 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
if _, err := db.GetEngine(ctx).ID(ou.ID).Delete(ou); err != nil {
|
||||
if _, err := db.DeleteByID[organization.OrgUser](ctx, ou.ID); err != nil {
|
||||
return err
|
||||
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil {
|
||||
return err
|
||||
|
|
|
@ -344,9 +344,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
|
|||
}
|
||||
}
|
||||
|
||||
if _, err := db.DeleteByBean(ctx, &asymkey_model.DeployKey{
|
||||
ID: key.ID,
|
||||
}); err != nil {
|
||||
if _, err := db.DeleteByID[asymkey_model.DeployKey](ctx, key.ID); err != nil {
|
||||
return fmt.Errorf("delete deploy key [%d]: %w", key.ID, err)
|
||||
}
|
||||
|
||||
|
@ -355,7 +353,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
|
|||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
if err = asymkey_model.DeletePublicKeys(ctx, key.KeyID); err != nil {
|
||||
if _, err = db.DeleteByID[asymkey_model.PublicKey](ctx, key.KeyID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,14 +68,6 @@ func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
var delRepoArchiver = new(RepoArchiver)
|
||||
|
||||
// DeleteRepoArchiver delete archiver
|
||||
func DeleteRepoArchiver(ctx context.Context, archiver *RepoArchiver) error {
|
||||
_, err := db.GetEngine(ctx).ID(archiver.ID).Delete(delRepoArchiver)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRepoArchiver get an archiver
|
||||
func GetRepoArchiver(ctx context.Context, repoID int64, tp git.ArchiveType, commitID string) (*RepoArchiver, error) {
|
||||
var archiver RepoArchiver
|
||||
|
@ -100,12 +92,6 @@ func ExistsRepoArchiverWithStoragePath(ctx context.Context, storagePath string)
|
|||
return db.GetEngine(ctx).Exist(archiver)
|
||||
}
|
||||
|
||||
// AddRepoArchiver adds an archiver
|
||||
func AddRepoArchiver(ctx context.Context, archiver *RepoArchiver) error {
|
||||
_, err := db.GetEngine(ctx).Insert(archiver)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepoArchiverStatus updates archiver's status
|
||||
func UpdateRepoArchiverStatus(ctx context.Context, archiver *RepoArchiver) error {
|
||||
_, err := db.GetEngine(ctx).ID(archiver.ID).Cols("status").Update(archiver)
|
||||
|
@ -114,6 +100,7 @@ func UpdateRepoArchiverStatus(ctx context.Context, archiver *RepoArchiver) error
|
|||
|
||||
// DeleteAllRepoArchives deletes all repo archives records
|
||||
func DeleteAllRepoArchives(ctx context.Context) error {
|
||||
// 1=1 to enforce delete all data, otherwise it will delete nothing
|
||||
_, err := db.GetEngine(ctx).Where("1=1").Delete(new(RepoArchiver))
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -34,12 +34,13 @@ type PushMirror struct {
|
|||
}
|
||||
|
||||
type PushMirrorOptions struct {
|
||||
db.ListOptions
|
||||
ID int64
|
||||
RepoID int64
|
||||
RemoteName string
|
||||
}
|
||||
|
||||
func (opts *PushMirrorOptions) toConds() builder.Cond {
|
||||
func (opts PushMirrorOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
|
@ -75,12 +76,6 @@ func (m *PushMirror) GetRemoteName() string {
|
|||
return m.RemoteName
|
||||
}
|
||||
|
||||
// InsertPushMirror inserts a push-mirror to database
|
||||
func InsertPushMirror(ctx context.Context, m *PushMirror) error {
|
||||
_, err := db.GetEngine(ctx).Insert(m)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdatePushMirror updates the push-mirror
|
||||
func UpdatePushMirror(ctx context.Context, m *PushMirror) error {
|
||||
_, err := db.GetEngine(ctx).ID(m.ID).AllCols().Update(m)
|
||||
|
@ -95,23 +90,12 @@ func UpdatePushMirrorInterval(ctx context.Context, m *PushMirror) error {
|
|||
|
||||
func DeletePushMirrors(ctx context.Context, opts PushMirrorOptions) error {
|
||||
if opts.RepoID > 0 {
|
||||
_, err := db.GetEngine(ctx).Where(opts.toConds()).Delete(&PushMirror{})
|
||||
_, err := db.Delete[PushMirror](ctx, opts)
|
||||
return err
|
||||
}
|
||||
return util.NewInvalidArgumentErrorf("repoID required and must be set")
|
||||
}
|
||||
|
||||
func GetPushMirror(ctx context.Context, opts PushMirrorOptions) (*PushMirror, error) {
|
||||
mirror := &PushMirror{}
|
||||
exist, err := db.GetEngine(ctx).Where(opts.toConds()).Get(mirror)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !exist {
|
||||
return nil, ErrPushMirrorNotExist
|
||||
}
|
||||
return mirror, nil
|
||||
}
|
||||
|
||||
// GetPushMirrorsByRepoID returns push-mirror information of a repository.
|
||||
func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) {
|
||||
sess := db.GetEngine(ctx).Where("repo_id = ?", repoID)
|
||||
|
|
|
@ -20,20 +20,20 @@ func TestPushMirrorsIterate(t *testing.T) {
|
|||
|
||||
now := timeutil.TimeStampNow()
|
||||
|
||||
repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
|
||||
db.Insert(db.DefaultContext, &repo_model.PushMirror{
|
||||
RemoteName: "test-1",
|
||||
LastUpdateUnix: now,
|
||||
Interval: 1,
|
||||
})
|
||||
|
||||
long, _ := time.ParseDuration("24h")
|
||||
repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
|
||||
db.Insert(db.DefaultContext, &repo_model.PushMirror{
|
||||
RemoteName: "test-2",
|
||||
LastUpdateUnix: now,
|
||||
Interval: long,
|
||||
})
|
||||
|
||||
repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
|
||||
db.Insert(db.DefaultContext, &repo_model.PushMirror{
|
||||
RemoteName: "test-3",
|
||||
LastUpdateUnix: now,
|
||||
Interval: 0,
|
||||
|
|
|
@ -451,12 +451,6 @@ func SortReleases(rels []*Release) {
|
|||
sort.Sort(sorter)
|
||||
}
|
||||
|
||||
// DeleteReleaseByID deletes a release from database by given ID.
|
||||
func DeleteReleaseByID(ctx context.Context, id int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(id).Delete(new(Release))
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID
|
||||
func UpdateReleasesMigrationsByType(ctx context.Context, gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error {
|
||||
_, err := db.GetEngine(ctx).Table("release").
|
||||
|
@ -510,7 +504,7 @@ func PushUpdateDeleteTag(ctx context.Context, repo *Repository, tagName string)
|
|||
return fmt.Errorf("GetRelease: %w", err)
|
||||
}
|
||||
if rel.IsTag {
|
||||
if _, err = db.GetEngine(ctx).ID(rel.ID).Delete(new(Release)); err != nil {
|
||||
if _, err = db.DeleteByID[Release](ctx, rel.ID); err != nil {
|
||||
return fmt.Errorf("Delete: %w", err)
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -179,6 +180,7 @@ type Repository struct {
|
|||
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
|
||||
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Topics []string `xorm:"TEXT JSON"`
|
||||
ObjectFormatName string `xorm:"-"`
|
||||
|
||||
TrustModel TrustModelType
|
||||
|
||||
|
@ -274,6 +276,10 @@ func (repo *Repository) AfterLoad() {
|
|||
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
|
||||
repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
|
||||
repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns
|
||||
|
||||
// this is a temporary behaviour to support old repos, next step is to store the object format in the database
|
||||
// and read from database so this line could be removed. To not depend on git module, we use a constant variable here
|
||||
repo.ObjectFormatName = "sha1"
|
||||
}
|
||||
|
||||
// LoadAttributes loads attributes of the repository.
|
||||
|
@ -313,7 +319,7 @@ func (repo *Repository) HTMLURL() string {
|
|||
// CommitLink make link to by commit full ID
|
||||
// note: won't check whether it's an right id
|
||||
func (repo *Repository) CommitLink(commitID string) (result string) {
|
||||
if commitID == "" || commitID == "0000000000000000000000000000000000000000" {
|
||||
if git.IsEmptyCommitID(commitID) {
|
||||
result = ""
|
||||
} else {
|
||||
result = repo.Link() + "/commit/" + url.PathEscape(commitID)
|
||||
|
@ -608,25 +614,23 @@ func ComposeHTTPSCloneURL(owner, repo string) string {
|
|||
|
||||
func ComposeSSHCloneURL(ownerName, repoName string) string {
|
||||
sshUser := setting.SSH.User
|
||||
|
||||
// if we have a ipv6 literal we need to put brackets around it
|
||||
// for the git cloning to work.
|
||||
sshDomain := setting.SSH.Domain
|
||||
ip := net.ParseIP(setting.SSH.Domain)
|
||||
if ip != nil && ip.To4() == nil {
|
||||
sshDomain = "[" + setting.SSH.Domain + "]"
|
||||
|
||||
// non-standard port, it must use full URI
|
||||
if setting.SSH.Port != 22 {
|
||||
sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port))
|
||||
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||
}
|
||||
|
||||
if setting.SSH.Port != 22 {
|
||||
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser,
|
||||
net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)),
|
||||
url.PathEscape(ownerName),
|
||||
url.PathEscape(repoName))
|
||||
// for standard port, it can use a shorter URI (without the port)
|
||||
sshHost := sshDomain
|
||||
if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil {
|
||||
sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets
|
||||
}
|
||||
if setting.Repository.UseCompatSSHURI {
|
||||
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||
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, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
|
||||
}
|
||||
|
||||
func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -186,3 +188,32 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||
test(t, "try.gitea.io:user2/repo2.git")
|
||||
})
|
||||
}
|
||||
|
||||
func TestComposeSSHCloneURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.SSH, setting.SSH)()
|
||||
defer test.MockVariableValue(&setting.Repository, setting.Repository)()
|
||||
|
||||
setting.SSH.User = "git"
|
||||
|
||||
// test SSH_DOMAIN
|
||||
setting.SSH.Domain = "domain"
|
||||
setting.SSH.Port = 22
|
||||
setting.Repository.UseCompatSSHURI = false
|
||||
assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
||||
setting.Repository.UseCompatSSHURI = true
|
||||
assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
||||
// test SSH_DOMAIN while use non-standard SSH port
|
||||
setting.SSH.Port = 123
|
||||
setting.Repository.UseCompatSSHURI = false
|
||||
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
||||
setting.Repository.UseCompatSSHURI = true
|
||||
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
||||
|
||||
// test IPv6 SSH_DOMAIN
|
||||
setting.Repository.UseCompatSSHURI = false
|
||||
setting.SSH.Domain = "::1"
|
||||
setting.SSH.Port = 22
|
||||
assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
||||
setting.SSH.Port = 123
|
||||
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
||||
}
|
||||
|
|
|
@ -405,3 +405,13 @@ func syncTopicsInRepository(sess db.Engine, repoID int64) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountOrphanedAttachments returns the number of topics that don't belong to any repository.
|
||||
func CountOrphanedTopics(ctx context.Context) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("repo_count = 0").Count(new(Topic))
|
||||
}
|
||||
|
||||
// DeleteOrphanedAttachments delete all topics that don't belong to any repository.
|
||||
func DeleteOrphanedTopics(ctx context.Context) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("repo_count = 0").Delete(new(Topic))
|
||||
}
|
||||
|
|
|
@ -131,9 +131,7 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
|
|||
for i := 0; i < len(uploads); i++ {
|
||||
ids[i] = uploads[i].ID
|
||||
}
|
||||
if _, err = db.GetEngine(ctx).
|
||||
In("id", ids).
|
||||
Delete(new(Upload)); err != nil {
|
||||
if err = db.DeleteByIDs[Upload](ctx, ids...); err != nil {
|
||||
return fmt.Errorf("delete uploads: %w", err)
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue