mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-13 02:02:53 +09:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e4d5a5594 | ||
|
|
6124c78de0 | ||
|
|
12b429c0d1 | ||
|
|
8c31456a87 | ||
|
|
4bfc43ef8d | ||
|
|
93e105a228 | ||
|
|
e8b6d28ab9 | ||
|
|
346b662305 | ||
|
|
25b0c99a41 | ||
|
|
bfc7c8a598 | ||
|
|
1f89763744 | ||
|
|
a129c0c06c | ||
|
|
bd7de0c4e4 | ||
|
|
6651d2d87a | ||
|
|
76b6754c3a | ||
|
|
0b5a4e7db4 | ||
|
|
b6a2b9594a | ||
|
|
6ee58a0ac2 | ||
|
|
2f1eb619bc | ||
|
|
9db426ad8c | ||
|
|
a8277cfc8f | ||
|
|
5667ef9aab | ||
|
|
02df269d24 | ||
|
|
4ef7e496b8 | ||
|
|
b519e4750b | ||
|
|
b84303ef6e | ||
|
|
2b059f493e | ||
|
|
5ca2971ccb | ||
|
|
63ec6facea | ||
|
|
971eab18fa | ||
|
|
86cd94cba6 | ||
|
|
8723389028 | ||
|
|
401cc394d5 | ||
|
|
3a8877c058 | ||
|
|
a86d9337e9 | ||
|
|
8d08558783 | ||
|
|
730cd2dee4 | ||
|
|
65b9ffe3c0 | ||
|
|
c293e34df0 | ||
|
|
9abba8c11a | ||
|
|
5477728282 | ||
|
|
b43ce53a23 | ||
|
|
222f93822e | ||
|
|
eabcfd3f7d | ||
|
|
2df38af752 | ||
|
|
dc48eb070b | ||
|
|
06dc26167a | ||
|
|
9456deb512 | ||
|
|
c758a8afba | ||
|
|
83327e043a | ||
|
|
78cb09deda | ||
|
|
0f35cb5a2a | ||
|
|
0b3d6c399c | ||
|
|
f98a1b851c | ||
|
|
e75594f7a6 | ||
|
|
a1c0b3a02e | ||
|
|
6624f257d3 | ||
|
|
35db5a373b | ||
|
|
5043ad54c7 | ||
|
|
727435743a | ||
|
|
829b807a91 | ||
|
|
874cdcc974 |
18
.github/workflows/pull-db-tests.yml
vendored
18
.github/workflows/pull-db-tests.yml
vendored
@@ -49,7 +49,10 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- run: make test-pgsql-migration test-pgsql
|
||||
- name: run migration tests
|
||||
run: make test-pgsql-migration
|
||||
- name: run tests
|
||||
run: make test-pgsql
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata gogit
|
||||
@@ -72,7 +75,10 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
- run: make test-sqlite-migration test-sqlite
|
||||
- name: run migration tests
|
||||
run: make test-sqlite-migration
|
||||
- name: run tests
|
||||
run: make test-sqlite
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
@@ -189,8 +195,10 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: run migration tests
|
||||
run: make test-mysql-migration
|
||||
- name: run tests
|
||||
run: make test-mysql-migration integration-test-coverage
|
||||
run: make integration-test-coverage
|
||||
env:
|
||||
TAGS: bindata
|
||||
RACE_ENABLED: true
|
||||
@@ -252,7 +260,9 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- run: make test-mssql-migration test-mssql
|
||||
- run: make test-mssql-migration
|
||||
- name: run tests
|
||||
run: make test-mssql
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata
|
||||
|
||||
82
CHANGELOG.md
82
CHANGELOG.md
@@ -4,14 +4,71 @@ 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.com).
|
||||
|
||||
## [1.21.8](https://github.com/go-gitea/gitea/releases/tag/1.21.8) - 2024-03-12
|
||||
|
||||
* SECURITY
|
||||
* Only use supported sort orders for "/explore/users" page (#29430) (#29443)
|
||||
* ENHANCEMENTS
|
||||
* Fix wrong line number in code search result (#29260) (#29623)
|
||||
* BUGFIXES
|
||||
* Use Get but not Post to get actions artifacts (#29734) (#29737)
|
||||
* Fix inconsistent rendering of block mathematical expressions (#29677) (#29711)
|
||||
* Fix rendering internal file links in org (#29669) (#29705)
|
||||
* Don't show AbortErrors on logout (#29639) (#29667)
|
||||
* Fix user-defined markup links targets (#29305) (#29666)
|
||||
* Fix incorrect rendering csv file when file size is larger than UI.CSV.MaxFileSize (#29653) (#29663)
|
||||
* Fix hidden test's failure (#29254) (#29662)
|
||||
* Add empty repo check-in DetectAndHandleSchedules (#29606) (#29659)
|
||||
* Fix 500 when deleting an account with an incorrect password or unsupported login type (#29579) (#29656)
|
||||
* Use strict protocol check when redirect (#29642) (#29644)
|
||||
* Avoid issue info panic (#29625) (#29632)
|
||||
* Avoid unexpected panic in graceful manager (#29629) (#29630)
|
||||
* Make "/user/login" page redirect if the current user has signed in (#29583) (#29599)
|
||||
* Fix workflow trigger event IssueChangeXXX bug (#29559) (#29565)
|
||||
* Fix incorrect cookie path for AppSubURL (#29534) (#29552)
|
||||
* Fix queue worker incorrectly stopped when there are still more items in the queue (#29532) (#29546)
|
||||
* Fix incorrect redirection when creating a PR fails (#29537) (#29543)
|
||||
* Fix incorrect subpath in links (#29535) (#29541)
|
||||
* Fix issue link does not support quotes (#29484) (#29487) (#29536)
|
||||
* Fix issue & comment history bugs (#29525) (#29527)
|
||||
* Set pre-step status to `skipped` if the job is skipped (#29489) (#29523)
|
||||
* Fix/Improve `processWindowErrorEvent` (#29407) (#29480)
|
||||
* Fix counter display number incorrectly displayed on the page (#29448) (#29478)
|
||||
* Fix workflow trigger event bugs (#29467) (#29475)
|
||||
* Fix URL calculation in the clone input box (#29470) (#29473)
|
||||
* The job should always run when `if` is `always()` (#29464) (#29469)
|
||||
* Fix template bug (#27581) (#29446)
|
||||
* Not trigger all jobs anymore when re-running the first job (#29439) (#29441)
|
||||
* Ignore empty repo for CreateRepository in action notifier (#29416) (#29424)
|
||||
* Fix incorrect tree path value for patch editor (#29377) (#29421)
|
||||
* Add missing database transaction for new issues (#29490) (#29607)
|
||||
* Fix 500 when pushing release to an empty repo (#29554) (#29564)
|
||||
* Fix incorrect relative/absolute URL usages (#29531) (#29547)
|
||||
* Fix wrong test usage of `AppSubURL` (#29459) (#29488)
|
||||
* Fix missed return (#29450) (#29453)
|
||||
* Fixing the issue when status checks per rule matches multiple actions (#29631) (#29655)
|
||||
* Improve contrast on blame timestamp, fix double border (#29482) (#29485)
|
||||
|
||||
## [1.21.7](https://github.com/go-gitea/gitea/releases/tag/1.21.7) - 2024-02-26
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Users with `read` permission of pull requests can be assigned too (#27263) (#29372)
|
||||
* BUGFIXES
|
||||
* Do not double close reader (#29354) (#29370)
|
||||
* Display friendly error message (#29105) (#29363)
|
||||
* Fix project counter in organization/individual profile (#28068) (#29361)
|
||||
* Fix validity of the FROM email address not being checked (#29347) (#29360)
|
||||
* Fix tarball/zipball download bug (#29342) (#29352)
|
||||
* DOCS
|
||||
* Docker Tag Information in Docs (#29047) (#29362)
|
||||
* MISC
|
||||
* Enforce maxlength in frontend (#29389) (#29396)
|
||||
|
||||
## [1.21.6](https://github.com/go-gitea/gitea/releases/tag/v1.21.6) - 2024-02-22
|
||||
|
||||
* SECURITY
|
||||
* Fix XSS vulnerabilities (#29336)
|
||||
* Use general token signing secret (#29205) (#29325)
|
||||
* API
|
||||
* Refactor issue template parsing and fix API endpoint (#29069) (#29140)
|
||||
* Fix swift packages not resolving (#29095) (#29102)
|
||||
* ENHANCEMENTS
|
||||
* Refactor git version functions and check compatibility (#29155) (#29157)
|
||||
* Improve user experience for outdated comments (#29050) (#29086)
|
||||
@@ -19,7 +76,11 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Wrap contained tags and branches again (#29021) (#29026)
|
||||
* Fix incorrect button CSS usages (#29015) (#29023)
|
||||
* Strip trailing newline in markdown code copy (#29019) (#29022)
|
||||
* Implement some action notifier functions (#29173) (#29308)
|
||||
* Load outdated comments when (un)resolving conversation on PR timeline (#29203) (#29221)
|
||||
* BUGFIXES
|
||||
* Refactor issue template parsing and fix API endpoint (#29069) (#29140)
|
||||
* Fix swift packages not resolving (#29095) (#29102)
|
||||
* Remove SSH workaround (#27893) (#29332)
|
||||
* Only log error when tag sync fails (#29295) (#29327)
|
||||
* Fix SSPI user creation (#28948) (#29323)
|
||||
@@ -44,18 +105,15 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Avoid showing unnecessary JS errors when there are elements with different origin on the page (#29081) (#29089)
|
||||
* Fix gitea-origin-url with default ports (#29085) (#29088)
|
||||
* Fix orgmode link resolving (#29024) (#29076)
|
||||
* Fix: Elasticsearch: Request Entity Too Large #28117 (#29062) (#29075)
|
||||
* Fix Elasticsearh Request Entity Too Large #28117 (#29062) (#29075)
|
||||
* Do not render empty comments (#29039) (#29049)
|
||||
* Avoid sending update/delete release notice when it is draft (#29008) (#29025)
|
||||
* DOCS
|
||||
* Rm outdated docs from some languages (#27530) (#29208)
|
||||
* MISC
|
||||
* Implement some action notifier functions (#29173) (#29308)
|
||||
* Fix gitea-action user avatar broken on edited menu (#29190) (#29307)
|
||||
* Disallow merge when required checked are missing (#29143) (#29268)
|
||||
* Convert visibility to number (#29226) (#29244)
|
||||
* Load outdated comments when (un)resolving conversation on PR timeline (#29203) (#29221)
|
||||
* Fix incorrect link to swift doc and swift package-registry login command (#29096) (#29103)
|
||||
* Convert visibility to number (#29226) (#29244)
|
||||
* DOCS
|
||||
* Remove outdated docs from some languages (#27530) (#29208)
|
||||
* Fix typos in the documentation (#29048) (#29056)
|
||||
* Explained where create issue/PR template (#29035)
|
||||
|
||||
@@ -174,7 +232,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
|
||||
* Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
|
||||
|
||||
## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/1.21.2) - 2023-12-12
|
||||
## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/v1.21.2) - 2023-12-12
|
||||
|
||||
* SECURITY
|
||||
* Rebuild with recently released golang version
|
||||
@@ -213,7 +271,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Use full width for project boards (#28225) (#28245)
|
||||
* Enable system users search via the API (#28013) (#28018)
|
||||
|
||||
## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/1.21.1) - 2023-11-26
|
||||
## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/v1.21.1) - 2023-11-26
|
||||
|
||||
* SECURITY
|
||||
* Fix comment permissions (#28213) (#28216)
|
||||
|
||||
18
Makefile
18
Makefile
@@ -111,6 +111,7 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
||||
|
||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
||||
|
||||
FOMANTIC_WORK_DIR := web_src/fomantic
|
||||
|
||||
@@ -748,9 +749,7 @@ migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||
|
||||
.PHONY: migrations.individual.mysql.test
|
||||
migrations.individual.mysql.test: $(GO_SOURCES)
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.mysql8.test
|
||||
migrations.individual.mysql8.test: $(GO_SOURCES)
|
||||
@@ -764,20 +763,15 @@ migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test\#%
|
||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||
|
||||
|
||||
.PHONY: migrations.individual.mssql.test
|
||||
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg -test.failfast; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.mssql.test\#%
|
||||
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
||||
@@ -785,9 +779,7 @@ migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[Unit]
|
||||
Description=Gitea (Git with a cup of tea)
|
||||
After=syslog.target
|
||||
After=network.target
|
||||
###
|
||||
# Don't forget to add the database service dependencies
|
||||
|
||||
@@ -222,9 +222,11 @@ Our translations are currently crowd-sourced on our [Crowdin project](https://cr
|
||||
|
||||
Whether you want to change a translation or add a new one, it will need to be there as all translations are overwritten in our CI via the Crowdin integration.
|
||||
|
||||
## Push Hook / Webhook aren't running
|
||||
## Push Hook / Webhook / Actions aren't running
|
||||
|
||||
If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook, there are a few possibilities:
|
||||
If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook and Actions workflows, it's likely that the git hooks are not working.
|
||||
|
||||
There are a few possibilities:
|
||||
|
||||
1. The git hooks are out of sync: run "Resynchronize pre-receive, update and post-receive hooks of all repositories" on the site admin panel
|
||||
2. The git repositories (and hooks) are stored on some filesystems (ex: mounted by NAS) which don't support script execution, make sure the filesystem supports `chmod a+x any-script`
|
||||
|
||||
@@ -226,9 +226,11 @@ Gitea还提供了自己的SSH服务器,用于在SSHD不可用时使用。
|
||||
|
||||
无论您想要更改翻译还是添加新的翻译,都需要在Crowdin集成中进行,因为所有翻译都会被CI覆盖。
|
||||
|
||||
## 推送钩子/ Webhook未运行
|
||||
## 推送钩子/ Webhook / Actions 未运行
|
||||
|
||||
如果您可以推送但无法在主页仪表板上看到推送活动,或者推送不触发Webhook,有几种可能性:
|
||||
如果您可以推送但无法在主页仪表板上看到推送活动,或者推送不触发 Webhook 和 Actions,可能是 git 钩子不工作而导致的。
|
||||
|
||||
这可能是由于以下原因:
|
||||
|
||||
1. Git钩子不同步:在站点管理面板上运行“重新同步所有仓库的pre-receive、update和post-receive钩子”
|
||||
2. Git仓库(和钩子)存储在一些不支持脚本执行的文件系统上(例如由NAS挂载),请确保文件系统支持`chmod a+x any-script`
|
||||
|
||||
@@ -43,27 +43,26 @@ Still, this is completely optional since both options have the same effect at th
|
||||
Not yet.
|
||||
It is technically possible to implement, but we need to discuss whether it is necessary.
|
||||
|
||||
## Where will the runner download scripts when using actions such as `actions/checkout@v3`?
|
||||
## Where will the runner download scripts when using actions such as `actions/checkout@v4`?
|
||||
|
||||
You may be aware that there are tens of thousands of [marketplace actions](https://github.com/marketplace?type=actions) in GitHub.
|
||||
However, when you write `uses: actions/checkout@v3`, it actually downloads the scripts from [gitea.com/actions/checkout](http://gitea.com/actions/checkout) by default (not GitHub).
|
||||
This is a mirror of [github.com/actions/checkout](http://github.com/actions/checkout), but it's impossible to mirror all of them.
|
||||
That's why you may encounter failures when trying to use some actions that haven't been mirrored.
|
||||
There are tens of thousands of [actions scripts](https://github.com/marketplace?type=actions) in GitHub, and when you write `uses: actions/checkout@v4`, it downloads the scripts from [github.com/actions/checkout](http://github.com/actions/checkout) by default.
|
||||
But what if you want to use actions from other places such as gitea.com instead of GitHub?
|
||||
|
||||
The good news is that you can specify the URL prefix to use actions from anywhere.
|
||||
This is an extra syntax in Gitea Actions.
|
||||
For example:
|
||||
|
||||
- `uses: https://github.com/xxx/xxx@xxx`
|
||||
- `uses: https://gitea.com/xxx/xxx@xxx`
|
||||
- `uses: https://github.com/xxx/xxx@xxx`
|
||||
- `uses: http://your_gitea_instance.com/xxx@xxx`
|
||||
|
||||
Be careful, the `https://` or `http://` prefix is necessary!
|
||||
|
||||
Alternatively, if you want your runners to download actions from GitHub or your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`.
|
||||
See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions).
|
||||
This is one of the differences from GitHub Actions which supports actions scripts only from GitHub.
|
||||
But it should allow users much more flexibility in how they run Actions.
|
||||
|
||||
This is one of the differences from GitHub Actions, but it should allow users much more flexibility in how they run Actions.
|
||||
Alternatively, if you want your runners to download actions from your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`.
|
||||
See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions).
|
||||
|
||||
## How to limit the permission of the runners?
|
||||
|
||||
|
||||
@@ -43,27 +43,27 @@ DEFAULT_REPO_UNITS = ...,repo.actions
|
||||
目前还不可以。
|
||||
从技术上讲是可以实现的,但我们需要讨论是否有必要。
|
||||
|
||||
## 使用`actions/checkout@v3`等Actions时,Job容器会从何处下载脚本?
|
||||
## 使用`actions/checkout@v4`等Actions时,Job容器会从何处下载脚本?
|
||||
|
||||
您可能知道GitHub上有成千上万个[Actions市场](https://github.com/marketplace?type=actions)。
|
||||
然而,当您编写`uses: actions/checkout@v3`时,它实际上默认从[gitea.com/actions/checkout](http://gitea.com/actions/checkout)下载脚本(而不是从GitHub下载)。
|
||||
这是[github.com/actions/checkout](http://github.com/actions/checkout)的镜像,但无法将它们全部镜像。
|
||||
这就是为什么在尝试使用尚未镜像的某些Actions时可能会遇到失败的原因。
|
||||
GitHub 上有成千上万个 [Actions 脚本](https://github.com/marketplace?type=actions)。
|
||||
当您编写 `uses: actions/checkout@v4` 时,它默认会从 [github.com/actions/checkout](https://github.com/actions/checkout) 下载脚本。
|
||||
那如果您想使用一些托管在其它平台上的脚本呢,比如在 gitea.com 上的?
|
||||
|
||||
好消息是,您可以指定要从任何位置使用Actions的URL前缀。
|
||||
这是Gitea Actions中的额外语法。
|
||||
例如:
|
||||
|
||||
- `uses: https://github.com/xxx/xxx@xxx`
|
||||
- `uses: https://gitea.com/xxx/xxx@xxx`
|
||||
- `uses: https://github.com/xxx/xxx@xxx`
|
||||
- `uses: http://your_gitea_instance.com/xxx@xxx`
|
||||
|
||||
注意,`https://`或`http://`前缀是必需的!
|
||||
|
||||
另外,如果您希望您的Runner默认从GitHub或您自己的Gitea实例下载Actions,可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置。
|
||||
参见[配置速查表](administration/config-cheat-sheet.md#actions-actions)。
|
||||
这是与 GitHub Actions 的一个区别,GitHub Actions 只允许使用托管在 GitHub 上的 actions 脚本。
|
||||
但用户理应拥有权利去灵活决定如何运行 Actions。
|
||||
|
||||
这是与GitHub Actions的一个区别,但它应该允许用户以更灵活的方式运行Actions。
|
||||
另外,如果您希望您的 Runner 默认从您自己的 Gitea 实例下载 Actions,可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置。
|
||||
参见[配置速查表](administration/config-cheat-sheet.md#actions-actions)。
|
||||
|
||||
## 如何限制Runner的权限?
|
||||
|
||||
|
||||
@@ -39,6 +39,16 @@ Images must follow this naming convention:
|
||||
|
||||
`{registry}/{owner}/{image}`
|
||||
|
||||
When building your docker image, using the naming convention above, this looks like:
|
||||
|
||||
```shell
|
||||
# build an image with tag
|
||||
docker build -t {registry}/{owner}/{image}:{tag} .
|
||||
# name an existing image with tag
|
||||
docker tag {some-existing-image}:{tag} {registry}/{owner}/{image}:{tag}
|
||||
```
|
||||
|
||||
where your registry is the domain of your gitea instance (e.g. gitea.example.com).
|
||||
For example, these are all valid image names for the owner `testuser`:
|
||||
|
||||
`gitea.example.com/testuser/myimage`
|
||||
|
||||
2
go.mod
2
go.mod
@@ -114,7 +114,7 @@ require (
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.17.0
|
||||
google.golang.org/grpc v1.58.3
|
||||
google.golang.org/protobuf v1.31.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1453,8 +1453,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -393,10 +393,14 @@ func (a *Action) GetCreate() time.Time {
|
||||
return a.CreatedUnix.AsTime()
|
||||
}
|
||||
|
||||
// GetIssueInfos returns a list of issues associated with
|
||||
// the action.
|
||||
// GetIssueInfos returns a list of associated information with the action.
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
return strings.SplitN(a.Content, "|", 3)
|
||||
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
|
||||
ret := strings.SplitN(a.Content, "|", 3)
|
||||
for len(ret) < 3 {
|
||||
ret = append(ret, "")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetIssueTitle returns the title of first issue associated with the action.
|
||||
|
||||
@@ -172,13 +172,9 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6
|
||||
|
||||
// HasIssueContentHistory check if a ContentHistory entry exists
|
||||
func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) {
|
||||
exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{
|
||||
IssueID: issueID,
|
||||
CommentID: commentID,
|
||||
})
|
||||
exists, err := db.GetEngine(dbCtx).Where(builder.Eq{"issue_id": issueID, "comment_id": commentID}).Exist(&ContentHistory{})
|
||||
if err != nil {
|
||||
log.Error("can not fetch issue content history. err=%v", err)
|
||||
return false, err
|
||||
return false, fmt.Errorf("can not check issue content history. err: %w", err)
|
||||
}
|
||||
return exists, err
|
||||
}
|
||||
|
||||
@@ -78,3 +78,22 @@ func TestContentHistory(t *testing.T) {
|
||||
assert.EqualValues(t, 7, list2[1].HistoryID)
|
||||
assert.EqualValues(t, 4, list2[2].HistoryID)
|
||||
}
|
||||
|
||||
func TestHasIssueContentHistoryForCommentOnly(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
_ = db.TruncateBeans(db.DefaultContext, &issues_model.ContentHistory{})
|
||||
|
||||
hasHistory1, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
|
||||
assert.False(t, hasHistory1)
|
||||
hasHistory2, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
|
||||
assert.False(t, hasHistory2)
|
||||
|
||||
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow(), "c-a", true)
|
||||
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow().Add(5), "c-b", false)
|
||||
|
||||
hasHistory1, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
|
||||
assert.False(t, hasHistory1)
|
||||
hasHistory2, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
|
||||
assert.True(t, hasHistory2)
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m
|
||||
}
|
||||
|
||||
// ChangeProjectAssign changes the project associated with an issue
|
||||
func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -36,12 +36,14 @@ func Test_DropTableColumns(t *testing.T) {
|
||||
"updated_unix",
|
||||
}
|
||||
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
|
||||
for i := range columns {
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to create DropTest table: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
if err := sess.Begin(); err != nil {
|
||||
sess.Close()
|
||||
@@ -64,7 +66,6 @@ func Test_DropTableColumns(t *testing.T) {
|
||||
return
|
||||
}
|
||||
for j := range columns[i+1:] {
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to create DropTest table: %v", err)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
index: 1
|
||||
@@ -0,0 +1,11 @@
|
||||
-
|
||||
id: 1
|
||||
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
|
||||
issue_id: 1
|
||||
release_id: 0
|
||||
|
||||
-
|
||||
id: 2
|
||||
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12
|
||||
issue_id: 0
|
||||
release_id: 1
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
@@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
@@ -15,7 +15,6 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
RepoID int64 `xorm:"INDEX"` // this should not be zero
|
||||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"`
|
||||
@@ -44,12 +43,21 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
var issueAttachments []*Attachment
|
||||
err := x.Where("issue_id > 0").Find(&issueAttachments)
|
||||
type NewAttachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
RepoID int64 `xorm:"INDEX"` // this should not be zero
|
||||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"`
|
||||
}
|
||||
|
||||
var issueAttachments []*NewAttachment
|
||||
err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range issueAttachments {
|
||||
assert.Greater(t, attach.RepoID, 0)
|
||||
assert.Greater(t, attach.IssueID, 0)
|
||||
assert.Greater(t, attach.RepoID, int64(0))
|
||||
assert.Greater(t, attach.IssueID, int64(0))
|
||||
var issue Issue
|
||||
has, err := x.ID(attach.IssueID).Get(&issue)
|
||||
assert.NoError(t, err)
|
||||
@@ -57,12 +65,12 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
assert.EqualValues(t, attach.RepoID, issue.RepoID)
|
||||
}
|
||||
|
||||
var releaseAttachments []*Attachment
|
||||
err = x.Where("release_id > 0").Find(&releaseAttachments)
|
||||
var releaseAttachments []*NewAttachment
|
||||
err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range releaseAttachments {
|
||||
assert.Greater(t, attach.RepoID, 0)
|
||||
assert.Greater(t, attach.IssueID, 0)
|
||||
assert.Greater(t, attach.RepoID, int64(0))
|
||||
assert.Greater(t, attach.ReleaseID, int64(0))
|
||||
var release Release
|
||||
has, err := x.ID(attach.ReleaseID).Get(&release)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -70,16 +70,26 @@ type PackageFileDescriptor struct {
|
||||
Properties PackagePropertyList
|
||||
}
|
||||
|
||||
// PackageWebLink returns the package web link
|
||||
// PackageWebLink returns the relative package web link
|
||||
func (pd *PackageDescriptor) PackageWebLink() string {
|
||||
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
||||
}
|
||||
|
||||
// FullWebLink returns the package version web link
|
||||
func (pd *PackageDescriptor) FullWebLink() string {
|
||||
// VersionWebLink returns the relative package version web link
|
||||
func (pd *PackageDescriptor) VersionWebLink() string {
|
||||
return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion))
|
||||
}
|
||||
|
||||
// PackageHTMLURL returns the absolute package HTML URL
|
||||
func (pd *PackageDescriptor) PackageHTMLURL() string {
|
||||
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HTMLURL(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
||||
}
|
||||
|
||||
// VersionHTMLURL returns the absolute package version HTML URL
|
||||
func (pd *PackageDescriptor) VersionHTMLURL() string {
|
||||
return fmt.Sprintf("%s/%s", pd.PackageHTMLURL(), url.PathEscape(pd.Version.LowerVersion))
|
||||
}
|
||||
|
||||
// CalculateBlobSize returns the total blobs size in bytes
|
||||
func (pd *PackageDescriptor) CalculateBlobSize() int64 {
|
||||
size := int64(0)
|
||||
|
||||
@@ -332,7 +332,6 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
|
||||
|
||||
// CanBeAssigned return true if user can be assigned to issue or pull requests in repo
|
||||
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
|
||||
// FIXME: user could send PullRequest also could be assigned???
|
||||
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
|
||||
if user.IsOrganization() {
|
||||
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
|
||||
@@ -341,7 +340,8 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return perm.CanAccessAny(perm_model.AccessModeWrite, unit.TypeCode, unit.TypeIssues, unit.TypePullRequests), nil
|
||||
return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) ||
|
||||
perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
|
||||
}
|
||||
|
||||
// HasAccess returns true if user has access to repo
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -78,7 +79,8 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -30,6 +31,8 @@ type SearchUserOptions struct {
|
||||
Actor *User // The user doing the search
|
||||
SearchByEmail bool // Search by email as well as username/full name
|
||||
|
||||
SupportedSortOrders container.Set[string] // if not nil, only allow to use the sort orders in this set
|
||||
|
||||
IsActive util.OptionalBool
|
||||
IsAdmin util.OptionalBool
|
||||
IsRestricted util.OptionalBool
|
||||
|
||||
@@ -35,6 +35,9 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
|
||||
} else if task.Status.IsDone() {
|
||||
preStep.Stopped = task.Stopped
|
||||
preStep.Status = actions_model.StatusFailure
|
||||
if task.Status.IsSkipped() {
|
||||
preStep.Status = actions_model.StatusSkipped
|
||||
}
|
||||
}
|
||||
logIndex += preStep.LogLength
|
||||
|
||||
|
||||
@@ -441,6 +441,9 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
|
||||
// all acts conditions should be satisfied
|
||||
for cond, vals := range acts {
|
||||
switch cond {
|
||||
case "types":
|
||||
// types have been checked
|
||||
continue
|
||||
case "branches":
|
||||
refName := git.RefName(prPayload.PullRequest.Base.Ref)
|
||||
patterns, err := workflowpattern.CompilePatterns(vals...)
|
||||
|
||||
@@ -255,7 +255,7 @@ func (b *Base) Redirect(location string, status ...int) {
|
||||
code = status[0]
|
||||
}
|
||||
|
||||
if strings.Contains(location, "://") || strings.HasPrefix(location, "//") {
|
||||
if strings.HasPrefix(location, "http://") || strings.HasPrefix(location, "https://") || strings.HasPrefix(location, "//") {
|
||||
// Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path
|
||||
// 1. the first request to "/my-path" contains cookie
|
||||
// 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking)
|
||||
|
||||
@@ -7,11 +7,13 @@ package contexttest
|
||||
import (
|
||||
gocontext "context"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@@ -35,7 +37,7 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
|
||||
}
|
||||
requestURL, err := url.Parse(path)
|
||||
assert.NoError(t, err)
|
||||
req := &http.Request{Method: method, URL: requestURL, Form: url.Values{}}
|
||||
req := &http.Request{Method: method, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}}
|
||||
req = req.WithContext(middleware.WithContextData(req.Context()))
|
||||
return req
|
||||
}
|
||||
@@ -61,7 +63,8 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
|
||||
base.Locale = &translation.MockLocale{}
|
||||
|
||||
ctx := context.NewWebContext(base, opt.Render, nil)
|
||||
|
||||
ctx.PageData = map[string]any{}
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
chiCtx := chi.NewRouteContext()
|
||||
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||
return ctx, resp
|
||||
|
||||
@@ -102,7 +102,17 @@ func (b *blobReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
// Close implements io.Closer
|
||||
func (b *blobReader) Close() error {
|
||||
if b.rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer b.cancel()
|
||||
|
||||
return DiscardFull(b.rd, b.n+1)
|
||||
if err := DiscardFull(b.rd, b.n+1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.rd = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -118,7 +118,15 @@ func (g *Manager) start(ctx context.Context) {
|
||||
defer close(startupDone)
|
||||
// Wait till we're done getting all of the listeners and then close
|
||||
// the unused ones
|
||||
g.createServerWaitGroup.Wait()
|
||||
func() {
|
||||
// FIXME: there is a fundamental design problem of the "manager" and the "wait group".
|
||||
// If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned
|
||||
// There is no clear solution besides a complete rewriting of the "manager"
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
g.createServerWaitGroup.Wait()
|
||||
}()
|
||||
// Ignore the error here there's not much we can do with it
|
||||
// They're logged in the CloseProvidedListeners function
|
||||
_ = CloseProvidedListeners()
|
||||
|
||||
@@ -227,7 +227,15 @@ func (g *Manager) awaitServer(limit time.Duration) bool {
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
g.createServerWaitGroup.Wait()
|
||||
func() {
|
||||
// FIXME: there is a fundamental design problem of the "manager" and the "wait group".
|
||||
// If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned
|
||||
// There is no clear solution besides a complete rewriting of the "manager"
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
g.createServerWaitGroup.Wait()
|
||||
}()
|
||||
}()
|
||||
if limit > 0 {
|
||||
select {
|
||||
|
||||
@@ -16,14 +16,18 @@ import (
|
||||
|
||||
// Result a search result to display
|
||||
type Result struct {
|
||||
RepoID int64
|
||||
Filename string
|
||||
CommitID string
|
||||
UpdatedUnix timeutil.TimeStamp
|
||||
Language string
|
||||
Color string
|
||||
LineNumbers []int
|
||||
FormattedLines template.HTML
|
||||
RepoID int64
|
||||
Filename string
|
||||
CommitID string
|
||||
UpdatedUnix timeutil.TimeStamp
|
||||
Language string
|
||||
Color string
|
||||
Lines []ResultLine
|
||||
}
|
||||
|
||||
type ResultLine struct {
|
||||
Num int
|
||||
FormattedContent template.HTML
|
||||
}
|
||||
|
||||
type SearchResultLanguages = internal.SearchResultLanguages
|
||||
@@ -70,7 +74,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
|
||||
var formattedLinesBuffer bytes.Buffer
|
||||
|
||||
contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n")
|
||||
lineNumbers := make([]int, len(contentLines))
|
||||
lines := make([]ResultLine, 0, len(contentLines))
|
||||
index := startIndex
|
||||
for i, line := range contentLines {
|
||||
var err error
|
||||
@@ -93,21 +97,29 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lineNumbers[i] = startLineNum + i
|
||||
lines = append(lines, ResultLine{Num: startLineNum + i})
|
||||
index += len(line)
|
||||
}
|
||||
|
||||
highlighted, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String())
|
||||
// we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
|
||||
hl, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String())
|
||||
highlightedLines := strings.Split(string(hl), "\n")
|
||||
|
||||
// The lines outputted by highlight.Code might not match the original lines, because "highlight" removes the last `\n`
|
||||
lines = lines[:min(len(highlightedLines), len(lines))]
|
||||
highlightedLines = highlightedLines[:len(lines)]
|
||||
for i := 0; i < len(lines); i++ {
|
||||
lines[i].FormattedContent = template.HTML(highlightedLines[i])
|
||||
}
|
||||
|
||||
return &Result{
|
||||
RepoID: result.RepoID,
|
||||
Filename: result.Filename,
|
||||
CommitID: result.CommitID,
|
||||
UpdatedUnix: result.UpdatedUnix,
|
||||
Language: result.Language,
|
||||
Color: result.Color,
|
||||
LineNumbers: lineNumbers,
|
||||
FormattedLines: highlighted,
|
||||
RepoID: result.RepoID,
|
||||
Filename: result.Filename,
|
||||
CommitID: result.CommitID,
|
||||
UpdatedUnix: result.UpdatedUnix,
|
||||
Language: result.Language,
|
||||
Color: result.Color,
|
||||
Lines: lines,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,10 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
|
||||
if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tmpBlock.WriteString("</pre>")
|
||||
return err
|
||||
if _, err := tmpBlock.WriteString("</pre>"); err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpBlock.Flush()
|
||||
}
|
||||
|
||||
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes))
|
||||
|
||||
@@ -103,7 +103,8 @@ func SpecializedMarkdown() goldmark.Markdown {
|
||||
}
|
||||
|
||||
// include language-x class as part of commonmark spec
|
||||
_, err = w.WriteString(`<code class="chroma language-` + string(language) + `">`)
|
||||
// the "display" class is used by "js/markup/math.js" to render the code element as a block
|
||||
_, err = w.WriteString(`<code class="chroma language-` + string(language) + ` display">`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,12 +21,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
AppURL = "http://localhost:3000/"
|
||||
Repo = "gogits/gogs"
|
||||
AppSubURL = AppURL + Repo + "/"
|
||||
AppURL = "http://localhost:3000/"
|
||||
FullURL = AppURL + "gogits/gogs/"
|
||||
)
|
||||
|
||||
// these values should match the Repo const above
|
||||
// these values should match the const above
|
||||
var localMetas = map[string]string{
|
||||
"user": "gogits",
|
||||
"repo": "gogs",
|
||||
@@ -48,13 +47,12 @@ func TestMain(m *testing.M) {
|
||||
|
||||
func TestRender_StandardLinks(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
test := func(input, expected, expectedWiki string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
Base: FullURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
@@ -63,7 +61,7 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||
buffer, err = RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
Base: FullURL,
|
||||
},
|
||||
IsWiki: true,
|
||||
}, input)
|
||||
@@ -74,8 +72,8 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||
googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
|
||||
test("<https://google.com/>", googleRendered, googleRendered)
|
||||
|
||||
lnk := util.URLJoin(AppSubURL, "WikiPage")
|
||||
lnkWiki := util.URLJoin(AppSubURL, "wiki", "WikiPage")
|
||||
lnk := util.URLJoin(FullURL, "WikiPage")
|
||||
lnkWiki := util.URLJoin(FullURL, "wiki", "WikiPage")
|
||||
test("[WikiPage](WikiPage)",
|
||||
`<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`,
|
||||
`<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`)
|
||||
@@ -83,13 +81,12 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||
|
||||
func TestRender_Images(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
Base: FullURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
@@ -99,7 +96,7 @@ func TestRender_Images(t *testing.T) {
|
||||
url := "../../.images/src/02/train.jpg"
|
||||
title := "Train"
|
||||
href := "https://gitea.io"
|
||||
result := util.URLJoin(AppSubURL, url)
|
||||
result := util.URLJoin(FullURL, url)
|
||||
// hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
|
||||
|
||||
test(
|
||||
@@ -289,15 +286,14 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
|
||||
|
||||
func TestTotal_RenderWiki(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
answers := testAnswers(util.URLJoin(AppSubURL, "wiki"), util.URLJoin(AppSubURL, "wiki", "raw"))
|
||||
answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
|
||||
|
||||
for i := 0; i < len(sameCases); i++ {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
Base: FullURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
@@ -310,12 +306,12 @@ func TestTotal_RenderWiki(t *testing.T) {
|
||||
// Guard wiki sidebar: special syntax
|
||||
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
||||
// rendered
|
||||
`<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
||||
`<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
||||
`,
|
||||
// special syntax
|
||||
`[[Name|Link]]`,
|
||||
// rendered
|
||||
`<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p>
|
||||
`<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -323,7 +319,7 @@ func TestTotal_RenderWiki(t *testing.T) {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
Base: FullURL,
|
||||
},
|
||||
IsWiki: true,
|
||||
}, testCases[i])
|
||||
@@ -334,15 +330,14 @@ func TestTotal_RenderWiki(t *testing.T) {
|
||||
|
||||
func TestTotal_RenderString(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
answers := testAnswers(util.URLJoin(AppSubURL, "src", "master"), util.URLJoin(AppSubURL, "media", "master"))
|
||||
answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
|
||||
|
||||
for i := 0; i < len(sameCases); i++ {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: AppSubURL,
|
||||
Base: FullURL,
|
||||
BranchPath: "master",
|
||||
},
|
||||
Metas: localMetas,
|
||||
@@ -357,7 +352,7 @@ func TestTotal_RenderString(t *testing.T) {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: AppSubURL,
|
||||
Base: FullURL,
|
||||
},
|
||||
}, testCases[i])
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -143,10 +143,18 @@ func (r *Writer) resolveLink(kind, link string) string {
|
||||
// so we need to try to guess the link kind again here
|
||||
kind = org.RegularLink{URL: link}.Kind()
|
||||
}
|
||||
|
||||
base := r.Ctx.Links.Base
|
||||
if r.Ctx.IsWiki {
|
||||
base = r.Ctx.Links.WikiLink()
|
||||
} else if r.Ctx.Links.HasBranchInfo() {
|
||||
base = r.Ctx.Links.SrcLink()
|
||||
}
|
||||
|
||||
if kind == "image" || kind == "video" {
|
||||
base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki)
|
||||
}
|
||||
|
||||
link = util.URLJoin(base, link)
|
||||
}
|
||||
return link
|
||||
|
||||
@@ -19,6 +19,30 @@ const AppURL = "http://localhost:3000/"
|
||||
func TestRender_StandardLinks(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
|
||||
test := func(input, expected string, isWiki bool) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: "/relative-path",
|
||||
BranchPath: "branch/main",
|
||||
},
|
||||
IsWiki: isWiki,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
}
|
||||
|
||||
test("[[https://google.com/]]",
|
||||
`<p><a href="https://google.com/">https://google.com/</a></p>`, false)
|
||||
test("[[WikiPage][The WikiPage Desc]]",
|
||||
`<p><a href="/relative-path/wiki/WikiPage">The WikiPage Desc</a></p>`, true)
|
||||
test("[[ImageLink.svg][The Image Desc]]",
|
||||
`<p><a href="/relative-path/media/branch/main/ImageLink.svg">The Image Desc</a></p>`, false)
|
||||
}
|
||||
|
||||
func TestRender_InternalLinks(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
@@ -31,12 +55,14 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
}
|
||||
|
||||
test("[[https://google.com/]]",
|
||||
`<p><a href="https://google.com/">https://google.com/</a></p>`)
|
||||
test("[[WikiPage][The WikiPage Desc]]",
|
||||
`<p><a href="/relative-path/WikiPage">The WikiPage Desc</a></p>`)
|
||||
test("[[ImageLink.svg][The Image Desc]]",
|
||||
`<p><a href="/relative-path/media/branch/main/ImageLink.svg">The Image Desc</a></p>`)
|
||||
test("[[file:test.org][Test]]",
|
||||
`<p><a href="/relative-path/src/branch/main/test.org">Test</a></p>`)
|
||||
test("[[./test.org][Test]]",
|
||||
`<p><a href="/relative-path/src/branch/main/test.org">Test</a></p>`)
|
||||
test("[[test.org][Test]]",
|
||||
`<p><a href="/relative-path/src/branch/main/test.org">Test</a></p>`)
|
||||
test("[[path/to/test.org][Test]]",
|
||||
`<p><a href="/relative-path/src/branch/main/path/to/test.org">Test</a></p>`)
|
||||
}
|
||||
|
||||
func TestRender_Media(t *testing.T) {
|
||||
|
||||
@@ -60,6 +60,9 @@ func (q *WorkerPoolQueue[T]) doDispatchBatchToWorker(wg *workerGroup[T], flushCh
|
||||
full = true
|
||||
}
|
||||
|
||||
// TODO: the logic could be improved in the future, to avoid a data-race between "doStartNewWorker" and "workerNum"
|
||||
// The root problem is that if we skip "doStartNewWorker" here, the "workerNum" might be decreased by other workers later
|
||||
// So ideally, it should check whether there are enough workers by some approaches, and start new workers if necessary.
|
||||
q.workerNumMu.Lock()
|
||||
noWorker := q.workerNum == 0
|
||||
if full || noWorker {
|
||||
@@ -143,7 +146,11 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) {
|
||||
log.Debug("Queue %q starts new worker", q.GetName())
|
||||
defer log.Debug("Queue %q stops idle worker", q.GetName())
|
||||
|
||||
atomic.AddInt32(&q.workerStartedCounter, 1) // Only increase counter, used for debugging
|
||||
|
||||
t := time.NewTicker(workerIdleDuration)
|
||||
defer t.Stop()
|
||||
|
||||
keepWorking := true
|
||||
stopWorking := func() {
|
||||
q.workerNumMu.Lock()
|
||||
@@ -158,13 +165,18 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) {
|
||||
case batch, ok := <-q.batchChan:
|
||||
if !ok {
|
||||
stopWorking()
|
||||
} else {
|
||||
q.doWorkerHandle(batch)
|
||||
t.Reset(workerIdleDuration)
|
||||
continue
|
||||
}
|
||||
q.doWorkerHandle(batch)
|
||||
// reset the idle ticker, and drain the tick after reset in case a tick is already triggered
|
||||
t.Reset(workerIdleDuration)
|
||||
select {
|
||||
case <-t.C:
|
||||
default:
|
||||
}
|
||||
case <-t.C:
|
||||
q.workerNumMu.Lock()
|
||||
keepWorking = q.workerNum <= 1
|
||||
keepWorking = q.workerNum <= 1 // keep the last worker running
|
||||
if !keepWorking {
|
||||
q.workerNum--
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ type WorkerPoolQueue[T any] struct {
|
||||
workerMaxNum int
|
||||
workerActiveNum int
|
||||
workerNumMu sync.Mutex
|
||||
|
||||
workerStartedCounter int32
|
||||
}
|
||||
|
||||
type flushType chan struct{}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -175,11 +176,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett
|
||||
}
|
||||
|
||||
func TestWorkerPoolQueueActiveWorkers(t *testing.T) {
|
||||
oldWorkerIdleDuration := workerIdleDuration
|
||||
workerIdleDuration = 300 * time.Millisecond
|
||||
defer func() {
|
||||
workerIdleDuration = oldWorkerIdleDuration
|
||||
}()
|
||||
defer test.MockVariableValue(&workerIdleDuration, 300*time.Millisecond)()
|
||||
|
||||
handler := func(items ...int) (unhandled []int) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -250,3 +247,25 @@ func TestWorkerPoolQueueShutdown(t *testing.T) {
|
||||
q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", qs, handler, false)
|
||||
assert.EqualValues(t, 20, q.GetQueueItemNumber())
|
||||
}
|
||||
|
||||
func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) {
|
||||
defer test.MockVariableValue(&workerIdleDuration, 10*time.Millisecond)()
|
||||
|
||||
handler := func(items ...int) (unhandled []int) {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false)
|
||||
stop := runWorkerPoolQueue(q)
|
||||
for i := 0; i < 20; i++ {
|
||||
assert.NoError(t, q.Push(i))
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
assert.EqualValues(t, 2, q.GetWorkerNumber())
|
||||
assert.EqualValues(t, 2, q.GetWorkerActiveNumber())
|
||||
// when the queue never becomes empty, the existing workers should keep working
|
||||
assert.EqualValues(t, 2, q.workerStartedCounter)
|
||||
stop()
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ var (
|
||||
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
|
||||
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`)
|
||||
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
|
||||
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\')([#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
|
||||
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
|
||||
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
|
||||
issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$))`)
|
||||
issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\')`)
|
||||
// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
|
||||
// e.g. org/repo#12345
|
||||
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
|
||||
|
||||
@@ -429,6 +429,8 @@ func TestRegExp_issueNumericPattern(t *testing.T) {
|
||||
" #12",
|
||||
"#12:",
|
||||
"ref: #12: msg",
|
||||
"\"#1234\"",
|
||||
"'#1234'",
|
||||
}
|
||||
falseTestCases := []string{
|
||||
"# 1234",
|
||||
@@ -459,6 +461,8 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) {
|
||||
"(ABC-123)",
|
||||
"[ABC-123]",
|
||||
"ABC-123:",
|
||||
"\"ABC-123\"",
|
||||
"'ABC-123'",
|
||||
}
|
||||
falseTestCases := []string{
|
||||
"RC-08",
|
||||
|
||||
@@ -21,7 +21,7 @@ var SessionConfig = struct {
|
||||
ProviderConfig string
|
||||
// Cookie name to save session ID. Default is "MacaronSession".
|
||||
CookieName string
|
||||
// Cookie path to store. Default is "/". HINT: there was a bug, the old value doesn't have trailing slash, and could be empty "".
|
||||
// Cookie path to store. Default is "/".
|
||||
CookiePath string
|
||||
// GC interval time in seconds. Default is 3600.
|
||||
Gclifetime int64
|
||||
@@ -49,7 +49,10 @@ func loadSessionFrom(rootCfg ConfigProvider) {
|
||||
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
|
||||
}
|
||||
SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
|
||||
SessionConfig.CookiePath = AppSubURL + "/" // there was a bug, old code only set CookePath=AppSubURL, no trailing slash
|
||||
SessionConfig.CookiePath = AppSubURL
|
||||
if SessionConfig.CookiePath == "" {
|
||||
SessionConfig.CookiePath = "/"
|
||||
}
|
||||
SessionConfig.Secure = sec.Key("COOKIE_SECURE").MustBool(strings.HasPrefix(strings.ToLower(AppURL), "https://"))
|
||||
SessionConfig.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(86400)
|
||||
SessionConfig.Maxlifetime = sec.Key("SESSION_LIFE_TIME").MustInt64(86400)
|
||||
|
||||
@@ -51,18 +51,16 @@ func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
|
||||
|
||||
attrs := make([]string, 0, 10+len(extraAttrs))
|
||||
attrs = append(attrs, extraAttrs...)
|
||||
attrs = append(attrs, `data-tooltip-content`, `data-tooltip-interactive="true"`)
|
||||
attrs = append(attrs, `format="datetime"`, `weekday=""`, `year="numeric"`)
|
||||
attrs = append(attrs, `weekday=""`, `year="numeric"`)
|
||||
|
||||
switch format {
|
||||
case "short":
|
||||
attrs = append(attrs, `month="short"`, `day="numeric"`)
|
||||
case "long":
|
||||
attrs = append(attrs, `month="long"`, `day="numeric"`)
|
||||
case "full":
|
||||
attrs = append(attrs, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`)
|
||||
case "short", "long": // date only
|
||||
attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
|
||||
return template.HTML(fmt.Sprintf(`<gitea-absolute-date %s date="%s">%s</gitea-absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
case "full": // full date including time
|
||||
attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`)
|
||||
return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported format %s", format))
|
||||
}
|
||||
return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ func TestDateTime(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
||||
|
||||
refTimeStr := "2018-01-01T00:00:00Z"
|
||||
refDateStr := "2018-01-01"
|
||||
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
|
||||
refTimeStamp := TimeStamp(refTime.Unix())
|
||||
|
||||
@@ -27,17 +28,20 @@ func TestDateTime(t *testing.T) {
|
||||
assert.EqualValues(t, "-", DateTime("short", TimeStamp(0)))
|
||||
|
||||
actual := DateTime("short", "invalid")
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="invalid">invalid</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="invalid">invalid</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refTimeStr)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refTime)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="2018-01-01T00:00:00Z">2018-01-01</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refDateStr)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01">2018-01-01</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refTimeStamp)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="2017-12-31T19:00:00-05:00">2017-12-31</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("full", refTimeStamp)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<relative-time weekday="" year="numeric" format="datetime" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" data-tooltip-content data-tooltip-interactive="true" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ package translation
|
||||
import "fmt"
|
||||
|
||||
// MockLocale provides a mocked locale without any translations
|
||||
type MockLocale struct{}
|
||||
type MockLocale struct {
|
||||
Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
|
||||
}
|
||||
|
||||
var _ Locale = (*MockLocale)(nil)
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ func Match(tags ...language.Tag) language.Tag {
|
||||
// locale represents the information of localization.
|
||||
type locale struct {
|
||||
i18n.Locale
|
||||
Lang, LangName string // these fields are used directly in templates: .i18n.Lang
|
||||
Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
|
||||
msgPrinter *message.Printer
|
||||
}
|
||||
|
||||
|
||||
@@ -246,6 +246,7 @@ email_title = Email Settings
|
||||
smtp_addr = SMTP Host
|
||||
smtp_port = SMTP Port
|
||||
smtp_from = Send Email As
|
||||
smtp_from_invalid = The "Send Email As" address is invalid
|
||||
smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" <email@example.com> format.
|
||||
mailer_user = SMTP Username
|
||||
mailer_password = SMTP Password
|
||||
@@ -566,6 +567,8 @@ enterred_invalid_repo_name = The repository name you entered is incorrect.
|
||||
enterred_invalid_org_name = The organization name you entered is incorrect.
|
||||
enterred_invalid_owner_name = The new owner name is not valid.
|
||||
enterred_invalid_password = The password you entered is incorrect.
|
||||
unset_password = The login user has not set the password.
|
||||
unsupported_login_type = The login type is not supported to delete account.
|
||||
user_not_exist = The user does not exist.
|
||||
team_not_exist = The team does not exist.
|
||||
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization.
|
||||
@@ -1774,9 +1777,9 @@ pulls.unrelated_histories = Merge Failed: The merge head and base do not share a
|
||||
pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again.
|
||||
pulls.head_out_of_date = Merge Failed: Whilst generating the merge, the head was updated. Hint: Try again.
|
||||
pulls.has_merged = Failed: The pull request has been merged, you cannot merge again or change the target branch.
|
||||
pulls.push_rejected = Merge Failed: The push was rejected. Review the Git Hooks for this repository.
|
||||
pulls.push_rejected = Push Failed: The push was rejected. Review the Git Hooks for this repository.
|
||||
pulls.push_rejected_summary = Full Rejection Message
|
||||
pulls.push_rejected_no_message = Merge Failed: The push was rejected but there was no remote message.<br>Review the Git Hooks for this repository
|
||||
pulls.push_rejected_no_message = Push Failed: The push was rejected but there was no remote message. Review the Git Hooks for this repository
|
||||
pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.`
|
||||
pulls.status_checking = Some checks are pending
|
||||
pulls.status_checks_success = All checks were successful
|
||||
|
||||
2
public/assets/img/svg/gitea-twitter.svg
generated
2
public/assets/img/svg/gitea-twitter.svg
generated
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" aria-hidden="true" class="gitea-twitter__svg gitea-twitter__gitea-twitter svg gitea-twitter" clip-rule="evenodd" viewBox="-89.009 -46.884 643.937 446.884" width="16" height="16"><path fill="#1da1f2" fill-rule="nonzero" d="M154.729 400c185.669 0 287.205-153.876 287.205-287.312 0-4.37-.089-8.72-.286-13.052A205.304 205.304 0 0 0 492 47.346c-18.087 8.044-37.55 13.458-57.968 15.899 20.841-12.501 36.84-32.278 44.389-55.852a202.42 202.42 0 0 1-64.098 24.511C395.903 12.276 369.679 0 340.641 0c-55.744 0-100.948 45.222-100.948 100.965 0 7.925.887 15.631 2.619 23.025-83.895-4.223-158.287-44.405-208.074-105.504A100.739 100.739 0 0 0 20.57 69.24c0 35.034 17.82 65.961 44.92 84.055a100.172 100.172 0 0 1-45.716-12.63c-.015.424-.015.837-.015 1.29 0 48.903 34.794 89.734 80.982 98.986a101.036 101.036 0 0 1-26.617 3.553c-6.493 0-12.821-.639-18.971-1.82 12.851 40.122 50.115 69.319 94.296 70.135-34.549 27.089-78.07 43.224-125.371 43.224A204.9 204.9 0 0 1 0 354.634c44.674 28.645 97.72 45.359 154.734 45.359"/></svg>
|
||||
<svg viewBox="0 0 24 24" class="svg gitea-twitter" xmlns="http://www.w3.org/2000/svg" width="16" height="16" aria-hidden="true"><path d="M14.095 10.316 22.286 1h-1.94L13.23 9.088 7.551 1H1l8.59 12.231L1 23h1.94l7.51-8.543 6 8.543H23l-8.905-12.684zm-2.658 3.022-.872-1.218L3.64 2.432h2.98l5.59 7.821.869 1.219 7.265 10.166h-2.982l-5.926-8.3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 349 B |
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
npm_module "code.gitea.io/gitea/modules/packages/npm"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *npm_module.PackageMetadata {
|
||||
@@ -98,7 +99,7 @@ func createPackageSearchResponse(pds []*packages_model.PackageDescriptor, total
|
||||
Maintainers: []npm_module.User{}, // npm cli needs this field
|
||||
Keywords: metadata.Keywords,
|
||||
Links: &npm_module.PackageSearchPackageLinks{
|
||||
Registry: pd.FullWebLink(),
|
||||
Registry: setting.AppURL + "api/packages/" + pd.Owner.Name + "/npm",
|
||||
Homepage: metadata.ProjectURL,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
AppURL = "http://localhost:3000/"
|
||||
Repo = "gogits/gogs"
|
||||
AppSubURL = AppURL + Repo + "/"
|
||||
AppURL = "http://localhost:3000/"
|
||||
Repo = "gogits/gogs"
|
||||
FullURL = AppURL + Repo + "/"
|
||||
)
|
||||
|
||||
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
|
||||
@@ -74,20 +74,20 @@ func TestAPI_RenderGFM(t *testing.T) {
|
||||
// rendered
|
||||
`<p>Wiki! Enjoy :)</p>
|
||||
<ul>
|
||||
<li><a href="` + AppSubURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||
<li><a href="` + AppSubURL + `wiki/Tips" rel="nofollow">Tips</a></li>
|
||||
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||
<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
|
||||
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
|
||||
</ul>
|
||||
`,
|
||||
// Guard wiki sidebar: special syntax
|
||||
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
||||
// rendered
|
||||
`<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
||||
`<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
||||
`,
|
||||
// special syntax
|
||||
`[[Name|Link]]`,
|
||||
// rendered
|
||||
`<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p>
|
||||
`<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
|
||||
`,
|
||||
// empty
|
||||
``,
|
||||
@@ -111,8 +111,8 @@ Here are some links to the most important topics. You can find the full list of
|
||||
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
|
||||
<h2 id="user-content-quick-links">Quick Links</h2>
|
||||
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
|
||||
<p><a href="` + AppSubURL + `wiki/Configuration" rel="nofollow">Configuration</a>
|
||||
<a href="` + AppSubURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + AppSubURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
||||
<p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
|
||||
<a href="` + FullURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + FullURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
||||
`,
|
||||
}
|
||||
|
||||
|
||||
@@ -649,6 +649,7 @@ func UpdateFile(ctx *context.APIContext) {
|
||||
apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
if apiOpts.BranchName == "" {
|
||||
|
||||
@@ -707,7 +707,7 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
form.Labels = make([]int64, 0)
|
||||
}
|
||||
|
||||
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil {
|
||||
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, 0); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
|
||||
return
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
@@ -221,6 +222,10 @@ func CreateRelease(ctx *context.APIContext) {
|
||||
// "409":
|
||||
// "$ref": "#/responses/error"
|
||||
form := web.GetForm(ctx).(*api.CreateReleaseOption)
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
|
||||
return
|
||||
}
|
||||
rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
|
||||
if err != nil {
|
||||
if !repo_model.IsErrReleaseNotExist(err) {
|
||||
|
||||
@@ -38,6 +38,7 @@ func ProtocolMiddlewares() (handlers []any) {
|
||||
})
|
||||
})
|
||||
|
||||
// wrap the request and response, use the process context and add it to the process manager
|
||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
|
||||
|
||||
@@ -7,6 +7,7 @@ package install
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -417,6 +418,11 @@ func SubmitInstall(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
|
||||
if _, err := mail.ParseAddress(form.SMTPFrom); err != nil {
|
||||
ctx.RenderWithErr(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form)
|
||||
return
|
||||
}
|
||||
|
||||
cfg.Section("mailer").Key("ENABLED").SetValue("true")
|
||||
cfg.Section("mailer").Key("SMTP_ADDR").SetValue(form.SMTPAddr)
|
||||
cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
|
||||
|
||||
@@ -109,9 +109,21 @@ func resetLocale(ctx *context.Context, u *user_model.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RedirectAfterLogin(ctx *context.Context) {
|
||||
redirectTo := ctx.FormString("redirect_to")
|
||||
if redirectTo == "" {
|
||||
redirectTo = ctx.GetSiteCookie("redirect_to")
|
||||
}
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL)
|
||||
if setting.LandingPageURL == setting.LandingPageLogin {
|
||||
nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page
|
||||
}
|
||||
ctx.RedirectToFirst(redirectTo, nextRedirectTo)
|
||||
}
|
||||
|
||||
func checkAutoLogin(ctx *context.Context) bool {
|
||||
// Check auto-login
|
||||
isSucceed, err := AutoSignIn(ctx)
|
||||
isSucceed, err := AutoSignIn(ctx) // try to auto-login
|
||||
if err != nil {
|
||||
ctx.ServerError("AutoSignIn", err)
|
||||
return true
|
||||
@@ -120,17 +132,10 @@ func checkAutoLogin(ctx *context.Context) bool {
|
||||
redirectTo := ctx.FormString("redirect_to")
|
||||
if len(redirectTo) > 0 {
|
||||
middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
|
||||
} else {
|
||||
redirectTo = ctx.GetSiteCookie("redirect_to")
|
||||
}
|
||||
|
||||
if isSucceed {
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL)
|
||||
if setting.LandingPageURL == setting.LandingPageLogin {
|
||||
nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page
|
||||
}
|
||||
ctx.RedirectToFirst(redirectTo, nextRedirectTo)
|
||||
RedirectAfterLogin(ctx)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -146,6 +151,10 @@ func SignIn(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
RedirectAfterLogin(ctx)
|
||||
return
|
||||
}
|
||||
orderedOAuth2Names, oauth2Providers, err := oauth2.GetOAuth2ProvidersMap(true)
|
||||
if err != nil {
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
|
||||
43
routers/web/auth/auth_test.go
Normal file
43
routers/web/auth/auth_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/contexttest"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserLogin(t *testing.T) {
|
||||
ctx, resp := contexttest.MockContext(t, "/user/login")
|
||||
SignIn(ctx)
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
|
||||
ctx, resp = contexttest.MockContext(t, "/user/login")
|
||||
ctx.IsSigned = true
|
||||
SignIn(ctx)
|
||||
assert.Equal(t, http.StatusSeeOther, resp.Code)
|
||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||
|
||||
ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to=/other")
|
||||
ctx.IsSigned = true
|
||||
SignIn(ctx)
|
||||
assert.Equal(t, "/other", test.RedirectURL(resp))
|
||||
|
||||
ctx, resp = contexttest.MockContext(t, "/user/login")
|
||||
ctx.Req.AddCookie(&http.Cookie{Name: "redirect_to", Value: "/other-cookie"})
|
||||
ctx.IsSigned = true
|
||||
SignIn(ctx)
|
||||
assert.Equal(t, "/other-cookie", test.RedirectURL(resp))
|
||||
|
||||
ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to="+url.QueryEscape("https://example.com"))
|
||||
ctx.IsSigned = true
|
||||
SignIn(ctx)
|
||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package explore
|
||||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
@@ -24,8 +25,16 @@ func Organizations(ctx *context.Context) {
|
||||
visibleTypes = append(visibleTypes, structs.VisibleTypeLimited, structs.VisibleTypePrivate)
|
||||
}
|
||||
|
||||
if ctx.FormString("sort") == "" {
|
||||
ctx.SetFormString("sort", UserSearchDefaultSortType)
|
||||
supportedSortOrders := container.SetOf(
|
||||
"newest",
|
||||
"oldest",
|
||||
"alphabetically",
|
||||
"reversealphabetically",
|
||||
)
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if sortOrder == "" {
|
||||
sortOrder = "newest"
|
||||
ctx.SetFormString("sort", sortOrder)
|
||||
}
|
||||
|
||||
RenderUserSearch(ctx, &user_model.SearchUserOptions{
|
||||
@@ -33,5 +42,7 @@ func Organizations(ctx *context.Context) {
|
||||
Type: user_model.UserTypeOrganization,
|
||||
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
||||
Visible: visibleTypes,
|
||||
|
||||
SupportedSortOrders: supportedSortOrders,
|
||||
}, tplExploreUsers)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -60,8 +61,8 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
|
||||
|
||||
// we can not set orderBy to `models.SearchOrderByXxx`, because there may be a JOIN in the statement, different tables may have the same name columns
|
||||
|
||||
ctx.Data["SortType"] = ctx.FormString("sort")
|
||||
switch ctx.FormString("sort") {
|
||||
sortOrder := ctx.FormString("sort")
|
||||
switch sortOrder {
|
||||
case "newest":
|
||||
orderBy = "`user`.id DESC"
|
||||
case "oldest":
|
||||
@@ -80,9 +81,15 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
|
||||
fallthrough
|
||||
default:
|
||||
// in case the sortType is not valid, we set it to recentupdate
|
||||
ctx.Data["SortType"] = "recentupdate"
|
||||
sortOrder = "recentupdate"
|
||||
orderBy = "`user`.updated_unix DESC"
|
||||
}
|
||||
ctx.Data["SortType"] = sortOrder
|
||||
|
||||
if opts.SupportedSortOrders != nil && !opts.SupportedSortOrders.Contains(sortOrder) {
|
||||
ctx.NotFound("unsupported sort order", nil)
|
||||
return
|
||||
}
|
||||
|
||||
opts.Keyword = ctx.FormTrim("q")
|
||||
opts.OrderBy = orderBy
|
||||
@@ -133,8 +140,16 @@ func Users(ctx *context.Context) {
|
||||
ctx.Data["PageIsExploreUsers"] = true
|
||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
|
||||
if ctx.FormString("sort") == "" {
|
||||
ctx.SetFormString("sort", UserSearchDefaultSortType)
|
||||
supportedSortOrders := container.SetOf(
|
||||
"newest",
|
||||
"oldest",
|
||||
"alphabetically",
|
||||
"reversealphabetically",
|
||||
)
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if sortOrder == "" {
|
||||
sortOrder = "newest"
|
||||
ctx.SetFormString("sort", sortOrder)
|
||||
}
|
||||
|
||||
RenderUserSearch(ctx, &user_model.SearchUserOptions{
|
||||
@@ -143,5 +158,7 @@ func Users(ctx *context.Context) {
|
||||
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
|
||||
|
||||
SupportedSortOrders: supportedSortOrders,
|
||||
}, tplExploreUsers)
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ func UpdateIssueProject(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil {
|
||||
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,17 +61,17 @@ func List(ctx *context.Context) {
|
||||
|
||||
var workflows []Workflow
|
||||
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("IsEmpty", err)
|
||||
return
|
||||
} else if !empty {
|
||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
entries, err := actions.ListWorkflows(commit)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("ListWorkflows", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func List(ctx *context.Context) {
|
||||
workflow := Workflow{Entry: *entry}
|
||||
content, err := actions.GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("GetContentFromEntry", err)
|
||||
return
|
||||
}
|
||||
wf, err := model.ReadWorkflow(bytes.NewReader(content))
|
||||
@@ -173,7 +173,7 @@ func List(ctx *context.Context) {
|
||||
|
||||
runs, total, err := actions_model.FindRuns(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("FindAndCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func List(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if err := runs.LoadTriggerUser(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("LoadTriggerUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func List(ctx *context.Context) {
|
||||
|
||||
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("GetActors", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -260,10 +261,14 @@ func ViewPost(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
// Rerun will rerun jobs in the given run
|
||||
// jobIndex = 0 means rerun all jobs
|
||||
// If jobIndexStr is a blank string, it means rerun all jobs
|
||||
func Rerun(ctx *context_module.Context) {
|
||||
runIndex := ctx.ParamsInt64("run")
|
||||
jobIndex := ctx.ParamsInt64("job")
|
||||
jobIndexStr := ctx.Params("job")
|
||||
var jobIndex int64
|
||||
if jobIndexStr != "" {
|
||||
jobIndex, _ = strconv.ParseInt(jobIndexStr, 10, 64)
|
||||
}
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
if err != nil {
|
||||
@@ -284,7 +289,7 @@ func Rerun(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if jobIndex != 0 {
|
||||
if jobIndexStr != "" {
|
||||
jobs = []*actions_model.ActionRunJob{job}
|
||||
}
|
||||
|
||||
|
||||
@@ -161,9 +161,6 @@ func editFile(ctx *context.Context, isNewFile bool) {
|
||||
}
|
||||
|
||||
d, _ := io.ReadAll(dataRc)
|
||||
if err := dataRc.Close(); err != nil {
|
||||
log.Error("Error whilst closing blob data: %v", err)
|
||||
}
|
||||
|
||||
buf = append(buf, d...)
|
||||
if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
|
||||
|
||||
@@ -1182,6 +1182,14 @@ func NewIssuePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if projectID > 0 {
|
||||
if !ctx.Repo.CanRead(unit.TypeProjects) {
|
||||
// User must also be able to see the project.
|
||||
ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if setting.Attachment.Enabled {
|
||||
attachments = form.Files
|
||||
}
|
||||
@@ -1214,7 +1222,7 @@ func NewIssuePost(ctx *context.Context) {
|
||||
Ref: form.Ref,
|
||||
}
|
||||
|
||||
if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs); err != nil {
|
||||
if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs, projectID); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
|
||||
return
|
||||
@@ -1223,18 +1231,6 @@ func NewIssuePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if projectID > 0 {
|
||||
if !ctx.Repo.CanRead(unit.TypeProjects) {
|
||||
// User must also be able to see the project.
|
||||
ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects")
|
||||
return
|
||||
}
|
||||
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
|
||||
if ctx.FormString("redirect_after_creation") == "project" && projectID > 0 {
|
||||
ctx.JSONRedirect(ctx.Repo.RepoLink + "/projects/" + strconv.FormatInt(projectID, 10))
|
||||
|
||||
@@ -94,7 +94,7 @@ func canSoftDeleteContentHistory(ctx *context.Context, issue *issues_model.Issue
|
||||
// CanWrite means the doer can manage the issue/PR list
|
||||
if ctx.Repo.IsOwner() || ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
canSoftDelete = true
|
||||
} else {
|
||||
} else if ctx.Doer != nil {
|
||||
// for read-only users, they could still post issues or comments,
|
||||
// they should be able to delete the history related to their own issue/comment, a case is:
|
||||
// 1. the user posts some sensitive data
|
||||
@@ -186,6 +186,10 @@ func SoftDeleteContentHistory(ctx *context.Context) {
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if ctx.Doer == nil {
|
||||
ctx.NotFound("Require SignIn", nil)
|
||||
return
|
||||
}
|
||||
|
||||
commentID := ctx.FormInt64("comment_id")
|
||||
historyID := ctx.FormInt64("history_id")
|
||||
|
||||
@@ -396,7 +396,7 @@ func UpdateIssueProject(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil {
|
||||
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1469,7 +1469,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Flash.Error(flashError)
|
||||
ctx.JSONRedirect(pullIssue.Link()) // FIXME: it's unfriendly, and will make the content lost
|
||||
ctx.JSONRedirect(ctx.Link + "?" + ctx.Req.URL.RawQuery) // FIXME: it's unfriendly, and will make the content lost
|
||||
return
|
||||
}
|
||||
ctx.ServerError("NewPullRequest", err)
|
||||
@@ -1481,7 +1481,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
|
||||
return
|
||||
}
|
||||
if err := issues_model.ChangeProjectAssign(pullIssue, ctx.Doer, projectID); err != nil {
|
||||
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas any) {
|
||||
func TestWiki(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_pages")
|
||||
ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki")
|
||||
ctx.SetParams("*", "Home")
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
Wiki(ctx)
|
||||
|
||||
@@ -6,6 +6,7 @@ package user
|
||||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
@@ -125,5 +126,21 @@ func LoadHeaderCount(ctx *context.Context) error {
|
||||
}
|
||||
ctx.Data["RepoCount"] = repoCount
|
||||
|
||||
var projectType project_model.Type
|
||||
if ctx.ContextUser.IsOrganization() {
|
||||
projectType = project_model.TypeOrganization
|
||||
} else {
|
||||
projectType = project_model.TypeIndividual
|
||||
}
|
||||
projectCount, err := project_model.CountProjects(ctx, project_model.SearchOptions{
|
||||
OwnerID: ctx.ContextUser.ID,
|
||||
IsClosed: util.OptionalBoolOf(false),
|
||||
Type: projectType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Data["ProjectCount"] = projectCount
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/contexttest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -117,3 +119,18 @@ func TestMilestonesForSpecificRepo(t *testing.T) {
|
||||
assert.Len(t, ctx.Data["Milestones"], 1)
|
||||
assert.Len(t, ctx.Data["Repos"], 2) // both repo 42 and 1 have milestones and both are owned by user 2
|
||||
}
|
||||
|
||||
func TestDashboardPagination(t *testing.T) {
|
||||
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
|
||||
page := context.NewPagination(10, 3, 1, 3)
|
||||
|
||||
setting.AppSubURL = "/SubPath"
|
||||
out, err := ctx.RenderToString("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, out, `<a class=" item navigation" href="/SubPath/?page=2">`)
|
||||
|
||||
setting.AppSubURL = ""
|
||||
out, err = ctx.RenderToString("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, out, `<a class=" item navigation" href="/?page=2">`)
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func RedirectToLastVersion(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(pd.FullWebLink())
|
||||
ctx.Redirect(pd.VersionWebLink())
|
||||
}
|
||||
|
||||
// ViewPackageVersion displays a single package version
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/auth/source/db"
|
||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
"code.gitea.io/gitea/services/user"
|
||||
@@ -236,11 +238,24 @@ func DeleteAccount(ctx *context.Context) {
|
||||
ctx.Data["PageIsSettingsAccount"] = true
|
||||
|
||||
if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
switch {
|
||||
case user_model.IsErrUserNotExist(err):
|
||||
loadAccountData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.user_not_exist"), tplSettingsAccount, nil)
|
||||
case errors.Is(err, smtp.ErrUnsupportedLoginType):
|
||||
loadAccountData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.unsupported_login_type"), tplSettingsAccount, nil)
|
||||
case errors.As(err, &db.ErrUserPasswordNotSet{}):
|
||||
loadAccountData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.unset_password"), tplSettingsAccount, nil)
|
||||
case errors.As(err, &db.ErrUserPasswordInvalid{}):
|
||||
loadAccountData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsAccount, nil)
|
||||
} else {
|
||||
default:
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -1360,7 +1360,7 @@ func registerRoutes(m *web.Route) {
|
||||
})
|
||||
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
|
||||
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
|
||||
m.Post("/artifacts", actions.ArtifactsView)
|
||||
m.Get("/artifacts", actions.ArtifactsView)
|
||||
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
|
||||
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
|
||||
})
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
@@ -76,12 +78,15 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
|
||||
type jobStatusResolver struct {
|
||||
statuses map[int64]actions_model.Status
|
||||
needs map[int64][]int64
|
||||
jobMap map[int64]*actions_model.ActionRunJob
|
||||
}
|
||||
|
||||
func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
|
||||
idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
|
||||
jobMap := make(map[int64]*actions_model.ActionRunJob)
|
||||
for _, job := range jobs {
|
||||
idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
|
||||
jobMap[job.ID] = job
|
||||
}
|
||||
|
||||
statuses := make(map[int64]actions_model.Status, len(jobs))
|
||||
@@ -97,6 +102,7 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
|
||||
return &jobStatusResolver{
|
||||
statuses: statuses,
|
||||
needs: needs,
|
||||
jobMap: jobMap,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +141,20 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
|
||||
if allSucceed {
|
||||
ret[id] = actions_model.StatusWaiting
|
||||
} else {
|
||||
ret[id] = actions_model.StatusSkipped
|
||||
// If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed.
|
||||
// See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
|
||||
always := false
|
||||
if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
|
||||
_, wfJob := wfJobs[0].Job()
|
||||
expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}"))
|
||||
always = expr == "always()"
|
||||
}
|
||||
|
||||
if always {
|
||||
ret[id] = actions_model.StatusWaiting
|
||||
} else {
|
||||
ret[id] = actions_model.StatusSkipped
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,62 @@ func Test_jobStatusResolver_Resolve(t *testing.T) {
|
||||
},
|
||||
want: map[int64]actions_model.Status{},
|
||||
},
|
||||
{
|
||||
name: "with ${{ always() }} condition",
|
||||
jobs: actions_model.ActionJobList{
|
||||
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||
`
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
job2:
|
||||
runs-on: ubuntu-latest
|
||||
needs: job1
|
||||
if: ${{ always() }}
|
||||
steps:
|
||||
- run: echo "always run"
|
||||
`)},
|
||||
},
|
||||
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
||||
},
|
||||
{
|
||||
name: "with always() condition",
|
||||
jobs: actions_model.ActionJobList{
|
||||
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||
`
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
job2:
|
||||
runs-on: ubuntu-latest
|
||||
needs: job1
|
||||
if: always()
|
||||
steps:
|
||||
- run: echo "always run"
|
||||
`)},
|
||||
},
|
||||
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
||||
},
|
||||
{
|
||||
name: "without always() condition",
|
||||
jobs: actions_model.ActionJobList{
|
||||
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||
`
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
job2:
|
||||
runs-on: ubuntu-latest
|
||||
needs: job1
|
||||
steps:
|
||||
- run: echo "not always run"
|
||||
`)},
|
||||
},
|
||||
want: map[int64]actions_model.Status{2: actions_model.StatusSkipped},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -153,7 +153,13 @@ func (n *actionsNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo
|
||||
} else {
|
||||
action = api.HookIssueAssigned
|
||||
}
|
||||
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestAssign, action)
|
||||
|
||||
hookEvent := webhook_module.HookEventIssueAssign
|
||||
if issue.IsPull {
|
||||
hookEvent = webhook_module.HookEventPullRequestAssign
|
||||
}
|
||||
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, action)
|
||||
}
|
||||
|
||||
// IssueChangeMilestone notifies assignee to notifiers
|
||||
@@ -166,14 +172,26 @@ func (n *actionsNotifier) IssueChangeMilestone(ctx context.Context, doer *user_m
|
||||
} else {
|
||||
action = api.HookIssueDemilestoned
|
||||
}
|
||||
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestMilestone, action)
|
||||
|
||||
hookEvent := webhook_module.HookEventIssueMilestone
|
||||
if issue.IsPull {
|
||||
hookEvent = webhook_module.HookEventPullRequestMilestone
|
||||
}
|
||||
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, action)
|
||||
}
|
||||
|
||||
func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue,
|
||||
_, _ []*issues_model.Label,
|
||||
) {
|
||||
ctx = withMethod(ctx, "IssueChangeLabels")
|
||||
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestLabel, api.HookIssueLabelUpdated)
|
||||
|
||||
hookEvent := webhook_module.HookEventIssueLabel
|
||||
if issue.IsPull {
|
||||
hookEvent = webhook_module.HookEventPullRequestLabel
|
||||
}
|
||||
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, api.HookIssueLabelUpdated)
|
||||
}
|
||||
|
||||
func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, event webhook_module.HookEventType, action api.HookIssueAction) {
|
||||
|
||||
@@ -113,6 +113,9 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
|
||||
return nil
|
||||
}
|
||||
if input.Repo.IsEmpty {
|
||||
return nil
|
||||
}
|
||||
if unit_model.TypeActions.UnitGlobalDisabled() {
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks: %v", err)
|
||||
@@ -456,6 +459,10 @@ func handleSchedules(
|
||||
|
||||
// DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
|
||||
func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
|
||||
if repo.IsEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(context.Background(), repo.RepoPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("git.OpenRepository: %w", err)
|
||||
|
||||
@@ -38,6 +38,7 @@ func isContainerPath(req *http.Request) bool {
|
||||
var (
|
||||
gitRawOrAttachPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`)
|
||||
lfsPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
|
||||
archivePathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/archive/`)
|
||||
)
|
||||
|
||||
func isGitRawOrAttachPath(req *http.Request) bool {
|
||||
@@ -54,6 +55,10 @@ func isGitRawOrAttachOrLFSPath(req *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isArchivePath(req *http.Request) bool {
|
||||
return archivePathRe.MatchString(req.URL.Path)
|
||||
}
|
||||
|
||||
// handleSignIn clears existing session variables and stores new ones for the specified user object
|
||||
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) {
|
||||
// We need to regenerate the session...
|
||||
|
||||
@@ -133,7 +133,7 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
|
||||
func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
||||
// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
|
||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
|
||||
!isGitRawOrAttachPath(req) {
|
||||
!isGitRawOrAttachPath(req) && !isArchivePath(req) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
39
services/context/base_test.go
Normal file
39
services/context/base_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRedirect(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
cases := []struct {
|
||||
url string
|
||||
keep bool
|
||||
}{
|
||||
{"http://test", false},
|
||||
{"https://test", false},
|
||||
{"//test", false},
|
||||
{"/://test", true},
|
||||
{"/test", true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
resp := httptest.NewRecorder()
|
||||
b, cleanup := context.NewBaseContext(resp, req)
|
||||
resp.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "dummy"}).String())
|
||||
b.Redirect(c.url)
|
||||
cleanup()
|
||||
has := resp.Header().Get("Set-Cookie") == "i_like_gitea=dummy"
|
||||
assert.Equal(t, c.keep, has, "url = %q", c.url)
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_m
|
||||
Name: pd.Package.Name,
|
||||
Version: pd.Version.Version,
|
||||
CreatedAt: pd.Version.CreatedUnix.AsTime(),
|
||||
HTMLURL: pd.FullWebLink(),
|
||||
HTMLURL: pd.VersionHTMLURL(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,24 @@ import (
|
||||
)
|
||||
|
||||
// NewIssue creates new issue with labels for repository.
|
||||
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64) error {
|
||||
if err := issues_model.NewIssue(ctx, repo, issue, labelIDs, uuids); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
|
||||
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64, projectID int64) error {
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := issues_model.NewIssue(ctx, repo, issue, labelIDs, uuids); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if projectID > 0 {
|
||||
if err := issues_model.ChangeProjectAssign(ctx, issue, issue.Poster, projectID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
|
||||
|
||||
@@ -34,9 +34,9 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
|
||||
}
|
||||
}
|
||||
|
||||
for _, commitStatus := range commitStatuses {
|
||||
for _, gp := range requiredContextsGlob {
|
||||
var targetStatus structs.CommitStatusState
|
||||
for _, gp := range requiredContextsGlob {
|
||||
for _, commitStatus := range commitStatuses {
|
||||
if gp.Match(commitStatus.Context) {
|
||||
targetStatus = commitStatus.State
|
||||
matchedCount++
|
||||
@@ -44,17 +44,21 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
|
||||
}
|
||||
}
|
||||
|
||||
if targetStatus != "" && targetStatus.NoBetterThan(returnedStatus) {
|
||||
// If required rule not match any action, then it is pending
|
||||
if targetStatus == "" {
|
||||
if structs.CommitStatusPending.NoBetterThan(returnedStatus) {
|
||||
returnedStatus = structs.CommitStatusPending
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if targetStatus.NoBetterThan(returnedStatus) {
|
||||
returnedStatus = targetStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matchedCount != len(requiredContexts) {
|
||||
return structs.CommitStatusPending
|
||||
}
|
||||
|
||||
if matchedCount == 0 {
|
||||
if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
|
||||
status := git_model.CalcCommitStatus(commitStatuses)
|
||||
if status != nil {
|
||||
return status.State
|
||||
|
||||
65
services/pull/commit_status_test.go
Normal file
65
services/pull/commit_status_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2024 The Gitea Authors.
|
||||
// All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package pull
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMergeRequiredContextsCommitStatus(t *testing.T) {
|
||||
testCases := [][]*git_model.CommitStatus{
|
||||
{
|
||||
{Context: "Build 1", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 3", State: structs.CommitStatusSuccess},
|
||||
},
|
||||
{
|
||||
{Context: "Build 1", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2t", State: structs.CommitStatusPending},
|
||||
},
|
||||
{
|
||||
{Context: "Build 1", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2t", State: structs.CommitStatusFailure},
|
||||
},
|
||||
{
|
||||
{Context: "Build 1", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2t", State: structs.CommitStatusSuccess},
|
||||
},
|
||||
{
|
||||
{Context: "Build 1", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2", State: structs.CommitStatusSuccess},
|
||||
{Context: "Build 2t", State: structs.CommitStatusSuccess},
|
||||
},
|
||||
}
|
||||
testCasesRequiredContexts := [][]string{
|
||||
{"Build*"},
|
||||
{"Build*", "Build 2t*"},
|
||||
{"Build*", "Build 2t*"},
|
||||
{"Build*", "Build 2t*", "Build 3*"},
|
||||
{"Build*", "Build *", "Build 2t*", "Build 1*"},
|
||||
}
|
||||
|
||||
testCasesExpected := []structs.CommitStatusState{
|
||||
structs.CommitStatusSuccess,
|
||||
structs.CommitStatusPending,
|
||||
structs.CommitStatusFailure,
|
||||
structs.CommitStatusPending,
|
||||
structs.CommitStatusSuccess,
|
||||
}
|
||||
|
||||
for i, commitStatuses := range testCases {
|
||||
if MergeRequiredContextsCommitStatus(commitStatuses, testCasesRequiredContexts[i]) != testCasesExpected[i] {
|
||||
assert.Fail(t, "Test case failed", "Test case %d failed", i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
{{$paginationLink := .Page.GetParams}}
|
||||
{{$paginationParams := .Page.GetParams}}
|
||||
{{$paginationLink := $.Link}}
|
||||
{{if eq $paginationLink AppSubUrl}}{{$paginationLink = print $paginationLink "/"}}{{end}}
|
||||
{{with .Page.Paginater}}
|
||||
{{if gt .TotalPages 1}}
|
||||
<div class="center page buttons">
|
||||
<div class="ui borderless pagination menu">
|
||||
<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$.Link}}{{if $paginationLink}}?{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$paginationLink}}{{if $paginationParams}}?{{$paginationParams}}{{end}}"{{end}}>
|
||||
{{svg "gitea-double-chevron-left" 16 "gt-mr-2"}}
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "admin.first_page"}}</span>
|
||||
</a>
|
||||
<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$paginationLink}}?page={{.Previous}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
|
||||
{{svg "octicon-chevron-left" 16 "gt-mr-2"}}
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.previous"}}</span>
|
||||
</a>
|
||||
@@ -15,14 +17,14 @@
|
||||
{{if eq .Num -1}}
|
||||
<a class="disabled item">...</a>
|
||||
{{else}}
|
||||
<a class="{{if .IsCurrent}}active {{end}}item gt-content-center" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>{{.Num}}</a>
|
||||
<a class="{{if .IsCurrent}}active {{end}}item gt-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.next"}}</span>
|
||||
{{svg "octicon-chevron-right" 16 "gt-ml-2"}}
|
||||
</a>
|
||||
<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$.Link}}?page={{.TotalPages}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
|
||||
<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$paginationLink}}?page={{.TotalPages}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
|
||||
<span class="navigation_label">{{ctx.Locale.Tr "admin.last_page"}}</span>
|
||||
{{svg "gitea-double-chevron-right" 16 "gt-ml-2"}}
|
||||
</a>
|
||||
|
||||
@@ -22,20 +22,7 @@
|
||||
<a role="button" class="ui basic tiny button" rel="nofollow" href="{{$repo.Link}}/src/commit/{{$result.CommitID | PathEscape}}/{{.Filename | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
|
||||
</h4>
|
||||
<div class="ui attached table segment">
|
||||
<div class="file-body file-code code-view">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="lines-num">
|
||||
{{range .LineNumbers}}
|
||||
<a href="{{$repo.Link}}/src/commit/{{$result.CommitID | PathEscape}}/{{$result.Filename | PathEscapeSegments}}#L{{.}}"><span>{{.}}</span></a>
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines}}</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{template "shared/searchfile" dict "RepoLink" $repo.Link "SearchResult" .}}
|
||||
</div>
|
||||
{{template "shared/searchbottom" dict "root" $ "result" .}}
|
||||
</div>
|
||||
|
||||
@@ -110,6 +110,16 @@
|
||||
<div><gitea-origin-url data-url="/test/url"></gitea-origin-url></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>GiteaAbsoluteDate</h1>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="numeric" day="numeric" month="short"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="numeric" day="numeric" month="long"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="" day="numeric" month="numeric"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="" day="numeric" month="numeric" weekday="long"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11T19:00:00-05:00" year="" day="numeric" month="numeric" weekday="long"></gitea-absolute-date></div>
|
||||
<div class="tw-text-text-light-2">relative-time: <relative-time format="datetime" datetime="2024-03-11" year="" day="numeric" month="numeric"></relative-time></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>LocaleNumber</h1>
|
||||
<div>{{ctx.Locale.PrettyNumber 1}}</div>
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a>
|
||||
<a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
|
||||
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{ctx.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}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
|
||||
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
{{if .CanReadProjects}}
|
||||
<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
|
||||
{{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}}
|
||||
{{if .ProjectCount}}
|
||||
<div class="ui small label">{{.ProjectCount}}</div>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if and .IsPackageEnabled .CanReadPackages}}
|
||||
|
||||
@@ -24,14 +24,22 @@
|
||||
const btn = isSSH ? sshBtn : httpsBtn;
|
||||
if (!btn) return;
|
||||
|
||||
let link = btn.getAttribute('data-link');
|
||||
if (link.startsWith('http://') || link.startsWith('https://')) {
|
||||
// use current protocol/host as the clone link
|
||||
const url = new URL(link);
|
||||
url.protocol = window.location.protocol;
|
||||
url.host = window.location.host;
|
||||
link = url.toString();
|
||||
// NOTE: Keep this function in sync with the one in the js folder
|
||||
function toOriginUrl(urlStr) {
|
||||
try {
|
||||
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
|
||||
const {origin, protocol, hostname, port} = window.location;
|
||||
const url = new URL(urlStr, origin);
|
||||
url.protocol = protocol;
|
||||
url.hostname = hostname;
|
||||
url.port = port || (protocol === 'https:' ? '443' : '80');
|
||||
return url.toString();
|
||||
}
|
||||
} catch {}
|
||||
return urlStr;
|
||||
}
|
||||
const link = toOriginUrl(btn.getAttribute('data-link'));
|
||||
|
||||
for (const el of document.getElementsByClassName('js-clone-url')) {
|
||||
el[el.nodeName === 'INPUT' ? 'value' : 'textContent'] = link;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="comment-header-right actions gt-df gt-ac">
|
||||
{{if .Invalidated}}
|
||||
{{$referenceUrl := printf "%s#%s" $.root.Issue.Link .HashTag}}
|
||||
<a href="{{AppSubUrl}}{{$referenceUrl}}" class="ui label basic small" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
|
||||
<a href="{{$referenceUrl}}" class="ui label basic small" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
|
||||
{{ctx.Locale.Tr "repo.issues.review.outdated"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
@@ -11,14 +11,6 @@
|
||||
{{ctx.Locale.Tr "action.compare_commits_general"}}
|
||||
{{end}}
|
||||
</h2>
|
||||
{{if .Flash.WarningMsg}}
|
||||
{{/*
|
||||
There's already an importing of alert.tmpl in new_form.tmpl,
|
||||
but only the negative message will be displayed within forms for some reasons, see semantic.css:10659.
|
||||
To avoid repeated negative messages, the importing here if for .Flash.WarningMsg only.
|
||||
*/}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
{{$BaseCompareName := $.BaseName -}}
|
||||
{{- $HeadCompareName := $.HeadRepo.OwnerName -}}
|
||||
{{- if and (eq $.BaseName $.HeadRepo.OwnerName) (ne $.Repository.Name $.HeadRepo.Name) -}}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
We only handle the case $resolved=true and $invalid=true in this template because if the comment is not resolved it has the outdated label in the comments area (not the header above).
|
||||
The case $resolved=false and $invalid=true is handled in repo/diff/comments.tmpl
|
||||
-->
|
||||
<a href="{{AppSubUrl}}{{$referenceUrl}}" class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
|
||||
<a href="{{$referenceUrl}}" class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
|
||||
{{ctx.Locale.Tr "repo.issues.review.outdated"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<a class="item" href="{{$.RepoLink}}/commit/{{PathEscape .Commit.ID.String}}.diff" download="{{ShortSha .Commit.ID.String}}.diff">{{ctx.Locale.Tr "repo.diff.download_diff"}}</a>
|
||||
{{end}}
|
||||
<a id="expand-files-btn" class="item">{{ctx.Locale.Tr "repo.pulls.expand_files"}}</a>
|
||||
<a id="collapse-files-btn"class="item">{{ctx.Locale.Tr "repo.pulls.collapse_files"}}</a>
|
||||
<a id="collapse-files-btn" class="item">{{ctx.Locale.Tr "repo.pulls.collapse_files"}}</a>
|
||||
{{if .Issue.Index}}
|
||||
{{if .ShowOutdatedComments}}
|
||||
<a class="item" href="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated=false">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{{ctx.Locale.Tr "repo.editor.commit_changes"}}
|
||||
{{- end}}</h3>
|
||||
<div class="field">
|
||||
<input name="commit_summary" placeholder="{{if .PageIsDelete}}{{ctx.Locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{ctx.Locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.add_tmpl"}}{{else if .PageIsPatch}}{{ctx.Locale.Tr "repo.editor.patch"}}{{else}}{{ctx.Locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}" autofocus>
|
||||
<input name="commit_summary" maxlength="100" placeholder="{{if .PageIsDelete}}{{ctx.Locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{ctx.Locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.add_tmpl"}}{{else if .PageIsPatch}}{{ctx.Locale.Tr "repo.editor.patch"}}{{else}}{{ctx.Locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}" autofocus>
|
||||
</div>
|
||||
<div class="field">
|
||||
<textarea name="commit_message" placeholder="{{ctx.Locale.Tr "repo.editor.commit_message_desc"}}" rows="5">{{.commit_message}}</textarea>
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="quick-pull-branch-name {{if not (eq .commit_choice "commit-to-new-branch")}}gt-hidden{{end}}">
|
||||
<div class="new-branch-name-input field {{if .Err_NewBranchName}}error{{end}}">
|
||||
{{svg "octicon-git-branch"}}
|
||||
<input type="text" name="new_branch_name" value="{{.new_branch_name}}" class="input-contrast gt-mr-2 js-quick-pull-new-branch-name" placeholder="{{ctx.Locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{ctx.Locale.Tr "repo.editor.new_branch_name"}}">
|
||||
<input type="text" name="new_branch_name" maxlength="100" value="{{.new_branch_name}}" class="input-contrast gt-mr-2 js-quick-pull-new-branch-name" placeholder="{{ctx.Locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{ctx.Locale.Tr "repo.editor.new_branch_name"}}">
|
||||
<span class="text-muted js-quick-pull-normalization-info"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{{range $i, $v := .TreeNames}}
|
||||
<div class="breadcrumb-divider">/</div>
|
||||
{{if eq $i $l}}
|
||||
<input id="file-name" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.EditorconfigJson}}" required autofocus>
|
||||
<input id="file-name" maxlength="500" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.EditorconfigJson}}" required autofocus>
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
||||
{{else}}
|
||||
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="breadcrumb-divider">:</div>
|
||||
<a class="section" href="{{$.BranchLink}}">{{.BranchName}}</a>
|
||||
<span>{{ctx.Locale.Tr "repo.editor.or"}} <a href="{{$.BranchLink}}">{{ctx.Locale.Tr "repo.editor.cancel_lower"}}</a></span>
|
||||
<input type="hidden" id="tree_path" name="tree_path" value="" required>
|
||||
<input type="hidden" name="tree_path" value="__dummy_for_EditRepoFileForm.TreePath(Required)__">
|
||||
<input id="file-name" type="hidden" value="diff.patch">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{{range $i, $v := .TreeNames}}
|
||||
<div class="breadcrumb-divider">/</div>
|
||||
{{if eq $i $l}}
|
||||
<input type="text" id="file-name" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.add_subdir"}}" autofocus>
|
||||
<input type="text" id="file-name" maxlength="500" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.add_subdir"}}" autofocus>
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
||||
{{else}}
|
||||
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user