mirror of
https://github.com/go-gitea/gitea
synced 2026-02-06 01:20:55 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcb0f3a90f | ||
|
|
c99374b151 | ||
|
|
b643b2ca9c | ||
|
|
94f86964b4 | ||
|
|
1f29cfa683 | ||
|
|
1cedf36d30 | ||
|
|
7da85fa0c3 | ||
|
|
0ac8b774e9 | ||
|
|
762d4245fb | ||
|
|
3571cbba34 | ||
|
|
6d60d4e554 | ||
|
|
31208fe9a1 | ||
|
|
4bdb8dd9cc | ||
|
|
24d6aacc7e | ||
|
|
d1a55aabc9 | ||
|
|
f1c5d33d3e | ||
|
|
acc0fd22d8 | ||
|
|
fe1b11b639 | ||
|
|
80d7288ea4 | ||
|
|
2d1202b32c | ||
|
|
9112ce22a4 | ||
|
|
2bdc38e592 | ||
|
|
f155cf60d3 | ||
|
|
0f5e07f538 | ||
|
|
368e9e0f1b | ||
|
|
d6cf261be8 | ||
|
|
7c555b2231 | ||
|
|
981ab48503 | ||
|
|
ec37ea5945 | ||
|
|
8ad331c9d2 | ||
|
|
dbabdf6d71 | ||
|
|
2d1a7e1cd4 | ||
|
|
df5558135b | ||
|
|
f329982b6e | ||
|
|
cb1a4da5c2 | ||
|
|
39cbca0f95 | ||
|
|
9aadc25bc1 | ||
|
|
b94370504f | ||
|
|
4fd8ac0653 | ||
|
|
59354d7135 | ||
|
|
0b97463cef | ||
|
|
fa431b377d | ||
|
|
8a97cdd91b | ||
|
|
3e9475b3b2 | ||
|
|
9be9042479 | ||
|
|
9451781ebe | ||
|
|
88f6f7579c | ||
|
|
fcd055c34a | ||
|
|
a57568bad7 | ||
|
|
8e07d53fe4 | ||
|
|
a758337046 | ||
|
|
2cf1515f5c | ||
|
|
a075285f24 | ||
|
|
2517da90aa | ||
|
|
060026995a | ||
|
|
0f265a2489 | ||
|
|
0d04f70d6a | ||
|
|
2122743093 | ||
|
|
3a29712e0a | ||
|
|
81d3dc1da5 | ||
|
|
c5fe09db72 |
+63
-3
@@ -2,9 +2,69 @@
|
||||
|
||||
This changelog goes through all the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.20.2](https://github.com/go-gitea/gitea/releases/tag/1.20.2) - 2023-07-29
|
||||
## [1.20.3](https://github.com/go-gitea/gitea/releases/tag/v1.20.3) - 2023-08-20
|
||||
|
||||
* BREAKING
|
||||
* Fix the wrong derive path (#26271) (#26318)
|
||||
* SECURITY
|
||||
* Fix API leaking Usermail if not logged in (#25097) (#26350)
|
||||
* FEATURES
|
||||
* Add ThreadID parameter for Telegram webhooks (#25996) (#26480)
|
||||
* ENHANCEMENTS
|
||||
* Add minimum polyfill to support "relative-time-element" in PaleMoon (#26575) (#26578)
|
||||
* Fix dark theme highlight for "NameNamespace" (#26519) (#26527)
|
||||
* Detect ogg mime-type as audio or video (#26494) (#26505)
|
||||
* Use `object-fit: contain` for oauth2 custom icons (#26493) (#26498)
|
||||
* Move dropzone progress bar to bottom to show filename when uploading (#26492) (#26497)
|
||||
* Remove last newline from config file (#26468) (#26471)
|
||||
* Minio: add missing region on client initialization (#26412) (#26438)
|
||||
* Add pull request review request webhook event (#26401) (#26407)
|
||||
* Fix text truncate (#26354) (#26384)
|
||||
* Fix incorrect color of selected assignees when create issue (#26324) (#26372)
|
||||
* Display human-readable text instead of cryptic filemodes (#26352) (#26358)
|
||||
* Hide `last indexed SHA` when a repo could not be indexed yet (#26340) (#26345)
|
||||
* Fix the topic validation rule and suport dots (#26286) (#26303)
|
||||
* Fix due date rendering the wrong date in issue (#26268) (#26274)
|
||||
* Don't autosize textarea in diff view (#26233) (#26244)
|
||||
* Fix commit compare style (#26209) (#26226)
|
||||
* Warn instead of reporting an error when a webhook cannot be found (#26039) (#26211)
|
||||
* BUGFIXES
|
||||
* Use "input" event instead of "keyup" event for migration form (#26602) (#26605)
|
||||
* Do not use deprecated log config options by default (#26592) (#26600)
|
||||
* Fix "issueReposQueryPattern does not match query" (#26556) (#26564)
|
||||
* Sync repo's IsEmpty status correctly (#26517) (#26560)
|
||||
* Fix project filter bugs (#26490) (#26558)
|
||||
* Use `hidden` over `clip` for text truncation (#26520) (#26522)
|
||||
* Set "type=button" for editor's toolbar buttons (#26510) (#26518)
|
||||
* Fix NuGet search endpoints (#25613) (#26499)
|
||||
* Fix storage path logic especially for relative paths (#26441) (#26481)
|
||||
* Close stdout correctly for "git blame" (#26470) (#26473)
|
||||
* Check first if minio bucket exists before trying to create it (#26420) (#26465)
|
||||
* Avoiding accessing undefined tributeValues #26461 (#26462)
|
||||
* Call git.InitSimple for runRepoSyncReleases (#26396) (#26450)
|
||||
* Add transaction when creating pull request created dirty data (#26259) (#26437)
|
||||
* Fix wrong middleware sequence (#26428) (#26436)
|
||||
* Fix admin queue page title and fix CI failures (#26409) (#26421)
|
||||
* Introduce ctx.PathParamRaw to avoid incorrect unescaping (#26392) (#26405)
|
||||
* Bypass MariaDB performance bug of the "IN" sub-query, fix incorrect IssueIndex (#26279) (#26368)
|
||||
* Fix incorrect CLI exit code and duplicate error message (#26346) (#26347)
|
||||
* Prevent newline errors with Debian packages (#26332) (#26342)
|
||||
* Fix bug with sqlite load read (#26305) (#26339)
|
||||
* Make git batch operations use parent context timeout instead of default timeout (#26325) (#26330)
|
||||
* Support getting changed files when commit ID is `EmptySHA` (#26290) (#26316)
|
||||
* Clarify the logger's MODE config option (#26267) (#26281)
|
||||
* Use shared template for webhook icons (#26242) (#26246)
|
||||
* Fix pull request check list is limited (#26179) (#26245)
|
||||
* Fix attachment clipboard copy on insecure origin (#26224) (#26231)
|
||||
* Fix access check for org-level project (#26182) (#26223)
|
||||
* MISC
|
||||
* Improve profile readme rendering (#25988) (#26453)
|
||||
* [docs] Add missing backtick in quickstart.zh-cn.md (#26349) (#26357)
|
||||
* Upgrade x/net to 0.13.0 (#26301)
|
||||
|
||||
## [1.20.2](https://github.com/go-gitea/gitea/releases/tag/v1.20.2) - 2023-07-29
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Calculate MAX_WORKERS default value by CPU number (#26177) (#26183)
|
||||
@@ -32,7 +92,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Fix wrong workflow status when rerun a job in an already finished workflow (#26119) (#26124)
|
||||
* Fix duplicated url prefix on issue context menu (#26066) (#26067)
|
||||
|
||||
## [1.20.1](https://github.com/go-gitea/gitea/releases/tag/1.20.1) - 2023-07-22
|
||||
## [1.20.1](https://github.com/go-gitea/gitea/releases/tag/v1.20.1) - 2023-07-22
|
||||
|
||||
* SECURITY
|
||||
* Disallow dangerous URL schemes (#25960) (#25964)
|
||||
|
||||
@@ -348,6 +348,10 @@ func runRepoSyncReleases(_ *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := git.InitSimple(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("Synchronizing repository releases (this may take a while)")
|
||||
for page := 1; ; page++ {
|
||||
repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func RunMainApp(app *cli.App, args ...string) error {
|
||||
err := app.Run(args)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
|
||||
// the cli package should already have output the error message, so just exit
|
||||
cli.OsExiter(1)
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
|
||||
cli.OsExiter(1)
|
||||
return err
|
||||
}
|
||||
@@ -4,9 +4,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -14,3 +21,64 @@ func TestMain(m *testing.M) {
|
||||
GiteaRootPath: "..",
|
||||
})
|
||||
}
|
||||
|
||||
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
|
||||
app := cli.NewApp()
|
||||
app.HelpName = "gitea"
|
||||
testCmd := cli.Command{Name: "test-cmd", Action: testCmdAction}
|
||||
app.Commands = append(app.Commands, testCmd)
|
||||
return app
|
||||
}
|
||||
|
||||
type runResult struct {
|
||||
Stdout string
|
||||
Stderr string
|
||||
ExitCode int
|
||||
}
|
||||
|
||||
func runTestApp(app *cli.App, args ...string) (runResult, error) {
|
||||
outBuf := new(strings.Builder)
|
||||
errBuf := new(strings.Builder)
|
||||
app.Writer = outBuf
|
||||
app.ErrWriter = errBuf
|
||||
exitCode := -1
|
||||
defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)()
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {
|
||||
if exitCode == -1 {
|
||||
exitCode = code // save the exit code once and then reset the writer (to simulate the exit)
|
||||
app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard
|
||||
}
|
||||
})()
|
||||
err := RunMainApp(app, args...)
|
||||
return runResult{outBuf.String(), errBuf.String(), exitCode}, err
|
||||
}
|
||||
|
||||
func TestCliCmdError(t *testing.T) {
|
||||
app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
|
||||
r, err := runTestApp(app, "./gitea", "test-cmd")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 1, r.ExitCode)
|
||||
assert.Equal(t, "", r.Stdout)
|
||||
assert.Equal(t, "Command error: normal error\n", r.Stderr)
|
||||
|
||||
app = newTestApp(func(ctx *cli.Context) error { return cli.NewExitError("exit error", 2) })
|
||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 2, r.ExitCode)
|
||||
assert.Equal(t, "", r.Stdout)
|
||||
assert.Equal(t, "exit error\n", r.Stderr)
|
||||
|
||||
app = newTestApp(func(ctx *cli.Context) error { return nil })
|
||||
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 1, r.ExitCode)
|
||||
assert.EqualValues(t, "Incorrect Usage: flag provided but not defined: -no-such\n\nNAME:\n gitea test-cmd - \n\nUSAGE:\n gitea test-cmd [arguments...]\n", r.Stdout)
|
||||
assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
|
||||
|
||||
app = newTestApp(func(ctx *cli.Context) error { return nil })
|
||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
|
||||
assert.Equal(t, "", r.Stdout)
|
||||
assert.Equal(t, "", r.Stderr)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ PATH = /data/gitea/attachments
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = info
|
||||
ROUTER = console
|
||||
ROOT_PATH = /data/gitea/log
|
||||
|
||||
[security]
|
||||
|
||||
@@ -79,8 +79,9 @@ SMTP_PORT = 465
|
||||
FROM = example.user@gmail.com
|
||||
USER = example.user
|
||||
PASSWD = `***`
|
||||
PROTOCOL = smtp
|
||||
IS_TLS_ENABLED = true
|
||||
PROTOCOL = smtps ; Gitea >= 1.19.0
|
||||
; PROTOCOL = smtp ; Gitea < 1.19.0
|
||||
; IS_TLS_ENABLED = true ; Gitea < 1.19.0
|
||||
```
|
||||
|
||||
Note that you'll need to create and use an [App password](https://support.google.com/accounts/answer/185833?hl=en) by enabling 2FA on your Google
|
||||
|
||||
@@ -102,8 +102,11 @@ MODE = file, file-error
|
||||
|
||||
; by default, the "file" mode will record logs to %(log.ROOT_PATH)/gitea.log, so we don't need to set it
|
||||
; [log.file]
|
||||
; by default, the MODE (actually it's the output writer of this logger) is taken from the section name, so we don't need to set it either
|
||||
; MODE = file
|
||||
|
||||
[log.file-error]
|
||||
MODE = file
|
||||
LEVEL = Error
|
||||
FILE_NAME = file-error.log
|
||||
```
|
||||
|
||||
@@ -40,7 +40,7 @@ apk add gitea
|
||||
|
||||
## Arch Linux
|
||||
|
||||
The rolling release distribution has [Gitea](https://www.archlinux.org/packages/community/x86_64/gitea/) in their official community repository and package updates are provided with new Gitea releases.
|
||||
The rolling release distribution has [Gitea](https://www.archlinux.org/packages/extra/x86_64/gitea/) in their official extra repository and package updates are provided with new Gitea releases.
|
||||
|
||||
```sh
|
||||
pacman -S gitea
|
||||
|
||||
@@ -17,16 +17,20 @@ menu:
|
||||
|
||||
# Upgrade from an old Gitea
|
||||
|
||||
To update Gitea, download a newer version, stop the old one, perform a backup, and run the new one.
|
||||
Every time a Gitea instance starts up, it checks whether a database migration should be run.
|
||||
If a database migration is required, Gitea will take some time to complete the upgrade and then serve.
|
||||
Follow below steps to ensure a smooth upgrade to a new Gitea version.
|
||||
|
||||
## Check the Changelog for breaking changes
|
||||
|
||||
To make Gitea better, some breaking changes are unavoidable, especially for big milestone releases.
|
||||
Before upgrade, please read the [Changelog on Gitea blog](https://blog.gitea.io/)
|
||||
Before upgrading, please read the [Changelog on Gitea blog](https://blog.gitea.com/)
|
||||
and check whether the breaking changes affect your Gitea instance.
|
||||
|
||||
## Verify there are no deprecated configuration options
|
||||
|
||||
New versions of Gitea often come with changed configuration syntax or options which are usually displayed for
|
||||
at least one release cycle inside at the top of the Site Administration panel. If these warnings are not
|
||||
resolved, Gitea may refuse to start in the following version.
|
||||
|
||||
## Backup for downgrade
|
||||
|
||||
Gitea keeps compatibility for patch versions whose first two fields are the same (`a.b.x` -> `a.b.y`),
|
||||
@@ -60,6 +64,11 @@ Backup steps:
|
||||
If you are using cloud services or filesystems with snapshot feature,
|
||||
a snapshot for the Gitea data volume and related object storage is more convenient.
|
||||
|
||||
After all of steps have been prepared, download the new version, stop the application, perform a backup and
|
||||
then start the new application. On each startup, Gitea verifies that the database is up to date and will automtically
|
||||
perform any necessary migrations. Depending on the size of the database, this can take some additional time on the
|
||||
first launch during which the application will be unavailable.
|
||||
|
||||
## Upgrade with Docker
|
||||
|
||||
* `docker pull` the latest Gitea release.
|
||||
|
||||
@@ -15,16 +15,20 @@ menu:
|
||||
|
||||
# 从旧版 Gitea 升级
|
||||
|
||||
想要升级 Gitea,只需要下载新版,停止运行旧版,进行数据备份,然后运行新版就好。
|
||||
每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。
|
||||
如果需要进行数据库迁移,Gitea 会花一些时间完成升级然后继续服务。
|
||||
在升级之前,您需要做如下的准备工作。
|
||||
|
||||
## 为重大变更检查更新日志
|
||||
|
||||
为了让 Gitea 变得更好,进行重大变更是不可避免的,尤其是一些里程碑更新的发布。
|
||||
在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.io/)
|
||||
在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.com/)
|
||||
并检查重大变更是否会影响你的 Gitea 实例。
|
||||
|
||||
## 在控制面板中检查过期的配置项
|
||||
|
||||
一些配置项可能会在后续版本中过期,你需要在控制面板中检查他们。如果不解决过期的配置项,
|
||||
Gitea也许会在升级后无法重启。你可以访问 https://docs.gitea.com 获得要升级的版本
|
||||
对应的文档来修改你的配置文件。
|
||||
|
||||
## 降级前的备份
|
||||
|
||||
Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`),
|
||||
@@ -56,6 +60,10 @@ Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`
|
||||
如果你在使用云服务或拥有快照功能的文件系统,
|
||||
最好对 Gitea 的数据盘及相关资料存储进行一次快照。
|
||||
|
||||
在所有上述步骤准备妥当之后,要升级 Gitea,只需要下载新版,停止运行旧版,进行数据备份,然后运行新版就好。
|
||||
每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。
|
||||
如果需要进行数据库迁移,Gitea 会花一些时间完成升级然后继续服务。
|
||||
|
||||
## 从 Docker 升级
|
||||
|
||||
* `docker pull` 拉取 Gitea 的最新发布版。
|
||||
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
|
||||
请注意,演示文件中包含一些表情符号。
|
||||
请确保您的数据库支持它们,特别是在使用MySQL时。
|
||||
如果字符集不是`utf8mb4,将出现错误,例如`Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\x8E\\x89 T...' for column 'name' at row 1`。
|
||||
如果字符集不是`utf8mb4`,将出现错误,例如`Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\x8E\\x89 T...' for column 'name' at row 1`。
|
||||
有关更多信息,请参阅[数据库准备工作](installation/database-preparation.md#mysql)。
|
||||
|
||||
或者,您可以从演示文件中删除所有表情符号,然后再尝试一次。
|
||||
|
||||
@@ -21,8 +21,8 @@ In Gitea `1.13`, support for [agit](https://git-repo.info/en/2020/03/agit-flow-a
|
||||
|
||||
## Creating PRs with Agit
|
||||
|
||||
Agit allows to create PRs while pushing code to the remote repo. \
|
||||
This can be done by pushing to the branch followed by a specific refspec (a location identifier known to git). \
|
||||
Agit allows to create PRs while pushing code to the remote repo.
|
||||
This can be done by pushing to the branch followed by a specific refspec (a location identifier known to git).
|
||||
The following example illustrates this:
|
||||
|
||||
```shell
|
||||
|
||||
@@ -109,7 +109,7 @@ require (
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/image v0.7.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/net v0.13.0
|
||||
golang.org/x/oauth2 v0.8.0
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/text v0.11.0
|
||||
|
||||
@@ -1269,8 +1269,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
||||
@@ -146,11 +146,11 @@ func main() {
|
||||
app.Commands = append(app.Commands, subCmdWithIni...)
|
||||
app.Commands = append(app.Commands, subCmdStandalone...)
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err)
|
||||
cli.OsExiter = func(code int) {
|
||||
log.GetManager().Close()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
|
||||
log.GetManager().Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
||||
if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil {
|
||||
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
|
||||
} else if len(gots) != 1 {
|
||||
return nil, false, fmt.Errorf("workflow of job %d: not signle workflow", job.ID)
|
||||
return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID)
|
||||
} else {
|
||||
_, workflowJob = gots[0].Job()
|
||||
}
|
||||
|
||||
@@ -685,18 +685,34 @@ func NotifyWatchersActions(acts []*Action) error {
|
||||
}
|
||||
|
||||
// DeleteIssueActions delete all actions related with issueID
|
||||
func DeleteIssueActions(ctx context.Context, repoID, issueID int64) error {
|
||||
func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64) error {
|
||||
// delete actions assigned to this issue
|
||||
subQuery := builder.Select("`id`").
|
||||
From("`comment`").
|
||||
Where(builder.Eq{"`issue_id`": issueID})
|
||||
if _, err := db.GetEngine(ctx).In("comment_id", subQuery).Delete(&Action{}); err != nil {
|
||||
return err
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
// MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
|
||||
// so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
|
||||
var lastCommentID int64
|
||||
commentIDs := make([]int64, 0, db.DefaultMaxInSize)
|
||||
for {
|
||||
commentIDs = commentIDs[:0]
|
||||
err := e.Select("`id`").Table(&issues_model.Comment{}).
|
||||
Where(builder.Eq{"issue_id": issueID}).And("`id` > ?", lastCommentID).
|
||||
OrderBy("`id`").Limit(db.DefaultMaxInSize).
|
||||
Find(&commentIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(commentIDs) == 0 {
|
||||
break
|
||||
} else if _, err = db.GetEngine(ctx).In("comment_id", commentIDs).Delete(&Action{}); err != nil {
|
||||
return err
|
||||
} else {
|
||||
lastCommentID = commentIDs[len(commentIDs)-1]
|
||||
}
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Table("action").Where("repo_id = ?", repoID).
|
||||
_, err := e.Where("repo_id = ?", repoID).
|
||||
In("op_type", ActionCreateIssue, ActionCreatePullRequest).
|
||||
Where("content LIKE ?", strconv.FormatInt(issueID, 10)+"|%").
|
||||
Where("content LIKE ?", strconv.FormatInt(issueIndex, 10)+"|%"). // "IssueIndex|content..."
|
||||
Delete(&Action{})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package activities_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
@@ -284,3 +285,36 @@ func TestConsistencyUpdateAction(t *testing.T) {
|
||||
assert.NoError(t, db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions))
|
||||
unittest.CheckConsistencyFor(t, &activities_model.Action{})
|
||||
}
|
||||
|
||||
func TestDeleteIssueActions(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// load an issue
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issue_model.Issue{ID: 4})
|
||||
assert.NotEqualValues(t, issue.ID, issue.Index) // it needs to use different ID/Index to test the DeleteIssueActions to delete some actions by IssueIndex
|
||||
|
||||
// insert a comment
|
||||
err := db.Insert(db.DefaultContext, &issue_model.Comment{Type: issue_model.CommentTypeComment, IssueID: issue.ID})
|
||||
assert.NoError(t, err)
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issue_model.Comment{Type: issue_model.CommentTypeComment, IssueID: issue.ID})
|
||||
|
||||
// truncate action table and insert some actions
|
||||
err = db.TruncateBeans(db.DefaultContext, &activities_model.Action{})
|
||||
assert.NoError(t, err)
|
||||
err = db.Insert(db.DefaultContext, &activities_model.Action{
|
||||
OpType: activities_model.ActionCommentIssue,
|
||||
CommentID: comment.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
err = db.Insert(db.DefaultContext, &activities_model.Action{
|
||||
OpType: activities_model.ActionCreateIssue,
|
||||
RepoID: issue.RepoID,
|
||||
Content: fmt.Sprintf("%d|content...", issue.Index),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// assert that the actions exist, then delete them
|
||||
unittest.AssertCount(t, &activities_model.Action{}, 2)
|
||||
assert.NoError(t, activities_model.DeleteIssueActions(db.DefaultContext, issue.RepoID, issue.ID, issue.Index))
|
||||
unittest.AssertCount(t, &activities_model.Action{}, 0)
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ func createIssueNotification(ctx context.Context, userID int64, issue *issues_mo
|
||||
}
|
||||
|
||||
func updateIssueNotification(ctx context.Context, userID, issueID, commentID, updatedByID int64) error {
|
||||
notification, err := getIssueNotification(ctx, userID, issueID)
|
||||
notification, err := GetIssueNotification(ctx, userID, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -331,7 +331,8 @@ func updateIssueNotification(ctx context.Context, userID, issueID, commentID, up
|
||||
return err
|
||||
}
|
||||
|
||||
func getIssueNotification(ctx context.Context, userID, issueID int64) (*Notification, error) {
|
||||
// GetIssueNotification return the notification about an issue
|
||||
func GetIssueNotification(ctx context.Context, userID, issueID int64) (*Notification, error) {
|
||||
notification := new(Notification)
|
||||
_, err := db.GetEngine(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
@@ -742,7 +743,7 @@ func GetUIDsAndNotificationCounts(since, until timeutil.TimeStamp) ([]UserIDCoun
|
||||
|
||||
// SetIssueReadBy sets issue to be read by given user.
|
||||
func SetIssueReadBy(ctx context.Context, issueID, userID int64) error {
|
||||
if err := issues_model.UpdateIssueUserByRead(userID, issueID); err != nil {
|
||||
if err := issues_model.UpdateIssueUserByRead(ctx, userID, issueID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -750,7 +751,7 @@ func SetIssueReadBy(ctx context.Context, issueID, userID int64) error {
|
||||
}
|
||||
|
||||
func setIssueNotificationStatusReadIfUnread(ctx context.Context, userID, issueID int64) error {
|
||||
notification, err := getIssueNotification(ctx, userID, issueID)
|
||||
notification, err := GetIssueNotification(ctx, userID, issueID)
|
||||
// ignore if not exists
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -762,7 +763,7 @@ func setIssueNotificationStatusReadIfUnread(ctx context.Context, userID, issueID
|
||||
|
||||
notification.Status = NotificationStatusRead
|
||||
|
||||
_, err = db.GetEngine(ctx).ID(notification.ID).Update(notification)
|
||||
_, err = db.GetEngine(ctx).ID(notification.ID).Cols("status").Update(notification)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package activities_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
@@ -109,3 +110,16 @@ func TestUpdateNotificationStatuses(t *testing.T) {
|
||||
unittest.AssertExistsAndLoadBean(t,
|
||||
&activities_model.Notification{ID: notfPinned.ID, Status: activities_model.NotificationStatusPinned})
|
||||
}
|
||||
|
||||
func TestSetIssueReadBy(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
assert.NoError(t, db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||
return activities_model.SetIssueReadBy(ctx, issue.ID, user.ID)
|
||||
}))
|
||||
|
||||
nt, err := activities_model.GetIssueNotification(db.DefaultContext, user.ID, issue.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, activities_model.NotificationStatusRead, nt.Status)
|
||||
}
|
||||
|
||||
@@ -20,3 +20,19 @@ func BuildCaseInsensitiveLike(key, value string) builder.Cond {
|
||||
}
|
||||
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
|
||||
}
|
||||
|
||||
// BuilderDialect returns the xorm.Builder dialect of the engine
|
||||
func BuilderDialect() string {
|
||||
switch {
|
||||
case setting.Database.Type.IsMySQL():
|
||||
return builder.MYSQL
|
||||
case setting.Database.Type.IsSQLite3():
|
||||
return builder.SQLITE
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
return builder.POSTGRES
|
||||
case setting.Database.Type.IsMSSQL():
|
||||
return builder.MSSQL
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,3 +280,9 @@
|
||||
team_id: 20
|
||||
type: 9 # package
|
||||
access_mode: 2
|
||||
|
||||
-
|
||||
id: 48
|
||||
team_id: 2
|
||||
type: 8
|
||||
access_mode: 2
|
||||
|
||||
@@ -283,9 +283,9 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
|
||||
Where("repo_id = ?", repoID).And("sha = ?", sha).
|
||||
Select("max( id ) as id").
|
||||
GroupBy("context_hash").OrderBy("max( id ) desc")
|
||||
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
|
||||
if !listOptions.IsListAll() {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
}
|
||||
count, err := sess.FindAndCount(&ids)
|
||||
if err != nil {
|
||||
return nil, count, err
|
||||
|
||||
@@ -155,6 +155,18 @@ func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sess
|
||||
return sess
|
||||
}
|
||||
|
||||
func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||
if opts.ProjectID > 0 { // specific project
|
||||
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
|
||||
And("project_issue.project_id=?", opts.ProjectID)
|
||||
} else if opts.ProjectID == db.NoConditionID { // show those that are in no project
|
||||
sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue").And(builder.Neq{"project_id": 0})))
|
||||
}
|
||||
// opts.ProjectID == 0 means all projects,
|
||||
// do not need to apply any condition
|
||||
return sess
|
||||
}
|
||||
|
||||
func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||
if len(opts.RepoIDs) == 1 {
|
||||
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]}
|
||||
@@ -213,12 +225,7 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||
sess.And(builder.Lte{"issue.updated_unix": opts.UpdatedBeforeUnix})
|
||||
}
|
||||
|
||||
if opts.ProjectID > 0 {
|
||||
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
|
||||
And("project_issue.project_id=?", opts.ProjectID)
|
||||
} else if opts.ProjectID == db.NoConditionID { // show those that are in no project
|
||||
sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue")))
|
||||
}
|
||||
applyProjectCondition(sess, opts)
|
||||
|
||||
if opts.ProjectBoardID != 0 {
|
||||
if opts.ProjectBoardID > 0 {
|
||||
|
||||
@@ -133,10 +133,7 @@ func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, err
|
||||
|
||||
applyMilestoneCondition(sess, opts)
|
||||
|
||||
if opts.ProjectID > 0 {
|
||||
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
|
||||
And("project_issue.project_id=?", opts.ProjectID)
|
||||
}
|
||||
applyProjectCondition(sess, opts)
|
||||
|
||||
if opts.AssigneeID > 0 {
|
||||
applyAssigneeCondition(sess, opts.AssigneeID)
|
||||
|
||||
@@ -55,8 +55,8 @@ func NewIssueUsers(ctx context.Context, repo *repo_model.Repository, issue *Issu
|
||||
}
|
||||
|
||||
// UpdateIssueUserByRead updates issue-user relation for reading.
|
||||
func UpdateIssueUserByRead(uid, issueID int64) error {
|
||||
_, err := db.GetEngine(db.DefaultContext).Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
|
||||
func UpdateIssueUserByRead(ctx context.Context, uid, issueID int64) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -40,13 +40,13 @@ func TestUpdateIssueUserByRead(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
|
||||
assert.NoError(t, issues_model.UpdateIssueUserByRead(4, issue.ID))
|
||||
assert.NoError(t, issues_model.UpdateIssueUserByRead(db.DefaultContext, 4, issue.ID))
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
|
||||
|
||||
assert.NoError(t, issues_model.UpdateIssueUserByRead(4, issue.ID))
|
||||
assert.NoError(t, issues_model.UpdateIssueUserByRead(db.DefaultContext, 4, issue.ID))
|
||||
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
|
||||
|
||||
assert.NoError(t, issues_model.UpdateIssueUserByRead(unittest.NonexistentID, unittest.NonexistentID))
|
||||
assert.NoError(t, issues_model.UpdateIssueUserByRead(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID))
|
||||
}
|
||||
|
||||
func TestUpdateIssueUsersByMentions(t *testing.T) {
|
||||
|
||||
@@ -531,13 +531,12 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
|
||||
}
|
||||
|
||||
// NewPullRequest creates new pull request with labels for repository.
|
||||
func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
|
||||
ctx, committer, err := db.TxContext(outerCtx)
|
||||
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
ctx.WithContext(outerCtx)
|
||||
|
||||
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -87,14 +87,14 @@ func TestLoadRequestedReviewers(t *testing.T) {
|
||||
user1, err := user_model.GetUserByID(db.DefaultContext, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
comment, err := issues_model.AddReviewRequest(issue, user1, &user_model.User{})
|
||||
comment, err := issues_model.AddReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, comment)
|
||||
|
||||
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
|
||||
assert.Len(t, pull.RequestedReviewers, 1)
|
||||
|
||||
comment, err = issues_model.RemoveReviewRequest(issue, user1, &user_model.User{})
|
||||
comment, err = issues_model.RemoveReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, comment)
|
||||
|
||||
|
||||
@@ -557,8 +557,8 @@ func InsertReviews(reviews []*Review) error {
|
||||
}
|
||||
|
||||
// AddReviewRequest add a review request from one reviewer
|
||||
func AddReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -612,8 +612,8 @@ func AddReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment,
|
||||
}
|
||||
|
||||
// RemoveReviewRequest remove a review request from one reviewer
|
||||
func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -673,8 +673,8 @@ func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64)
|
||||
}
|
||||
|
||||
// AddTeamReviewRequest add a review request from one team
|
||||
func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
func AddTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -732,8 +732,8 @@ func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_
|
||||
}
|
||||
|
||||
// RemoveTeamReviewRequest remove a review request from one team
|
||||
func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nuget
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// SearchVersions gets all versions of packages matching the search options
|
||||
func SearchVersions(ctx context.Context, opts *packages_model.PackageSearchOptions) ([]*packages_model.PackageVersion, int64, error) {
|
||||
cond := toConds(opts)
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
total, err := e.
|
||||
Where(cond).
|
||||
Count(&packages_model.Package{})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
inner := builder.
|
||||
Dialect(db.BuilderDialect()). // builder needs the sql dialect to build the Limit() below
|
||||
Select("*").
|
||||
From("package").
|
||||
Where(cond).
|
||||
OrderBy("package.name ASC")
|
||||
if opts.Paginator != nil {
|
||||
skip, take := opts.GetSkipTake()
|
||||
inner = inner.Limit(take, skip)
|
||||
}
|
||||
|
||||
sess := e.
|
||||
Where(opts.ToConds()).
|
||||
Table("package_version").
|
||||
Join("INNER", inner, "package.id = package_version.package_id")
|
||||
|
||||
pvs := make([]*packages_model.PackageVersion, 0, 10)
|
||||
return pvs, total, sess.Find(&pvs)
|
||||
}
|
||||
|
||||
// CountPackages counts all packages matching the search options
|
||||
func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(toConds(opts)).
|
||||
Count(&packages_model.Package{})
|
||||
}
|
||||
|
||||
func toConds(opts *packages_model.PackageSearchOptions) builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.is_internal": opts.IsInternal.IsTrue(),
|
||||
"package.owner_id": opts.OwnerID,
|
||||
"package.type": packages_model.TypeNuGet,
|
||||
}
|
||||
if opts.Name.Value != "" {
|
||||
if opts.Name.ExactMatch {
|
||||
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
|
||||
} else {
|
||||
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
|
||||
}
|
||||
}
|
||||
return cond
|
||||
}
|
||||
@@ -189,7 +189,7 @@ type PackageSearchOptions struct {
|
||||
db.Paginator
|
||||
}
|
||||
|
||||
func (opts *PackageSearchOptions) toConds() builder.Cond {
|
||||
func (opts *PackageSearchOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if !opts.IsInternal.IsNone() {
|
||||
cond = builder.Eq{
|
||||
@@ -283,7 +283,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
||||
// SearchVersions gets all versions of packages matching the search options
|
||||
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Where(opts.toConds()).
|
||||
Where(opts.ToConds()).
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id")
|
||||
|
||||
@@ -300,7 +300,7 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package
|
||||
|
||||
// SearchLatestVersions gets the latest version of every package matching the search options
|
||||
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||
cond := opts.toConds().
|
||||
cond := opts.ToConds().
|
||||
And(builder.Expr("pv2.id IS NULL"))
|
||||
|
||||
joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))")
|
||||
@@ -328,7 +328,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
|
||||
// ExistVersion checks if a version matching the search options exist
|
||||
func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.toConds()).
|
||||
Where(opts.ToConds()).
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Exist(new(PackageVersion))
|
||||
@@ -337,7 +337,7 @@ func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error)
|
||||
// CountVersions counts all versions of packages matching the search options
|
||||
func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.toConds()).
|
||||
Where(opts.ToConds()).
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Count(new(PackageVersion))
|
||||
|
||||
@@ -22,7 +22,7 @@ func init() {
|
||||
db.RegisterModel(new(RepoTopic))
|
||||
}
|
||||
|
||||
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||||
var topicPattern = regexp.MustCompile(`^[a-z0-9][-.a-z0-9]*$`)
|
||||
|
||||
// Topic represents a topic of repositories
|
||||
type Topic struct {
|
||||
|
||||
@@ -69,6 +69,7 @@ func TestAddTopic(t *testing.T) {
|
||||
func TestTopicValidator(t *testing.T) {
|
||||
assert.True(t, repo_model.ValidateTopic("12345"))
|
||||
assert.True(t, repo_model.ValidateTopic("2-test"))
|
||||
assert.True(t, repo_model.ValidateTopic("foo.bar"))
|
||||
assert.True(t, repo_model.ValidateTopic("test-3"))
|
||||
assert.True(t, repo_model.ValidateTopic("first"))
|
||||
assert.True(t, repo_model.ValidateTopic("second-test-topic"))
|
||||
@@ -77,4 +78,5 @@ func TestTopicValidator(t *testing.T) {
|
||||
assert.False(t, repo_model.ValidateTopic("$fourth-test,topic"))
|
||||
assert.False(t, repo_model.ValidateTopic("-fifth-test-topic"))
|
||||
assert.False(t, repo_model.ValidateTopic("sixth-go-project-topic-with-excess-length"))
|
||||
assert.False(t, repo_model.ValidateTopic(".foo"))
|
||||
}
|
||||
|
||||
+6
-1
@@ -203,11 +203,16 @@ func UpdateUserTheme(u *User, themeName string) error {
|
||||
return UpdateUserCols(db.DefaultContext, u, "theme")
|
||||
}
|
||||
|
||||
// GetPlaceholderEmail returns an noreply email
|
||||
func (u *User) GetPlaceholderEmail() string {
|
||||
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
|
||||
}
|
||||
|
||||
// GetEmail returns an noreply email, if the user has set to keep his
|
||||
// email address private, otherwise the primary email address.
|
||||
func (u *User) GetEmail() string {
|
||||
if u.KeepEmailPrivate {
|
||||
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
|
||||
return u.GetPlaceholderEmail()
|
||||
}
|
||||
return u.Email
|
||||
}
|
||||
|
||||
@@ -143,6 +143,10 @@ func (b *Base) Params(p string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *Base) PathParamRaw(p string) string {
|
||||
return chi.URLParam(b.Req, strings.TrimPrefix(p, ":"))
|
||||
}
|
||||
|
||||
// ParamsInt64 returns the param on route as int64
|
||||
func (b *Base) ParamsInt64(p string) int64 {
|
||||
v, _ := strconv.ParseInt(b.Params(p), 10, 64)
|
||||
|
||||
@@ -74,6 +74,8 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
|
||||
Stdin: batchStdinReader,
|
||||
Stdout: batchStdoutWriter,
|
||||
Stderr: &stderr,
|
||||
|
||||
UseContextTimeout: true,
|
||||
})
|
||||
if err != nil {
|
||||
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
|
||||
@@ -124,6 +126,8 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
|
||||
Stdin: batchStdinReader,
|
||||
Stdout: batchStdoutWriter,
|
||||
Stderr: &stderr,
|
||||
|
||||
UseContextTimeout: true,
|
||||
})
|
||||
if err != nil {
|
||||
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
|
||||
|
||||
+12
-5
@@ -5,11 +5,14 @@ package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// BlamePart represents block of blame - continuous lines with one sha
|
||||
@@ -115,15 +118,19 @@ func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*B
|
||||
done := make(chan error, 1)
|
||||
|
||||
go func(cmd *Command, dir string, stdout io.WriteCloser, done chan error) {
|
||||
if err := cmd.Run(&RunOpts{
|
||||
stderr := bytes.Buffer{}
|
||||
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
|
||||
err := cmd.Run(&RunOpts{
|
||||
UseContextTimeout: true,
|
||||
Dir: dir,
|
||||
Stdout: stdout,
|
||||
Stderr: os.Stderr,
|
||||
}); err == nil {
|
||||
stdout.Close()
|
||||
}
|
||||
Stderr: &stderr,
|
||||
})
|
||||
done <- err
|
||||
_ = stdout.Close()
|
||||
if err != nil {
|
||||
log.Error("Error running git blame (dir: %v): %v, stderr: %v", repoPath, err, stderr.String())
|
||||
}
|
||||
}(cmd, repoPath, stdout, done)
|
||||
|
||||
bufferedReader := bufio.NewReader(reader)
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error {
|
||||
// IsEmpty Check if repository is empty.
|
||||
func (repo *Repository) IsEmpty() (bool, error) {
|
||||
var errbuf, output strings.Builder
|
||||
if err := NewCommand(repo.Ctx).AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("show-ref", "--head", "^HEAD$").
|
||||
if err := NewCommand(repo.Ctx).AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all").
|
||||
Run(&RunOpts{
|
||||
Dir: repo.Path,
|
||||
Stdout: &output,
|
||||
|
||||
@@ -280,8 +280,16 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
|
||||
}
|
||||
|
||||
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
|
||||
// If base is undefined empty SHA (zeros), it only returns the files changed in the head commit
|
||||
// If base is the SHA of an empty tree (EmptyTreeSHA), it returns the files changes from the initial commit to the head commit
|
||||
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
|
||||
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
|
||||
if base == EmptySHA {
|
||||
cmd.AddDynamicArguments(head)
|
||||
} else {
|
||||
cmd.AddDynamicArguments(base, head)
|
||||
}
|
||||
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -119,3 +119,42 @@ func TestReadWritePullHead(t *testing.T) {
|
||||
err = repo.RemoveReference(PullPrefix + "1/head")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetCommitFilesChanged(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
testCases := []struct {
|
||||
base, head string
|
||||
files []string
|
||||
}{
|
||||
{
|
||||
EmptySHA,
|
||||
"95bb4d39648ee7e325106df01a621c530863a653",
|
||||
[]string{"file1.txt"},
|
||||
},
|
||||
{
|
||||
EmptySHA,
|
||||
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||
[]string{"file2.txt"},
|
||||
},
|
||||
{
|
||||
"95bb4d39648ee7e325106df01a621c530863a653",
|
||||
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||
[]string{"file2.txt"},
|
||||
},
|
||||
{
|
||||
EmptyTreeSHA,
|
||||
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||
[]string{"file1.txt", "file2.txt"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
changedFiles, err := repo.GetFilesChangedBetween(tc.base, tc.head)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, tc.files, changedFiles)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,10 +11,10 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EmptySHA defines empty git SHA
|
||||
// EmptySHA defines empty git SHA (undefined, non-existent)
|
||||
const EmptySHA = "0000000000000000000000000000000000000000"
|
||||
|
||||
// EmptyTreeSHA is the SHA of an empty tree
|
||||
// EmptyTreeSHA is the SHA of an empty tree, the root of all git repositories
|
||||
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
||||
|
||||
// SHAFullLength is the full length of a git SHA
|
||||
|
||||
@@ -172,19 +172,10 @@ func ParseControlFile(r io.Reader) (*Package, error) {
|
||||
value := strings.TrimSpace(parts[1])
|
||||
switch key {
|
||||
case "Package":
|
||||
if !namePattern.MatchString(value) {
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
p.Name = value
|
||||
case "Version":
|
||||
if !versionPattern.MatchString(value) {
|
||||
return nil, ErrInvalidVersion
|
||||
}
|
||||
p.Version = value
|
||||
case "Architecture":
|
||||
if value == "" {
|
||||
return nil, ErrInvalidArchitecture
|
||||
}
|
||||
p.Architecture = value
|
||||
case "Maintainer":
|
||||
a, err := mail.ParseAddress(value)
|
||||
@@ -208,13 +199,23 @@ func ParseControlFile(r io.Reader) (*Package, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !namePattern.MatchString(p.Name) {
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
if !versionPattern.MatchString(p.Version) {
|
||||
return nil, ErrInvalidVersion
|
||||
}
|
||||
if p.Architecture == "" {
|
||||
return nil, ErrInvalidArchitecture
|
||||
}
|
||||
|
||||
dependencies := strings.Split(depends.String(), ",")
|
||||
for i := range dependencies {
|
||||
dependencies[i] = strings.TrimSpace(dependencies[i])
|
||||
}
|
||||
p.Metadata.Dependencies = dependencies
|
||||
|
||||
p.Control = control.String()
|
||||
p.Control = strings.TrimSpace(control.String())
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -131,6 +132,11 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
|
||||
log.Error("Error reading file for %s : %v", envKey, envValue, err)
|
||||
continue
|
||||
}
|
||||
if bytes.HasSuffix(fileContent, []byte("\r\n")) {
|
||||
fileContent = fileContent[:len(fileContent)-2]
|
||||
} else if bytes.HasSuffix(fileContent, []byte("\n")) {
|
||||
fileContent = fileContent[:len(fileContent)-1]
|
||||
}
|
||||
keyValue = string(fileContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -99,4 +99,19 @@ key = old
|
||||
changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
|
||||
assert.True(t, changed)
|
||||
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
|
||||
|
||||
cfg, _ = NewConfigProviderFromData("")
|
||||
_ = os.WriteFile(tmpFile, []byte("value-from-file\n"), 0o644)
|
||||
EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
|
||||
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
|
||||
|
||||
cfg, _ = NewConfigProviderFromData("")
|
||||
_ = os.WriteFile(tmpFile, []byte("value-from-file\r\n"), 0o644)
|
||||
EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
|
||||
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
|
||||
|
||||
cfg, _ = NewConfigProviderFromData("")
|
||||
_ = os.WriteFile(tmpFile, []byte("value-from-file\n\n"), 0o644)
|
||||
EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
|
||||
assert.Equal(t, "value-from-file\n", cfg.Section("sec").Key("key").String())
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ func loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (wri
|
||||
writerMode.WriterOption = writerOption
|
||||
default:
|
||||
if !log.HasEventWriter(writerType) {
|
||||
return "", "", writerMode, fmt.Errorf("invalid log writer type (mode): %s", writerType)
|
||||
return "", "", writerMode, fmt.Errorf("invalid log writer type (mode): %s, maybe it needs something like 'MODE=file' in [log.%s] section", writerType, modeName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+150
-73
@@ -84,102 +84,179 @@ func getDefaultStorageSection(rootCfg ConfigProvider) ConfigSection {
|
||||
return storageSec
|
||||
}
|
||||
|
||||
// getStorage will find target section and extra special section first and then read override
|
||||
// items from extra section
|
||||
func getStorage(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (*Storage, error) {
|
||||
if name == "" {
|
||||
return nil, errors.New("no name for storage")
|
||||
}
|
||||
|
||||
var targetSec ConfigSection
|
||||
if typ != "" {
|
||||
var err error
|
||||
targetSec, err = rootCfg.GetSection(storageSectionName + "." + typ)
|
||||
if err != nil {
|
||||
if !IsValidStorageType(StorageType(typ)) {
|
||||
return nil, fmt.Errorf("get section via storage type %q failed: %v", typ, err)
|
||||
}
|
||||
}
|
||||
if targetSec != nil {
|
||||
targetType := targetSec.Key("STORAGE_TYPE").String()
|
||||
if targetType == "" {
|
||||
if !IsValidStorageType(StorageType(typ)) {
|
||||
return nil, fmt.Errorf("unknow storage type %q", typ)
|
||||
}
|
||||
targetSec.Key("STORAGE_TYPE").SetValue(typ)
|
||||
} else if !IsValidStorageType(StorageType(targetType)) {
|
||||
return nil, fmt.Errorf("unknow storage type %q for section storage.%v", targetType, typ)
|
||||
}
|
||||
}
|
||||
targetSec, tp, err := getStorageTargetSection(rootCfg, name, typ, sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storageNameSec, _ := rootCfg.GetSection(storageSectionName + "." + name)
|
||||
overrideSec := getStorageOverrideSection(rootCfg, targetSec, sec, tp, name)
|
||||
|
||||
if targetSec == nil {
|
||||
targetSec = sec
|
||||
targetType := targetSec.Key("STORAGE_TYPE").String()
|
||||
switch targetType {
|
||||
case string(LocalStorageType):
|
||||
return getStorageForLocal(targetSec, overrideSec, tp, name)
|
||||
case string(MinioStorageType):
|
||||
return getStorageForMinio(targetSec, overrideSec, tp, name)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported storage type %q", targetType)
|
||||
}
|
||||
if targetSec == nil {
|
||||
targetSec = storageNameSec
|
||||
}
|
||||
if targetSec == nil {
|
||||
targetSec = getDefaultStorageSection(rootCfg)
|
||||
} else {
|
||||
targetType := targetSec.Key("STORAGE_TYPE").String()
|
||||
switch {
|
||||
case targetType == "":
|
||||
if targetSec.Key("PATH").String() == "" {
|
||||
targetSec = getDefaultStorageSection(rootCfg)
|
||||
} else {
|
||||
targetSec.Key("STORAGE_TYPE").SetValue("local")
|
||||
}
|
||||
default:
|
||||
newTargetSec, _ := rootCfg.GetSection(storageSectionName + "." + targetType)
|
||||
if newTargetSec == nil {
|
||||
if !IsValidStorageType(StorageType(targetType)) {
|
||||
return nil, fmt.Errorf("invalid storage section %s.%q", storageSectionName, targetType)
|
||||
}
|
||||
} else {
|
||||
targetSec = newTargetSec
|
||||
if IsValidStorageType(StorageType(targetType)) {
|
||||
tp := targetSec.Key("STORAGE_TYPE").String()
|
||||
if tp == "" {
|
||||
targetSec.Key("STORAGE_TYPE").SetValue(targetType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type targetSecType int
|
||||
|
||||
const (
|
||||
targetSecIsTyp targetSecType = iota // target section is [storage.type] which the type from parameter
|
||||
targetSecIsStorage // target section is [storage]
|
||||
targetSecIsDefault // target section is the default value
|
||||
targetSecIsStorageWithName // target section is [storage.name]
|
||||
targetSecIsSec // target section is from the name seciont [name]
|
||||
)
|
||||
|
||||
func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) {
|
||||
targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
|
||||
if err != nil {
|
||||
if !IsValidStorageType(StorageType(typ)) {
|
||||
return nil, 0, fmt.Errorf("get section via storage type %q failed: %v", typ, err)
|
||||
}
|
||||
// if typ is a valid storage type, but there is no [storage.local] or [storage.minio] section
|
||||
// it's not an error
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
targetType := targetSec.Key("STORAGE_TYPE").String()
|
||||
if !IsValidStorageType(StorageType(targetType)) {
|
||||
return nil, fmt.Errorf("invalid storage type %q", targetType)
|
||||
if targetType == "" {
|
||||
if !IsValidStorageType(StorageType(typ)) {
|
||||
return nil, 0, fmt.Errorf("unknow storage type %q", typ)
|
||||
}
|
||||
targetSec.Key("STORAGE_TYPE").SetValue(typ)
|
||||
} else if !IsValidStorageType(StorageType(targetType)) {
|
||||
return nil, 0, fmt.Errorf("unknow storage type %q for section storage.%v", targetType, typ)
|
||||
}
|
||||
|
||||
var storage Storage
|
||||
storage.Type = StorageType(targetType)
|
||||
return targetSec, targetSecIsTyp, nil
|
||||
}
|
||||
|
||||
switch targetType {
|
||||
case string(LocalStorageType):
|
||||
storage.Path = ConfigSectionKeyString(targetSec, "PATH", filepath.Join(AppDataPath, name))
|
||||
if !filepath.IsAbs(storage.Path) {
|
||||
storage.Path = filepath.Join(AppWorkPath, storage.Path)
|
||||
func getStorageTargetSection(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (ConfigSection, targetSecType, error) {
|
||||
// check typ first
|
||||
if typ == "" {
|
||||
if sec != nil { // check sec's type secondly
|
||||
typ = sec.Key("STORAGE_TYPE").String()
|
||||
if IsValidStorageType(StorageType(typ)) {
|
||||
if targetSec, _ := rootCfg.GetSection(storageSectionName + "." + typ); targetSec == nil {
|
||||
return sec, targetSecIsSec, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case string(MinioStorageType):
|
||||
storage.MinioConfig.BasePath = name + "/"
|
||||
}
|
||||
|
||||
if err := targetSec.MapTo(&storage.MinioConfig); err != nil {
|
||||
return nil, fmt.Errorf("map minio config failed: %v", err)
|
||||
if typ != "" {
|
||||
targetSec, tp, err := getStorageSectionByType(rootCfg, typ)
|
||||
if targetSec != nil || err != nil {
|
||||
return targetSec, tp, err
|
||||
}
|
||||
// extra config section will be read SERVE_DIRECT, PATH, MINIO_BASE_PATH to override the targetsec
|
||||
extraConfigSec := sec
|
||||
if extraConfigSec == nil {
|
||||
extraConfigSec = storageNameSec
|
||||
}
|
||||
|
||||
// check stoarge name thirdly
|
||||
targetSec, _ := rootCfg.GetSection(storageSectionName + "." + name)
|
||||
if targetSec != nil {
|
||||
targetType := targetSec.Key("STORAGE_TYPE").String()
|
||||
switch {
|
||||
case targetType == "":
|
||||
if targetSec.Key("PATH").String() == "" { // both storage type and path are empty, use default
|
||||
return getDefaultStorageSection(rootCfg), targetSecIsDefault, nil
|
||||
}
|
||||
|
||||
targetSec.Key("STORAGE_TYPE").SetValue("local")
|
||||
default:
|
||||
targetSec, tp, err := getStorageSectionByType(rootCfg, targetType)
|
||||
if targetSec != nil || err != nil {
|
||||
return targetSec, tp, err
|
||||
}
|
||||
}
|
||||
|
||||
if extraConfigSec != nil {
|
||||
storage.MinioConfig.ServeDirect = ConfigSectionKeyBool(extraConfigSec, "SERVE_DIRECT", storage.MinioConfig.ServeDirect)
|
||||
storage.MinioConfig.BasePath = ConfigSectionKeyString(extraConfigSec, "MINIO_BASE_PATH", storage.MinioConfig.BasePath)
|
||||
storage.MinioConfig.Bucket = ConfigSectionKeyString(extraConfigSec, "MINIO_BUCKET", storage.MinioConfig.Bucket)
|
||||
return targetSec, targetSecIsStorageWithName, nil
|
||||
}
|
||||
|
||||
return getDefaultStorageSection(rootCfg), targetSecIsDefault, nil
|
||||
}
|
||||
|
||||
// getStorageOverrideSection override section will be read SERVE_DIRECT, PATH, MINIO_BASE_PATH, MINIO_BUCKET to override the targetsec when possible
|
||||
func getStorageOverrideSection(rootConfig ConfigProvider, targetSec, sec ConfigSection, targetSecType targetSecType, name string) ConfigSection {
|
||||
if targetSecType == targetSecIsSec {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sec != nil {
|
||||
return sec
|
||||
}
|
||||
|
||||
if targetSecType != targetSecIsStorageWithName {
|
||||
nameSec, _ := rootConfig.GetSection(storageSectionName + "." + name)
|
||||
return nameSec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) {
|
||||
storage := Storage{
|
||||
Type: StorageType(targetSec.Key("STORAGE_TYPE").String()),
|
||||
}
|
||||
|
||||
targetPath := ConfigSectionKeyString(targetSec, "PATH", "")
|
||||
var fallbackPath string
|
||||
if targetPath == "" { // no path
|
||||
fallbackPath = filepath.Join(AppDataPath, name)
|
||||
} else {
|
||||
if tp == targetSecIsStorage || tp == targetSecIsDefault {
|
||||
fallbackPath = filepath.Join(targetPath, name)
|
||||
} else {
|
||||
fallbackPath = targetPath
|
||||
}
|
||||
if !filepath.IsAbs(fallbackPath) {
|
||||
fallbackPath = filepath.Join(AppDataPath, fallbackPath)
|
||||
}
|
||||
}
|
||||
|
||||
if overrideSec == nil { // no override section
|
||||
storage.Path = fallbackPath
|
||||
} else {
|
||||
storage.Path = ConfigSectionKeyString(overrideSec, "PATH", "")
|
||||
if storage.Path == "" { // overrideSec has no path
|
||||
storage.Path = fallbackPath
|
||||
} else if !filepath.IsAbs(storage.Path) {
|
||||
if targetPath == "" {
|
||||
storage.Path = filepath.Join(AppDataPath, storage.Path)
|
||||
} else {
|
||||
storage.Path = filepath.Join(targetPath, storage.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &storage, nil
|
||||
}
|
||||
|
||||
func getStorageForMinio(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) {
|
||||
var storage Storage
|
||||
storage.Type = StorageType(targetSec.Key("STORAGE_TYPE").String())
|
||||
if err := targetSec.MapTo(&storage.MinioConfig); err != nil {
|
||||
return nil, fmt.Errorf("map minio config failed: %v", err)
|
||||
}
|
||||
|
||||
if storage.MinioConfig.BasePath == "" {
|
||||
storage.MinioConfig.BasePath = name + "/"
|
||||
}
|
||||
|
||||
if overrideSec != nil {
|
||||
storage.MinioConfig.ServeDirect = ConfigSectionKeyBool(overrideSec, "SERVE_DIRECT", storage.MinioConfig.ServeDirect)
|
||||
storage.MinioConfig.BasePath = ConfigSectionKeyString(overrideSec, "MINIO_BASE_PATH", storage.MinioConfig.BasePath)
|
||||
storage.MinioConfig.Bucket = ConfigSectionKeyString(overrideSec, "MINIO_BUCKET", storage.MinioConfig.Bucket)
|
||||
}
|
||||
return &storage, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -26,12 +27,15 @@ MINIO_BUCKET = gitea-storage
|
||||
|
||||
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||
assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket)
|
||||
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
|
||||
|
||||
assert.NoError(t, loadLFSFrom(cfg))
|
||||
assert.EqualValues(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket)
|
||||
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
|
||||
|
||||
assert.NoError(t, loadAvatarsFrom(cfg))
|
||||
assert.EqualValues(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket)
|
||||
assert.EqualValues(t, "avatars/", Avatar.Storage.MinioConfig.BasePath)
|
||||
}
|
||||
|
||||
func Test_getStorageUseOtherNameAsType(t *testing.T) {
|
||||
@@ -48,9 +52,11 @@ MINIO_BUCKET = gitea-storage
|
||||
|
||||
assert.NoError(t, loadAttachmentFrom(cfg))
|
||||
assert.EqualValues(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket)
|
||||
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
|
||||
|
||||
assert.NoError(t, loadLFSFrom(cfg))
|
||||
assert.EqualValues(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket)
|
||||
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageType(t *testing.T) {
|
||||
@@ -90,3 +96,319 @@ STORAGE_TYPE = minio
|
||||
assert.EqualValues(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket)
|
||||
assert.EqualValues(t, "repo-avatars/", RepoAvatar.Storage.MinioConfig.BasePath)
|
||||
}
|
||||
|
||||
type testLocalStoragePathCase struct {
|
||||
loader func(rootCfg ConfigProvider) error
|
||||
storagePtr **Storage
|
||||
expectedPath string
|
||||
}
|
||||
|
||||
func testLocalStoragePath(t *testing.T, appDataPath, iniStr string, cases []testLocalStoragePathCase) {
|
||||
cfg, err := NewConfigProviderFromData(iniStr)
|
||||
assert.NoError(t, err)
|
||||
AppDataPath = appDataPath
|
||||
for _, c := range cases {
|
||||
assert.NoError(t, c.loader(cfg))
|
||||
storage := *c.storagePtr
|
||||
|
||||
assert.EqualValues(t, "local", storage.Type)
|
||||
assert.True(t, filepath.IsAbs(storage.Path))
|
||||
assert.EqualValues(t, filepath.Clean(c.expectedPath), filepath.Clean(storage.Path))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocal(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage]
|
||||
STORAGE_TYPE = local
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/repo-archive"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPath(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage]
|
||||
STORAGE_TYPE = local
|
||||
PATH = /data/gitea
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalRelativePath(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage]
|
||||
STORAGE_TYPE = local
|
||||
PATH = storages
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/appdata/storages/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/appdata/storages/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/storages/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/appdata/storages/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/storages/repo-archive"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/appdata/storages/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/appdata/storages/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/storages/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPathOverride(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage]
|
||||
STORAGE_TYPE = local
|
||||
PATH = /data/gitea
|
||||
|
||||
[repo-archive]
|
||||
PATH = /data/gitea/the-archives-dir
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPathOverrideEmpty(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage]
|
||||
STORAGE_TYPE = local
|
||||
PATH = /data/gitea
|
||||
|
||||
[repo-archive]
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalRelativePathOverride(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage]
|
||||
STORAGE_TYPE = local
|
||||
PATH = /data/gitea
|
||||
|
||||
[repo-archive]
|
||||
PATH = the-archives-dir
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPathOverride3(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage.repo-archive]
|
||||
STORAGE_TYPE = local
|
||||
PATH = /data/gitea/archives
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPathOverride3_5(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage.repo-archive]
|
||||
STORAGE_TYPE = local
|
||||
PATH = a-relative-path
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/a-relative-path"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPathOverride4(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage.repo-archive]
|
||||
STORAGE_TYPE = local
|
||||
PATH = /data/gitea/archives
|
||||
|
||||
[repo-archive]
|
||||
PATH = /tmp/gitea/archives
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/tmp/gitea/archives"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPathOverride5(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage.repo-archive]
|
||||
STORAGE_TYPE = local
|
||||
PATH = /data/gitea/archives
|
||||
|
||||
[repo-archive]
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
|
||||
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
|
||||
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
|
||||
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"},
|
||||
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
|
||||
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
|
||||
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageInheritStorageTypeLocalPathOverride72(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[repo-archive]
|
||||
STORAGE_TYPE = local
|
||||
PATH = archives
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/archives"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration20(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[repo-archive]
|
||||
STORAGE_TYPE = my_storage
|
||||
PATH = archives
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Error(t, loadRepoArchiveFrom(cfg))
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration21(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage.repo-archive]
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/repo-archive"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration22(t *testing.T) {
|
||||
testLocalStoragePath(t, "/appdata", `
|
||||
[storage.repo-archive]
|
||||
PATH = archives
|
||||
`, []testLocalStoragePathCase{
|
||||
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/archives"},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration23(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[repo-archive]
|
||||
STORAGE_TYPE = minio
|
||||
MINIO_ACCESS_KEY_ID = my_access_key
|
||||
MINIO_SECRET_ACCESS_KEY = my_secret_key
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = getStorage(cfg, "", "", nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.NoError(t, loadRepoArchiveFrom(cfg))
|
||||
cp := RepoArchive.Storage.ToShadowCopy()
|
||||
assert.EqualValues(t, "******", cp.MinioConfig.AccessKeyID)
|
||||
assert.EqualValues(t, "******", cp.MinioConfig.SecretAccessKey)
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration24(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[repo-archive]
|
||||
STORAGE_TYPE = my_archive
|
||||
|
||||
[storage.my_archive]
|
||||
; unsupported, storage type should be defined explicitly
|
||||
PATH = archives
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
assert.Error(t, loadRepoArchiveFrom(cfg))
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration25(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[repo-archive]
|
||||
STORAGE_TYPE = my_archive
|
||||
|
||||
[storage.my_archive]
|
||||
; unsupported, storage type should be known type
|
||||
STORAGE_TYPE = unknown // should be local or minio
|
||||
PATH = archives
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
assert.Error(t, loadRepoArchiveFrom(cfg))
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration26(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[repo-archive]
|
||||
STORAGE_TYPE = minio
|
||||
MINIO_ACCESS_KEY_ID = my_access_key
|
||||
MINIO_SECRET_ACCESS_KEY = my_secret_key
|
||||
; wrong configuration
|
||||
MINIO_USE_SSL = abc
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
// assert.Error(t, loadRepoArchiveFrom(cfg))
|
||||
// FIXME: this should return error but now ini package's MapTo() doesn't check type
|
||||
assert.NoError(t, loadRepoArchiveFrom(cfg))
|
||||
}
|
||||
|
||||
func Test_getStorageConfiguration27(t *testing.T) {
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[storage.repo-archive]
|
||||
STORAGE_TYPE = minio
|
||||
MINIO_ACCESS_KEY_ID = my_access_key
|
||||
MINIO_SECRET_ACCESS_KEY = my_secret_key
|
||||
MINIO_USE_SSL = true
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, loadRepoArchiveFrom(cfg))
|
||||
assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
|
||||
assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey)
|
||||
assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL)
|
||||
assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
|
||||
}
|
||||
|
||||
+7
-2
@@ -17,6 +17,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -164,6 +165,10 @@ func sessionHandler(session ssh.Session) {
|
||||
}
|
||||
|
||||
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||
// FIXME: the "ssh.Context" is not thread-safe, so db operations should use the immutable parent "Context"
|
||||
// TODO: Remove after https://github.com/gliderlabs/ssh/pull/211
|
||||
parentCtx := reflect.ValueOf(ctx).Elem().FieldByName("Context").Interface().(context.Context)
|
||||
|
||||
if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
|
||||
log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
|
||||
}
|
||||
@@ -189,7 +194,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||
// look for the exact principal
|
||||
principalLoop:
|
||||
for _, principal := range cert.ValidPrincipals {
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal)
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal)
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrKeyNotExist(err) {
|
||||
log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal)
|
||||
@@ -246,7 +251,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||
log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
|
||||
}
|
||||
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
|
||||
pkey, err := asymkey_model.SearchPublicKeyByContent(parentCtx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrKeyNotExist(err) {
|
||||
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
|
||||
|
||||
@@ -84,17 +84,22 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
|
||||
Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
|
||||
Secure: config.UseSSL,
|
||||
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
|
||||
Region: config.Location,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, convertMinioErr(err)
|
||||
}
|
||||
|
||||
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{
|
||||
Region: config.Location,
|
||||
}); err != nil {
|
||||
// Check to see if we already own this bucket (which happens if you run this twice)
|
||||
exists, errBucketExists := minioClient.BucketExists(ctx, config.Bucket)
|
||||
if !exists || errBucketExists != nil {
|
||||
// Check to see if we already own this bucket
|
||||
exists, errBucketExists := minioClient.BucketExists(ctx, config.Bucket)
|
||||
if errBucketExists != nil {
|
||||
return nil, convertMinioErr(err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{
|
||||
Region: config.Location,
|
||||
}); err != nil {
|
||||
return nil, convertMinioErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,3 +16,9 @@ func RedirectURL(resp http.ResponseWriter) string {
|
||||
func IsNormalPageCompleted(s string) bool {
|
||||
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
|
||||
}
|
||||
|
||||
func MockVariableValue[T any](p *T, v T) (reset func()) {
|
||||
old := *p
|
||||
*p = v
|
||||
return func() { *p = old }
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (ct SniffedType) IsRepresentableAsText() bool {
|
||||
return ct.IsText() || ct.IsSvgImage()
|
||||
}
|
||||
|
||||
// IsBrowsableType returns whether a non-text type can be displayed in a browser
|
||||
// IsBrowsableBinaryType returns whether a non-text type can be displayed in a browser
|
||||
func (ct SniffedType) IsBrowsableBinaryType() bool {
|
||||
return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio()
|
||||
}
|
||||
@@ -116,6 +116,17 @@ func DetectContentType(data []byte) SniffedType {
|
||||
}
|
||||
}
|
||||
|
||||
if ct == "application/ogg" {
|
||||
dataHead := data
|
||||
if len(dataHead) > 256 {
|
||||
dataHead = dataHead[:256] // only need to do a quick check for the file header
|
||||
}
|
||||
if bytes.Contains(dataHead, []byte("theora")) || bytes.Contains(dataHead, []byte("dirac")) {
|
||||
ct = "video/ogg" // ogg is only used for some video formats, and it's not popular
|
||||
} else {
|
||||
ct = "audio/ogg" // for most cases, it is used as an audio container
|
||||
}
|
||||
}
|
||||
return SniffedType{ct}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package typesniffer
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -121,3 +122,15 @@ func TestDetectContentTypeFromReader(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, st.IsAudio())
|
||||
}
|
||||
|
||||
func TestDetectContentTypeOgg(t *testing.T) {
|
||||
oggAudio, _ := hex.DecodeString("4f67675300020000000000000000352f0000000000007dc39163011e01766f72626973000000000244ac0000000000000071020000000000b8014f6767530000")
|
||||
st, err := DetectContentTypeFromReader(bytes.NewReader(oggAudio))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, st.IsAudio())
|
||||
|
||||
oggVideo, _ := hex.DecodeString("4f676753000200000000000000007d9747ef000000009b59daf3012a807468656f7261030201001e00110001e000010e00020000001e00000001000001000001")
|
||||
st, err = DetectContentTypeFromReader(bytes.NewReader(oggVideo))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, st.IsVideo())
|
||||
}
|
||||
|
||||
@@ -2284,6 +2284,7 @@ settings.tags.protection.none = There are no protected tags.
|
||||
settings.tags.protection.pattern.description = You can use a single name or a glob pattern or regular expression to match multiple tags. Read more in the <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/protected-tags/">protected tags guide</a>.
|
||||
settings.bot_token = Bot Token
|
||||
settings.chat_id = Chat ID
|
||||
settings.thread_id = Thread ID
|
||||
settings.matrix.homeserver_url = Homeserver URL
|
||||
settings.matrix.room_id = Room ID
|
||||
settings.matrix.message_type = Message Type
|
||||
@@ -2484,7 +2485,7 @@ tag.create_success = Tag "%s" has been created.
|
||||
topic.manage_topics = Manage Topics
|
||||
topic.done = Done
|
||||
topic.count_prompt = You cannot select more than 25 topics
|
||||
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
||||
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
|
||||
|
||||
find_file.go_to_file = Go to file
|
||||
find_file.no_matching = No matching file found
|
||||
@@ -3474,3 +3475,12 @@ need_approval_desc = Need approval to run workflows for fork pull request.
|
||||
type-1.display_name = Individual Project
|
||||
type-2.display_name = Repository Project
|
||||
type-3.display_name = Organization Project
|
||||
|
||||
[git.filemode]
|
||||
changed_filemode = %[1]s → %[2]s
|
||||
# Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", …
|
||||
directory = Directory
|
||||
normal_file = Normal file
|
||||
executable_file = Executable file
|
||||
symbolic_link = Symbolic link
|
||||
submodule = Submodule
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
|
||||
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// https://docs.microsoft.com/en-us/nuget/api/service-index#resources
|
||||
@@ -207,9 +210,15 @@ func createSearchResultResponse(l *linkBuilder, totalHits int64, pds []*packages
|
||||
grouped[pd.Package.Name] = append(grouped[pd.Package.Name], pd)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(grouped))
|
||||
for key := range grouped {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
collate.New(language.English, collate.IgnoreCase).SortStrings(keys)
|
||||
|
||||
data := make([]*SearchResult, 0, len(pds))
|
||||
for _, group := range grouped {
|
||||
data = append(data, createSearchResult(l, group))
|
||||
for _, key := range keys {
|
||||
data = append(data, createSearchResult(l, grouped[key]))
|
||||
}
|
||||
|
||||
return &SearchResultResponse{
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
nuget_model "code.gitea.io/gitea/models/packages/nuget"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
@@ -115,7 +116,7 @@ func SearchServiceV2(ctx *context.Context) {
|
||||
skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
|
||||
paginator := db.NewAbsoluteListOptions(skip, take)
|
||||
|
||||
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
Type: packages_model.TypeNuGet,
|
||||
Name: packages_model.SearchValue{
|
||||
@@ -166,9 +167,8 @@ func SearchServiceV2(ctx *context.Context) {
|
||||
|
||||
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
|
||||
func SearchServiceV2Count(ctx *context.Context) {
|
||||
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
Type: packages_model.TypeNuGet,
|
||||
Name: packages_model.SearchValue{
|
||||
Value: getSearchTerm(ctx),
|
||||
},
|
||||
@@ -184,9 +184,8 @@ func SearchServiceV2Count(ctx *context.Context) {
|
||||
|
||||
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
|
||||
func SearchServiceV3(ctx *context.Context) {
|
||||
pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
Type: packages_model.TypeNuGet,
|
||||
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
|
||||
IsInternal: util.OptionalBoolFalse,
|
||||
Paginator: db.NewAbsoluteListOptions(
|
||||
|
||||
@@ -127,7 +127,7 @@ func EditWikiPage(ctx *context.APIContext) {
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
|
||||
|
||||
oldWikiName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
|
||||
oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
|
||||
newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
|
||||
|
||||
if len(newWikiName) == 0 {
|
||||
@@ -231,7 +231,7 @@ func DeleteWikiPage(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
wikiName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
|
||||
wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
|
||||
|
||||
if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
|
||||
if err.Error() == "file does not exist" {
|
||||
@@ -359,7 +359,7 @@ func GetWikiPage(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
// get requested pagename
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
|
||||
|
||||
wikiPage := getWikiPage(ctx, pageName)
|
||||
if !ctx.Written() {
|
||||
@@ -409,7 +409,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// get requested pagename
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
|
||||
if len(pageName) == 0 {
|
||||
pageName = "Home"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func Queues(ctx *context.Context) {
|
||||
if !setting.IsProd {
|
||||
initTestQueueOnce()
|
||||
}
|
||||
ctx.Data["Title"] = ctx.Tr("admin.monitor.queue")
|
||||
ctx.Data["Title"] = ctx.Tr("admin.monitor.queues")
|
||||
ctx.Data["PageIsAdminMonitorQueue"] = true
|
||||
ctx.Data["Queues"] = queue.GetManager().ManagedQueues()
|
||||
ctx.HTML(http.StatusOK, tplQueue)
|
||||
|
||||
@@ -339,7 +339,7 @@ func Diff(ctx *context.Context) {
|
||||
ctx.Data["Commit"] = commit
|
||||
ctx.Data["Diff"] = diff
|
||||
|
||||
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{})
|
||||
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
log.Error("GetLatestCommitStatus: %v", err)
|
||||
}
|
||||
|
||||
@@ -370,7 +370,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||
}
|
||||
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||
}
|
||||
}
|
||||
|
||||
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
|
||||
@@ -763,7 +765,9 @@ func UploadFilePost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||
}
|
||||
}
|
||||
|
||||
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
|
||||
|
||||
@@ -2226,7 +2226,7 @@ func UpdateIssueAssignee(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = issue_service.ToggleAssignee(ctx, issue, ctx.Doer, assigneeID)
|
||||
_, _, err = issue_service.ToggleAssigneeWithNotify(ctx, issue, ctx.Doer, assigneeID)
|
||||
if err != nil {
|
||||
ctx.ServerError("ToggleAssignee", err)
|
||||
return
|
||||
|
||||
@@ -421,7 +421,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
|
||||
|
||||
if len(compareInfo.Commits) != 0 {
|
||||
sha := compareInfo.Commits[0].ID.String()
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptions{})
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
@@ -483,7 +483,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
||||
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
|
||||
return nil
|
||||
}
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
@@ -575,7 +575,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
||||
return nil
|
||||
}
|
||||
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
|
||||
@@ -834,7 +834,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
|
||||
ctx.Data["LatestCommitVerification"] = verification
|
||||
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit)
|
||||
|
||||
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptions{})
|
||||
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
log.Error("GetLatestCommitStatus: %v", err)
|
||||
}
|
||||
|
||||
@@ -425,12 +425,13 @@ func telegramHookParams(ctx *context.Context) webhookParams {
|
||||
|
||||
return webhookParams{
|
||||
Type: webhook_module.TELEGRAM,
|
||||
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)),
|
||||
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)),
|
||||
ContentType: webhook.ContentTypeJSON,
|
||||
WebhookForm: form.WebhookForm,
|
||||
Meta: &webhook_service.TelegramMeta{
|
||||
BotToken: form.BotToken,
|
||||
ChatID: form.ChatID,
|
||||
ThreadID: form.ThreadID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||
ctx.Data["Pages"] = pages
|
||||
|
||||
// get requested page name
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
||||
if len(pageName) == 0 {
|
||||
pageName = "Home"
|
||||
}
|
||||
@@ -333,7 +333,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
|
||||
}
|
||||
|
||||
// get requested pagename
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
||||
if len(pageName) == 0 {
|
||||
pageName = "Home"
|
||||
}
|
||||
@@ -416,7 +416,7 @@ func renderEditPage(ctx *context.Context) {
|
||||
}()
|
||||
|
||||
// get requested pagename
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
|
||||
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
||||
if len(pageName) == 0 {
|
||||
pageName = "Home"
|
||||
}
|
||||
@@ -648,7 +648,7 @@ func WikiRaw(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
providedWebPath := wiki_service.WebPathFromRequest(ctx.Params("*"))
|
||||
providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
||||
providedGitPath := wiki_service.WebPathToGitPath(providedWebPath)
|
||||
var entry *git.TreeEntry
|
||||
if commit != nil {
|
||||
@@ -760,7 +760,7 @@ func EditWikiPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
oldWikiName := wiki_service.WebPathFromRequest(ctx.Params("*"))
|
||||
oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
||||
newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
|
||||
|
||||
if len(form.Message) == 0 {
|
||||
@@ -779,7 +779,7 @@ func EditWikiPost(ctx *context.Context) {
|
||||
|
||||
// DeleteWikiPagePost delete wiki page
|
||||
func DeleteWikiPagePost(ctx *context.Context) {
|
||||
wikiName := wiki_service.WebPathFromRequest(ctx.Params("*"))
|
||||
wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
|
||||
if len(wikiName) == 0 {
|
||||
wikiName = "Home"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) {
|
||||
ctx.Data["Runners"] = runners
|
||||
ctx.Data["Total"] = count
|
||||
ctx.Data["RegistrationToken"] = token.Token
|
||||
ctx.Data["RunnerOnwerID"] = opts.OwnerID
|
||||
ctx.Data["RunnerOwnerID"] = opts.OwnerID
|
||||
ctx.Data["RunnerRepoID"] = opts.RepoID
|
||||
|
||||
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
|
||||
|
||||
@@ -695,7 +695,7 @@ func getRepoIDs(reposQuery string) []int64 {
|
||||
return []int64{}
|
||||
}
|
||||
if !issueReposQueryPattern.MatchString(reposQuery) {
|
||||
log.Warn("issueReposQueryPattern does not match query")
|
||||
log.Warn("issueReposQueryPattern does not match query: %q", reposQuery)
|
||||
return []int64{}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ func Profile(ctx *context.Context) {
|
||||
profileContent, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
GitRepo: gitRepo,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
}, bytes)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
|
||||
+8
-7
@@ -262,9 +262,10 @@ func registerRoutes(m *web.Route) {
|
||||
}
|
||||
}
|
||||
|
||||
reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode) func(ctx *context.Context) {
|
||||
reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode, ignoreGlobal bool) func(ctx *context.Context) {
|
||||
return func(ctx *context.Context) {
|
||||
if unitType.UnitGlobalDisabled() {
|
||||
// only check global disabled units when ignoreGlobal is false
|
||||
if !ignoreGlobal && unitType.UnitGlobalDisabled() {
|
||||
ctx.NotFound(unitType.String(), nil)
|
||||
return
|
||||
}
|
||||
@@ -828,7 +829,7 @@ func registerRoutes(m *web.Route) {
|
||||
m.Group("", func() {
|
||||
m.Get("", org.Projects)
|
||||
m.Get("/{id}", org.ViewProject)
|
||||
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead))
|
||||
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
|
||||
m.Group("", func() { //nolint:dupl
|
||||
m.Get("/new", org.RenderNewProject)
|
||||
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
|
||||
@@ -849,17 +850,17 @@ func registerRoutes(m *web.Route) {
|
||||
m.Post("/move", org.MoveIssues)
|
||||
})
|
||||
})
|
||||
}, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite), func(ctx *context.Context) {
|
||||
}, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.Context) {
|
||||
if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID {
|
||||
ctx.NotFound("NewProject", nil)
|
||||
return
|
||||
}
|
||||
})
|
||||
}, repo.MustEnableProjects)
|
||||
})
|
||||
|
||||
m.Group("", func() {
|
||||
m.Get("/code", user.CodeSearch)
|
||||
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead))
|
||||
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
|
||||
}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
|
||||
|
||||
// ***** Release Attachment Download without Signin
|
||||
@@ -1118,7 +1119,7 @@ func registerRoutes(m *web.Route) {
|
||||
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
|
||||
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
||||
repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true))
|
||||
m.Get("/releases/attachments/{uuid}", repo.GetAttachment, repo.MustBeNotEmpty, reqRepoReleaseReader)
|
||||
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, reqRepoReleaseReader, repo.GetAttachment)
|
||||
m.Group("/releases", func() {
|
||||
m.Get("/new", repo.NewRelease)
|
||||
m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)
|
||||
|
||||
@@ -75,7 +75,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
|
||||
}
|
||||
ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
|
||||
state := toCommitStatus(job.Status)
|
||||
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil {
|
||||
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true}); err == nil {
|
||||
for _, v := range statuses {
|
||||
if v.Context == ctxname {
|
||||
if v.State == state {
|
||||
|
||||
@@ -56,7 +56,7 @@ func (p *AuthSourceProvider) DisplayName() string {
|
||||
|
||||
func (p *AuthSourceProvider) IconHTML() template.HTML {
|
||||
if p.iconURL != "" {
|
||||
img := fmt.Sprintf(`<img class="gt-mr-3" width="20" height="20" src="%s" alt="%s">`,
|
||||
img := fmt.Sprintf(`<img class="gt-object-contain gt-mr-3" width="20" height="20" src="%s" alt="%s">`,
|
||||
html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
|
||||
)
|
||||
return template.HTML(img)
|
||||
|
||||
@@ -51,7 +51,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
|
||||
ID: user.ID,
|
||||
UserName: user.Name,
|
||||
FullName: user.FullName,
|
||||
Email: user.GetEmail(),
|
||||
Email: user.GetPlaceholderEmail(),
|
||||
AvatarURL: user.AvatarLink(ctx),
|
||||
Created: user.CreatedUnix.AsTime(),
|
||||
Restricted: user.IsRestricted,
|
||||
|
||||
@@ -352,6 +352,7 @@ func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) b
|
||||
type NewTelegramHookForm struct {
|
||||
BotToken string `binding:"Required"`
|
||||
ChatID string `binding:"Required"`
|
||||
ThreadID string
|
||||
WebhookForm
|
||||
}
|
||||
|
||||
|
||||
@@ -427,6 +427,23 @@ func (diffFile *DiffFile) ShouldBeHidden() bool {
|
||||
return diffFile.IsGenerated || diffFile.IsViewed
|
||||
}
|
||||
|
||||
func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
|
||||
switch mode {
|
||||
case "040000":
|
||||
return "git.filemode.directory"
|
||||
case "100644":
|
||||
return "git.filemode.normal_file"
|
||||
case "100755":
|
||||
return "git.filemode.executable_file"
|
||||
case "120000":
|
||||
return "git.filemode.symbolic_link"
|
||||
case "160000":
|
||||
return "git.filemode.submodule"
|
||||
default:
|
||||
return mode
|
||||
}
|
||||
}
|
||||
|
||||
func getCommitFileLineCount(commit *git.Commit, filePath string) int {
|
||||
blob, err := commit.GetBlobByPath(filePath)
|
||||
if err != nil {
|
||||
|
||||
@@ -33,7 +33,7 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe
|
||||
|
||||
if !found {
|
||||
// This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here
|
||||
if _, _, err := ToggleAssignee(ctx, issue, doer, assignee.ID); err != nil {
|
||||
if _, _, err := ToggleAssigneeWithNotify(ctx, issue, doer, assignee.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
|
||||
func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
|
||||
// ToggleAssigneeWithNoNotify changes a user between assigned and not assigned for this issue, and make issue comment for it.
|
||||
func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
|
||||
removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -63,9 +63,9 @@ func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_m
|
||||
// ReviewRequest add or remove a review request from a user for this PR, and make comment for it.
|
||||
func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) {
|
||||
if isAdd {
|
||||
comment, err = issues_model.AddReviewRequest(issue, reviewer, doer)
|
||||
comment, err = issues_model.AddReviewRequest(ctx, issue, reviewer, doer)
|
||||
} else {
|
||||
comment, err = issues_model.RemoveReviewRequest(issue, reviewer, doer)
|
||||
comment, err = issues_model.RemoveReviewRequest(ctx, issue, reviewer, doer)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -230,9 +230,9 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team,
|
||||
// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it.
|
||||
func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) {
|
||||
if isAdd {
|
||||
comment, err = issues_model.AddTeamReviewRequest(issue, reviewer, doer)
|
||||
comment, err = issues_model.AddTeamReviewRequest(ctx, issue, reviewer, doer)
|
||||
} else {
|
||||
comment, err = issues_model.RemoveTeamReviewRequest(issue, reviewer, doer)
|
||||
comment, err = issues_model.RemoveTeamReviewRequest(ctx, issue, reviewer, doer)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
+14
-14
@@ -27,7 +27,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
|
||||
}
|
||||
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
if err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID); err != nil {
|
||||
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee
|
||||
// has access to the repo.
|
||||
for _, assignee := range allNewAssignees {
|
||||
// Extra method to prevent double adding (which would result in removing)
|
||||
err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID)
|
||||
_, err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -167,36 +167,36 @@ func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Reposi
|
||||
|
||||
// AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue.
|
||||
// Also checks for access of assigned user
|
||||
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) {
|
||||
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64, notify bool) (comment *issues_model.Comment, err error) {
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the user is already assigned
|
||||
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if isAssigned {
|
||||
// nothing to to
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if !valid {
|
||||
return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
|
||||
return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
|
||||
}
|
||||
|
||||
_, _, err = ToggleAssignee(ctx, issue, doer, assigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
if notify {
|
||||
_, comment, err = ToggleAssigneeWithNotify(ctx, issue, doer, assigneeID)
|
||||
return comment, err
|
||||
}
|
||||
|
||||
return nil
|
||||
_, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
|
||||
return comment, err
|
||||
}
|
||||
|
||||
// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name)
|
||||
@@ -242,7 +242,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
|
||||
issue.MilestoneID, err)
|
||||
}
|
||||
|
||||
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID); err != nil {
|
||||
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID, issue.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa
|
||||
}
|
||||
addSeparator = true
|
||||
|
||||
fmt.Fprint(w, pfd.Properties.GetByName(debian_module.PropertyControl))
|
||||
fmt.Fprintf(w, "%s\n", strings.TrimSpace(pfd.Properties.GetByName(debian_module.PropertyControl)))
|
||||
|
||||
fmt.Fprintf(w, "Filename: pool/%s/%s/%s\n", distribution, component, pfd.File.Name)
|
||||
fmt.Fprintf(w, "Size: %d\n", pfd.Blob.Size)
|
||||
|
||||
@@ -143,7 +143,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
|
||||
return "", errors.Wrap(err, "LoadBaseRepo")
|
||||
}
|
||||
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{})
|
||||
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "GetLatestCommitStatus")
|
||||
}
|
||||
|
||||
@@ -62,14 +62,19 @@ func TestPatch(pr *issues_model.PullRequest) error {
|
||||
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("TestPatch: %s", pr))
|
||||
defer finished()
|
||||
|
||||
// Clone base repo.
|
||||
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
|
||||
if err != nil {
|
||||
log.Error("createTemporaryRepoForPR %-v: %v", pr, err)
|
||||
if !models.IsErrBranchDoesNotExist(err) {
|
||||
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
return testPatch(ctx, prCtx, pr)
|
||||
}
|
||||
|
||||
func testPatch(ctx context.Context, prCtx *prContext, pr *issues_model.PullRequest) error {
|
||||
gitRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %w", err)
|
||||
|
||||
+83
-54
@@ -23,7 +23,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/notification"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
@@ -35,73 +34,70 @@ import (
|
||||
var pullWorkingPool = sync.NewExclusivePool()
|
||||
|
||||
// NewPullRequest creates new pull request with labels for repository.
|
||||
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
|
||||
if err := TestPatch(pr); err != nil {
|
||||
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
|
||||
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
|
||||
if err != nil {
|
||||
if !models.IsErrBranchDoesNotExist(err) {
|
||||
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
if err := testPatch(ctx, prCtx, pr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
divergence, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pr.CommitsAhead = divergence.Ahead
|
||||
pr.CommitsBehind = divergence.Behind
|
||||
|
||||
if err := issues_model.NewPullRequest(ctx, repo, pull, labelIDs, uuids, pr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
if err := issue_service.AddAssigneeIfNotAssigned(ctx, pull, pull.Poster, assigneeID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pr.Issue = pull
|
||||
pull.PullRequest = pr
|
||||
|
||||
// Now - even if the request context has been cancelled as the PR has been created
|
||||
// in the db and there is no way to cancel that transaction we have to proceed - therefore
|
||||
// create new context and work from there
|
||||
prCtx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("NewPullRequest: %s:%d", repo.FullName(), pr.Index))
|
||||
defer finished()
|
||||
|
||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
||||
err = PushToBaseRepo(prCtx, pr)
|
||||
} else {
|
||||
err = UpdateRef(prCtx, pr)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, pull, pull.Poster, pull.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notification.NotifyNewPullRequest(prCtx, pr, mentions)
|
||||
if len(pull.Labels) > 0 {
|
||||
notification.NotifyIssueChangeLabels(prCtx, pull.Poster, pull, pull.Labels, nil)
|
||||
}
|
||||
if pull.Milestone != nil {
|
||||
notification.NotifyIssueChangeMilestone(prCtx, pull.Poster, pull, 0)
|
||||
}
|
||||
assigneeCommentMap := make(map[int64]*issues_model.Comment)
|
||||
|
||||
// add first push codes comment
|
||||
baseGitRepo, err := git.OpenRepository(prCtx, pr.BaseRepo.RepoPath())
|
||||
baseGitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer baseGitRepo.Close()
|
||||
|
||||
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
|
||||
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
comment, err := issue_service.AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assigneeCommentMap[assigneeID] = comment
|
||||
}
|
||||
|
||||
pr.Issue = issue
|
||||
issue.PullRequest = pr
|
||||
|
||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
||||
err = PushToBaseRepo(ctx, pr)
|
||||
} else {
|
||||
err = UpdateRef(ctx, pr)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
|
||||
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(compareInfo.Commits) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(compareInfo.Commits) > 0 {
|
||||
data := issues_model.PushActionContent{IsForcePush: false}
|
||||
data.CommitIDs = make([]string, 0, len(compareInfo.Commits))
|
||||
for i := len(compareInfo.Commits) - 1; i >= 0; i-- {
|
||||
@@ -115,14 +111,47 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu
|
||||
|
||||
ops := &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pull.Poster,
|
||||
Doer: issue.Poster,
|
||||
Repo: repo,
|
||||
Issue: pr.Issue,
|
||||
IsForcePush: false,
|
||||
Content: string(dataJSON),
|
||||
}
|
||||
|
||||
_, _ = issue_service.CreateComment(ctx, ops)
|
||||
if _, err = issues_model.CreateComment(ctx, ops); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
// cleanup: this will only remove the reference, the real commit will be clean up when next GC
|
||||
if err1 := baseGitRepo.RemoveReference(pr.GetGitRefName()); err1 != nil {
|
||||
log.Error("RemoveReference: %v", err1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
baseGitRepo.Close() // close immediately to avoid notifications will open the repository again
|
||||
|
||||
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notification.NotifyNewPullRequest(ctx, pr, mentions)
|
||||
if len(issue.Labels) > 0 {
|
||||
notification.NotifyIssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil)
|
||||
}
|
||||
if issue.Milestone != nil {
|
||||
notification.NotifyIssueChangeMilestone(ctx, issue.Poster, issue, 0)
|
||||
}
|
||||
if len(assigneeIDs) > 0 {
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
assignee, err := user_model.GetUserByID(ctx, assigneeID)
|
||||
if err != nil {
|
||||
return ErrDependenciesLeft
|
||||
}
|
||||
notification.NotifyIssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -794,7 +823,7 @@ func getAllCommitStatus(gitRepo *git.Repository, pr *issues_model.PullRequest) (
|
||||
return nil, nil, shaErr
|
||||
}
|
||||
|
||||
statuses, _, err = git_model.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{})
|
||||
statuses, _, err = git_model.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true})
|
||||
lastStatus = git_model.CalcCommitStatus(statuses)
|
||||
return statuses, lastStatus, err
|
||||
}
|
||||
|
||||
@@ -340,7 +340,9 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
}
|
||||
|
||||
if repo.IsEmpty {
|
||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
|
||||
if isEmpty, err := gitRepo.IsEmpty(); err == nil && !isEmpty {
|
||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
|
||||
}
|
||||
}
|
||||
|
||||
return filesResponse, nil
|
||||
|
||||
@@ -131,6 +131,10 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
|
||||
case api.HookIssueReviewed:
|
||||
text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink)
|
||||
attachmentText = p.Review.Content
|
||||
case api.HookIssueReviewRequested:
|
||||
text = fmt.Sprintf("[%s] Pull request review requested: %s", repoLink, titleLink)
|
||||
case api.HookIssueReviewRequestRemoved:
|
||||
text = fmt.Sprintf("[%s] Pull request review request removed: %s", repoLink, titleLink)
|
||||
}
|
||||
if withSender {
|
||||
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
|
||||
|
||||
@@ -28,6 +28,7 @@ type (
|
||||
TelegramMeta struct {
|
||||
BotToken string `json:"bot_token"`
|
||||
ChatID string `json:"chat_id"`
|
||||
ThreadID string `json:"thread_id"`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -111,7 +112,11 @@ func handler(items ...int64) []int64 {
|
||||
for _, taskID := range items {
|
||||
task, err := webhook_model.GetHookTaskByID(ctx, taskID)
|
||||
if err != nil {
|
||||
log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
log.Warn("GetHookTaskByID[%d] warn: %v", taskID, err)
|
||||
} else {
|
||||
log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,16 @@ import (
|
||||
// - "/wiki/100%25+Free"
|
||||
// - "/wiki/2000-01-02+meeting.-"
|
||||
// - If a segment has a suffix "DashMarker(.-)", it means that there is no dash-space conversion for this segment.
|
||||
// - If a WebPath is a "*.md" pattern, then use it directly as GitPath, to make users can access the raw file.
|
||||
// - If a WebPath is a "*.md" pattern, then use the unescaped value directly as GitPath, to make users can access the raw file.
|
||||
// * Git Path (only space doesn't need to be escaped):
|
||||
// - "/.wiki.git/Home-Page.md"
|
||||
// - "/.wiki.git/100%25 Free.md"
|
||||
// - "/.wiki.git/2000-01-02 meeting.-.md"
|
||||
// TODO: support subdirectory in the future
|
||||
//
|
||||
// Although this package now has the ablity to support subdirectory, but the route package doesn't:
|
||||
// Although this package now has the ability to support subdirectory, but the route package doesn't:
|
||||
// * Double-escaping problem: the URL "/wiki/abc%2Fdef" becomes "/wiki/abc/def" by ctx.Params, which is incorrect
|
||||
// * This problem should have been 99% fixed, but it needs more tests.
|
||||
// * The old wiki code's behavior is always using %2F, instead of subdirectory, so there are a lot of legacy "%2F" files in user wikis.
|
||||
|
||||
type WebPath string
|
||||
@@ -91,7 +92,8 @@ func WebPathSegments(s WebPath) []string {
|
||||
|
||||
func WebPathToGitPath(s WebPath) string {
|
||||
if strings.HasSuffix(string(s), ".md") {
|
||||
return string(s)
|
||||
ret, _ := url.PathUnescape(string(s))
|
||||
return util.PathJoinRelX(ret)
|
||||
}
|
||||
|
||||
a := strings.Split(string(s), "/")
|
||||
@@ -124,7 +126,10 @@ func GitPathToWebPath(s string) (wp WebPath, err error) {
|
||||
func WebPathToUserTitle(s WebPath) (dir, display string) {
|
||||
dir = path.Dir(string(s))
|
||||
display = path.Base(string(s))
|
||||
display = strings.TrimSuffix(display, ".md")
|
||||
if strings.HasSuffix(display, ".md") {
|
||||
display = strings.TrimSuffix(display, ".md")
|
||||
display, _ = url.PathUnescape(display)
|
||||
}
|
||||
display, _ = unescapeSegment(display)
|
||||
return dir, display
|
||||
}
|
||||
@@ -141,8 +146,7 @@ func WebPathFromRequest(s string) WebPath {
|
||||
}
|
||||
|
||||
func UserTitleToWebPath(base, title string) WebPath {
|
||||
// TODO: ctx.Params does un-escaping, so the URL "/wiki/abc%2Fdef" becomes "wiki path = `abc/def`", which is incorrect.
|
||||
// And the old wiki code's behavior is always using %2F, instead of subdirectory.
|
||||
// TODO: no support for subdirectory, because the old wiki code's behavior is always using %2F, instead of subdirectory.
|
||||
// So we do not add the support for writing slashes in title at the moment.
|
||||
title = strings.TrimSpace(title)
|
||||
title = util.PathJoinRelX(base, escapeSegToWeb(title, false))
|
||||
|
||||
@@ -59,6 +59,7 @@ func TestWebPathToDisplayName(t *testing.T) {
|
||||
{"name with / slash", "name-with %2F slash"},
|
||||
{"name with % percent", "name-with %25 percent"},
|
||||
{"2000-01-02 meeting", "2000-01-02+meeting.-.md"},
|
||||
{"a b", "a%20b.md"},
|
||||
} {
|
||||
_, displayName := WebPathToUserTitle(test.WebPath)
|
||||
assert.EqualValues(t, test.Expected, displayName)
|
||||
@@ -73,7 +74,8 @@ func TestWebPathToGitPath(t *testing.T) {
|
||||
for _, test := range []test{
|
||||
{"wiki-name.md", "wiki%20name"},
|
||||
{"wiki-name.md", "wiki+name"},
|
||||
{"wiki%20name.md", "wiki%20name.md"},
|
||||
{"wiki name.md", "wiki%20name.md"},
|
||||
{"wiki%20name.md", "wiki%2520name.md"},
|
||||
{"2000-01-02-meeting.md", "2000-01-02+meeting"},
|
||||
{"2000-01-02 meeting.-.md", "2000-01-02%20meeting.-"},
|
||||
} {
|
||||
|
||||
@@ -11,29 +11,7 @@
|
||||
{{.locale.Tr "admin.defaulthooks.update_webhook"}}
|
||||
{{end}}
|
||||
<div class="ui right">
|
||||
{{if eq .HookType "gitea"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg">
|
||||
{{else if eq .HookType "gogs"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico">
|
||||
{{else if eq .HookType "slack"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png">
|
||||
{{else if eq .HookType "discord"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png">
|
||||
{{else if eq .HookType "dingtalk"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico">
|
||||
{{else if eq .HookType "telegram"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png">
|
||||
{{else if eq .HookType "msteams"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png">
|
||||
{{else if eq .HookType "feishu"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png">
|
||||
{{else if eq .HookType "matrix"}}
|
||||
{{svg "gitea-matrix" 26}}
|
||||
{{else if eq .HookType "wechatwork"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png">
|
||||
{{else if eq .HookType "packagist"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png">
|
||||
{{end}}
|
||||
{{template "shared/webhook/icon" .}}
|
||||
</div>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
</span>
|
||||
<div class="menu">
|
||||
<a class="{{if eq .SortType "newest"}}active {{end}}item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a>
|
||||
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a>
|
||||
<a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
|
||||
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
|
||||
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
|
||||
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
|
||||
<a class="{{if eq .SortType "newest"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=newest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a>
|
||||
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=oldest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a>
|
||||
<a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=alphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
|
||||
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=reversealphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
|
||||
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=recentupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
|
||||
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=leastupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
|
||||
{{if not .DisableStars}}
|
||||
<a class="{{if eq .SortType "moststars"}}active {{end}}item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.moststars"}}</a>
|
||||
<a class="{{if eq .SortType "feweststars"}}active {{end}}item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.feweststars"}}</a>
|
||||
<a class="{{if eq .SortType "moststars"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=moststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.moststars"}}</a>
|
||||
<a class="{{if eq .SortType "feweststars"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=feweststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.feweststars"}}</a>
|
||||
{{end}}
|
||||
<a class="{{if eq .SortType "mostforks"}}active {{end}}item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.mostforks"}}</a>
|
||||
<a class="{{if eq .SortType "fewestforks"}}active {{end}}item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.fewestforks"}}</a>
|
||||
<a class="{{if eq .SortType "mostforks"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=mostforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.mostforks"}}</a>
|
||||
<a class="{{if eq .SortType "fewestforks"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=fewestforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.fewestforks"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,29 +3,7 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{if .PageIsSettingsHooksNew}}{{.locale.Tr "repo.settings.add_webhook"}}{{else}}{{.locale.Tr "repo.settings.update_webhook"}}{{end}}
|
||||
<div class="ui right">
|
||||
{{if eq .HookType "gitea"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg">
|
||||
{{else if eq .HookType "gogs"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico">
|
||||
{{else if eq .HookType "slack"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png">
|
||||
{{else if eq .HookType "discord"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png">
|
||||
{{else if eq .HookType "dingtalk"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico">
|
||||
{{else if eq .HookType "telegram"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png">
|
||||
{{else if eq .HookType "msteams"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png">
|
||||
{{else if eq .HookType "feishu"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png">
|
||||
{{else if eq .HookType "matrix"}}
|
||||
{{svg "gitea-matrix" 26}}
|
||||
{{else if eq .HookType "wechatwork"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png">
|
||||
{{else if eq .HookType "packagist"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png">
|
||||
{{end}}
|
||||
{{template "shared/webhook/icon" .}}
|
||||
</div>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{{.locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}} {{if .RefName}}({{.RefName}}){{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="commits-table-right">
|
||||
<div class="commits-table-right gt-whitespace-nowrap">
|
||||
{{if .PageIsCommits}}
|
||||
<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/search">
|
||||
<div class="ui tiny search input">
|
||||
|
||||
@@ -110,9 +110,11 @@
|
||||
<span class="ui label">{{$.locale.Tr "repo.diff.vendored"}}</span>
|
||||
{{end}}
|
||||
{{if and $file.Mode $file.OldMode}}
|
||||
<span class="gt-ml-4 gt-mono">{{$file.OldMode}} → {{$file.Mode}}</span>
|
||||
{{$old := $.locale.Tr ($file.ModeTranslationKey $file.OldMode)}}
|
||||
{{$new := $.locale.Tr ($file.ModeTranslationKey $file.Mode)}}
|
||||
<span class="gt-ml-4 gt-mono">{{$.locale.Tr "git.filemode.changed_filemode" $old $new}}</span>
|
||||
{{else if $file.Mode}}
|
||||
<span class="gt-ml-4 gt-mono">{{$file.Mode}}</span>
|
||||
<span class="gt-ml-4 gt-mono">{{$.locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="diff-file-header-actions gt-df gt-ac gt-gap-2 gt-fw">
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"TextareaName" "content"
|
||||
"TextareaPlaceholder" ($.locale.Tr "repo.diff.comment.placeholder")
|
||||
"DropzoneParentContainer" "form"
|
||||
"DisableAutosize" "true"
|
||||
)}}
|
||||
|
||||
<div class="field footer gt-mx-3">
|
||||
|
||||
@@ -170,11 +170,13 @@
|
||||
<span class="no-select item {{if .HasSelectedLabel}}gt-hidden{{end}}">
|
||||
{{.locale.Tr "repo.issues.new.no_assignees"}}
|
||||
</span>
|
||||
<div class="selected">
|
||||
{{range .Assignees}}
|
||||
<a class="item gt-p-2 muted gt-hidden" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}">
|
||||
{{avatar $.Context . 28 "gt-mr-3 gt-vm"}}{{.GetDisplayName}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
|
||||
<div class="ui divider"></div>
|
||||
|
||||
@@ -362,7 +362,7 @@
|
||||
<div class="gt-df gt-sb gt-ac">
|
||||
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{.locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
|
||||
{{svg "octicon-calendar" 16 "gt-mr-3"}}
|
||||
{{DateTime "long" .Issue.DeadlineUnix}}
|
||||
{{DateTime "long" .Issue.DeadlineUnix.FormatDate}}
|
||||
</div>
|
||||
<div>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="four wide column">
|
||||
<div class="ui fluid vertical menu">
|
||||
<div class="header item">{{.locale.Tr "org.settings"}}</div>
|
||||
<div class="header item">{{.locale.Tr "repo.settings"}}</div>
|
||||
<a class="{{if .PageIsSettingsOptions}}active {{end}}item" href="{{.RepoLink}}/settings">
|
||||
{{.locale.Tr "repo.settings.options"}}
|
||||
</a>
|
||||
|
||||
@@ -711,9 +711,11 @@
|
||||
{{end}}
|
||||
<h4 class="ui header">{{.locale.Tr "repo.settings.admin_stats_indexer"}}</h4>
|
||||
<div class="inline fields">
|
||||
<label>{{.locale.Tr "repo.settings.admin_indexer_commit_sha"}}</label>
|
||||
{{if and .StatsIndexerStatus .StatsIndexerStatus.CommitSha}}
|
||||
<label>{{.locale.Tr "repo.settings.admin_indexer_commit_sha"}}</label>
|
||||
{{end}}
|
||||
<span class="field">
|
||||
{{if .StatsIndexerStatus}}
|
||||
{{if and .StatsIndexerStatus .StatsIndexerStatus.CommitSha}}
|
||||
<a rel="nofollow" class="ui sha label" href="{{.RepoLink}}/commit/{{.StatsIndexerStatus.CommitSha}}">
|
||||
<span class="shortsha">{{ShortSha .StatsIndexerStatus.CommitSha}}</span>
|
||||
</a>
|
||||
|
||||
@@ -5,37 +5,48 @@
|
||||
<div class="ui primary tiny button">{{.locale.Tr "repo.settings.add_webhook"}}</div>
|
||||
<div class="menu">
|
||||
<a class="item" href="{{.BaseLinkNew}}/gitea/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/gitea.svg">{{.locale.Tr "repo.settings.web_hook_name_gitea"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "gitea" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_gitea"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/gogs/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/gogs.ico">{{.locale.Tr "repo.settings.web_hook_name_gogs"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "gogs" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_gogs"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/slack/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/slack.png">{{.locale.Tr "repo.settings.web_hook_name_slack"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "slack" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_slack"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/discord/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/discord.png">{{.locale.Tr "repo.settings.web_hook_name_discord"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "discord" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_discord"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/dingtalk/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/dingtalk.ico">{{.locale.Tr "repo.settings.web_hook_name_dingtalk"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "dingtalk" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_dingtalk"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/telegram/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/telegram.png">{{.locale.Tr "repo.settings.web_hook_name_telegram"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "telegram" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_telegram"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/msteams/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/msteams.png">{{.locale.Tr "repo.settings.web_hook_name_msteams"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "msteams" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_msteams"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/feishu/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/feishu.png">{{.locale.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "feishu" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/matrix/new">
|
||||
{{svg "gitea-matrix" 20 "img"}}{{.locale.Tr "repo.settings.web_hook_name_matrix"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "matrix" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_matrix"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/wechatwork/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/wechatwork.png">{{.locale.Tr "repo.settings.web_hook_name_wechatwork"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "wechatwork" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_wechatwork"}}
|
||||
</a>
|
||||
<a class="item" href="{{.BaseLinkNew}}/packagist/new">
|
||||
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/packagist.png">{{.locale.Tr "repo.settings.web_hook_name_packagist"}}
|
||||
{{template "shared/webhook/icon" (dict "HookType" "packagist" "Size" 20)}}
|
||||
{{.locale.Tr "repo.settings.web_hook_name_packagist"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,29 +3,7 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{if .PageIsSettingsHooksNew}}{{.locale.Tr "repo.settings.add_webhook"}}{{else}}{{.locale.Tr "repo.settings.update_webhook"}}{{end}}
|
||||
<div class="ui right">
|
||||
{{if eq .HookType "gitea"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg">
|
||||
{{else if eq .HookType "gogs"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico">
|
||||
{{else if eq .HookType "slack"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png">
|
||||
{{else if eq .HookType "discord"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png">
|
||||
{{else if eq .HookType "dingtalk"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico">
|
||||
{{else if eq .HookType "telegram"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png">
|
||||
{{else if eq .HookType "msteams"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png">
|
||||
{{else if eq .HookType "feishu"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png">
|
||||
{{else if eq .HookType "matrix"}}
|
||||
{{svg "gitea-matrix" 26}}
|
||||
{{else if eq .HookType "wechatwork"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png">
|
||||
{{else if eq .HookType "packagist"}}
|
||||
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png">
|
||||
{{end}}
|
||||
{{template "shared/webhook/icon" .}}
|
||||
</div>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
<label for="chat_id">{{.locale.Tr "repo.settings.chat_id"}}</label>
|
||||
<input id="chat_id" name="chat_id" type="text" value="{{.TelegramHook.ChatID}}" required>
|
||||
</div>
|
||||
<div class="field {{if .Err_ThreadID}}error{{end}}">
|
||||
<label for="thread_id">{{.locale.Tr "repo.settings.thread_id"}}</label>
|
||||
<input id="thread_id" name="thread_id" type="text" value="{{.TelegramHook.ThreadID}}">
|
||||
</div>
|
||||
{{template "repo/settings/webhook/settings" .}}
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</td>
|
||||
<td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline $.locale}}{{else}}{{$.locale.Tr "never"}}{{end}}</td>
|
||||
<td class="runner-ops">
|
||||
{{if .Editable $.RunnerOnwerID $.RunnerRepoID}}
|
||||
{{if .Editable $.RunnerOwnerID $.RunnerRepoID}}
|
||||
<a href="{{$.Link}}/{{.ID}}">{{svg "octicon-pencil"}}</a>
|
||||
{{end}}
|
||||
</td>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user