mirror of
https://github.com/go-gitea/gitea
synced 2026-02-09 08:18:10 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4923854f6 | ||
|
|
d590607106 | ||
|
|
4746291b08 | ||
|
|
022552d5b6 | ||
|
|
376fa0d8c4 | ||
|
|
be541d9877 | ||
|
|
ae99233db0 | ||
|
|
cbf366643b | ||
|
|
df694f6a7d | ||
|
|
84282c608c | ||
|
|
d1db2b7251 | ||
|
|
6493085aee | ||
|
|
fbf29f29b5 | ||
|
|
6e29242ebb | ||
|
|
56e722f825 | ||
|
|
80e564087d | ||
|
|
571822b6ec | ||
|
|
2a0fbe23b8 | ||
|
|
95901a99c0 | ||
|
|
cb33623bb6 | ||
|
|
9f0c709637 | ||
|
|
5e9fd0ab5e | ||
|
|
f220f4231a | ||
|
|
bce27d0a31 | ||
|
|
12f418a7e8 | ||
|
|
9865aa2394 | ||
|
|
def178ce32 | ||
|
|
2399b4d483 | ||
|
|
ad2cb9863c | ||
|
|
7f833d8f71 | ||
|
|
e67c819cf4 | ||
|
|
83457805bb | ||
|
|
3c79315cf2 | ||
|
|
3e1bd61000 | ||
|
|
18da3f8483 | ||
|
|
2165729d16 | ||
|
|
683b95f0da | ||
|
|
ff565a787f | ||
|
|
f7cca2a290 | ||
|
|
373e78895e | ||
|
|
8ec232817c | ||
|
|
a5c7ac9980 | ||
|
|
bf983735fd | ||
|
|
7a2786ca6c | ||
|
|
b2588338f0 | ||
|
|
8a46a6417e | ||
|
|
5b104a5533 | ||
|
|
f2add36a29 | ||
|
|
564068aa99 | ||
|
|
6a559ad634 | ||
|
|
4dd39eb54a | ||
|
|
acc8100d47 | ||
|
|
1a3803effd | ||
|
|
1183002b32 | ||
|
|
1fc6bc1be2 |
@@ -4,6 +4,60 @@ 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.4](https://github.com/go-gitea/gitea/releases/tag/1.21.4) - 2024-01-16
|
||||
|
||||
* SECURITY
|
||||
* Update github.com/cloudflare/circl (#28789) (#28790)
|
||||
* Require token for GET subscription endpoint (#28765) (#28768)
|
||||
* BUGFIXES
|
||||
* Use refname:strip-2 instead of refname:short when syncing tags (#28797) (#28811)
|
||||
* Fix links in issue card (#28806) (#28807)
|
||||
* Fix nil pointer panic when exec some gitea cli command (#28791) (#28795)
|
||||
* Require token for GET subscription endpoint (#28765) (#28778)
|
||||
* Fix button size in "attached header right" (#28770) (#28774)
|
||||
* Fix `convert.ToTeams` on empty input (#28426) (#28767)
|
||||
* Hide code related setting options in repository when code unit is disabled (#28631) (#28749)
|
||||
* Fix incorrect URL for "Reference in New Issue" (#28716) (#28723)
|
||||
* Fix panic when parsing empty pgsql host (#28708) (#28709)
|
||||
* Upgrade xorm to new version which supported update join for all supported databases (#28590) (#28668)
|
||||
* Fix alpine package files are not rebuilt (#28638) (#28665)
|
||||
* Avoid cycle-redirecting user/login page (#28636) (#28658)
|
||||
* Fix empty ref for cron workflow runs (#28640) (#28647)
|
||||
* Remove unnecessary syncbranchToDB with tests (#28624) (#28629)
|
||||
* Use known issue IID to generate new PR index number when migrating from GitLab (#28616) (#28618)
|
||||
* Fix flex container width (#28603) (#28605)
|
||||
* Fix the scroll behavior for emoji/mention list (#28597) (#28601)
|
||||
* Fix wrong due date rendering in issue list page (#28588) (#28591)
|
||||
* Fix `status_check_contexts` matching bug (#28582) (#28589)
|
||||
* Fix 500 error of searching commits (#28576) (#28579)
|
||||
* Use information from previous blame parts (#28572) (#28577)
|
||||
* Update mermaid for 1.21 (#28571)
|
||||
* Fix 405 method not allowed CORS / OIDC (#28583) (#28586) (#28587) (#28611)
|
||||
* Fix `GetCommitStatuses` (#28787) (#28804)
|
||||
* Forbid removing the last admin user (#28337) (#28793)
|
||||
* Fix schedule tasks bugs (#28691) (#28780)
|
||||
* Fix issue dependencies (#27736) (#28776)
|
||||
* Fix system webhooks API bug (#28531) (#28666)
|
||||
* Fix when private user following user, private user will not be counted in his own view (#28037) (#28792)
|
||||
* Render code block in activity tab (#28816) (#28818)
|
||||
* ENHANCEMENTS
|
||||
* Rework markup link rendering (#26745) (#28803)
|
||||
* Modernize merge button (#28140) (#28786)
|
||||
* Speed up loading the dashboard on mysql/mariadb (#28546) (#28784)
|
||||
* Assign pull request to project during creation (#28227) (#28775)
|
||||
* Show description as tooltip instead of title for labels (#28754) (#28766)
|
||||
* Make template `DateTime` show proper tooltip (#28677) (#28683)
|
||||
* Switch destination directory for apt signing keys (#28639) (#28642)
|
||||
* Include heap pprof in diagnosis report to help debugging memory leaks (#28596) (#28599)
|
||||
* DOCS
|
||||
* Suggest to use Type=simple for systemd service (#28717) (#28722)
|
||||
* Extend description for ARTIFACT_RETENTION_DAYS (#28626) (#28630)
|
||||
* MISC
|
||||
* Add -F to commit search to treat keywords as strings (#28744) (#28748)
|
||||
* Add download attribute to release attachments (#28739) (#28740)
|
||||
* Concatenate error in `checkIfPRContentChanged` (#28731) (#28737)
|
||||
* Improve 1.21 document for Database Preparation (#28643) (#28644)
|
||||
|
||||
## [1.21.3](https://github.com/go-gitea/gitea/releases/tag/1.21.3) - 2023-12-21
|
||||
|
||||
* SECURITY
|
||||
|
||||
@@ -52,7 +52,7 @@ After=network.target
|
||||
# Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that
|
||||
# LimitNOFILE=524288:524288
|
||||
RestartSec=2s
|
||||
Type=notify
|
||||
Type=simple
|
||||
User=git
|
||||
Group=git
|
||||
WorkingDirectory=/var/lib/gitea/
|
||||
@@ -62,7 +62,6 @@ WorkingDirectory=/var/lib/gitea/
|
||||
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
|
||||
Restart=always
|
||||
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
||||
WatchdogSec=30s
|
||||
# If you install Git to directory prefix other than default PATH (which happens
|
||||
# for example if you install other versions of Git side-to-side with
|
||||
# distribution version), uncomment below line and add that prefix to PATH
|
||||
|
||||
@@ -1156,15 +1156,9 @@ LEVEL = Info
|
||||
;; enable cors headers (disabled by default)
|
||||
;ENABLED = false
|
||||
;;
|
||||
;; scheme of allowed requests
|
||||
;SCHEME = http
|
||||
;;
|
||||
;; list of requesting domains that are allowed
|
||||
;; list of requesting origins that are allowed, eg: "https://*.example.com"
|
||||
;ALLOW_DOMAIN = *
|
||||
;;
|
||||
;; allow subdomains of headers listed above to request
|
||||
;ALLOW_SUBDOMAIN = false
|
||||
;;
|
||||
;; list of methods allowed to request
|
||||
;METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
|
||||
;;
|
||||
@@ -2577,7 +2571,7 @@ LEVEL = Info
|
||||
;;
|
||||
;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
|
||||
;DEFAULT_ACTIONS_URL = github
|
||||
;; Default artifact retention time in days, default is 90 days
|
||||
;; Default artifact retention time in days. Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
|
||||
;ARTIFACT_RETENTION_DAYS = 90
|
||||
;; Timeout to stop the task which have running status, but haven't been updated for a long time
|
||||
;ZOMBIE_TASK_TIMEOUT = 10m
|
||||
|
||||
@@ -196,9 +196,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||
## CORS (`cors`)
|
||||
|
||||
- `ENABLED`: **false**: enable cors headers (disabled by default)
|
||||
- `SCHEME`: **http**: scheme of allowed requests
|
||||
- `ALLOW_DOMAIN`: **\***: list of requesting domains that are allowed
|
||||
- `ALLOW_SUBDOMAIN`: **false**: allow subdomains of headers listed above to request
|
||||
- `ALLOW_DOMAIN`: **\***: list of requesting origins that are allowed, eg: "https://*.example.com"
|
||||
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: list of methods allowed to request
|
||||
- `MAX_AGE`: **10m**: max time to cache response
|
||||
- `ALLOW_CREDENTIALS`: **false**: allow request with credentials
|
||||
@@ -1393,7 +1391,7 @@ PROXY_HOSTS = *.github.com
|
||||
- `DEFAULT_ACTIONS_URL`: **github**: Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
|
||||
- `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||
- `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
|
||||
- `ARTIFACT_RETENTION_DAYS`: **90**: Number of days to keep artifacts. Set to 0 to disable artifact retention. Default is 90 days if not set.
|
||||
- `ARTIFACT_RETENTION_DAYS`: **90**: Default number of days to keep artifacts. Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
|
||||
- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
|
||||
- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time
|
||||
- `ABANDONED_JOB_TIMEOUT`: **24h**: Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
|
||||
|
||||
@@ -195,9 +195,7 @@ menu:
|
||||
## 跨域 (`cors`)
|
||||
|
||||
- `ENABLED`: **false**: 启用 CORS 头部(默认禁用)
|
||||
- `SCHEME`: **http**: 允许请求的协议
|
||||
- `ALLOW_DOMAIN`: **\***: 允许请求的域名列表
|
||||
- `ALLOW_SUBDOMAIN`: **false**: 允许上述列出的头部的子域名发出请求。
|
||||
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: 允许发起的请求方式列表
|
||||
- `MAX_AGE`: **10m**: 缓存响应的最大时间
|
||||
- `ALLOW_CREDENTIALS`: **false**: 允许带有凭据的请求
|
||||
|
||||
@@ -48,11 +48,12 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
|
||||
10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event.
|
||||
11. Custom event names are recommended to use `ce-` prefix.
|
||||
12. Gitea's tailwind-style CSS classes use `gt-` prefix (`gt-relative`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
|
||||
13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided.
|
||||
|
||||
### Accessibility / ARIA
|
||||
|
||||
In history, Gitea heavily uses Fomantic UI which is not an accessibility-friendly framework.
|
||||
Gitea uses some patches to make Fomantic UI more accessible (see the `aria.js` and `aria.md`),
|
||||
Gitea uses some patches to make Fomantic UI more accessible (see `aria.md` and related JS files),
|
||||
but there are still many problems which need a lot of work and time to fix.
|
||||
|
||||
### Framework Usage
|
||||
|
||||
@@ -17,7 +17,9 @@ menu:
|
||||
|
||||
# Database Preparation
|
||||
|
||||
You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), MariaDB, SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter.
|
||||
You need a database to use Gitea. Gitea supports PostgreSQL (>= 12), MySQL (>= 8.0), MariaDB (>= 10.4), SQLite (builtin), and MSSQL (>= 2012 SP4). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter.
|
||||
|
||||
If you use an unsupported database version, please [get in touch](/help/support) with us for information on our Extended Support Contracts. We can provide testing and support for older databases and integrate those fixes into the Gitea codebase.
|
||||
|
||||
Database instance can be on same machine as Gitea (local database setup), or on different machine (remote database).
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The following examples use `apt`.
|
||||
To register the Debian registry add the url to the list of known apt sources:
|
||||
|
||||
```shell
|
||||
echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
| Placeholder | Description |
|
||||
@@ -39,13 +39,13 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {
|
||||
If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication):
|
||||
|
||||
```shell
|
||||
echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
The Debian registry files are signed with a PGP key which must be known to apt:
|
||||
|
||||
```shell
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/keyrings/gitea-{owner}.asc
|
||||
```
|
||||
|
||||
Afterwards update the local package index:
|
||||
|
||||
@@ -27,7 +27,7 @@ menu:
|
||||
要注册 Debian 注册表,请将 URL 添加到已知 `apt` 源列表中:
|
||||
|
||||
```shell
|
||||
echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
| 占位符 | 描述 |
|
||||
@@ -39,13 +39,13 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {
|
||||
如果注册表是私有的,请在 URL 中提供凭据。您可以使用密码或[个人访问令牌](development/api-usage.md#通过-api-认证):
|
||||
|
||||
```shell
|
||||
echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list
|
||||
```
|
||||
|
||||
Debian 注册表文件使用 PGP 密钥进行签名,`apt` 必须知道该密钥:
|
||||
|
||||
```shell
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc
|
||||
sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/keyrings/gitea-{owner}.asc
|
||||
```
|
||||
|
||||
然后更新本地软件包索引:
|
||||
|
||||
@@ -121,7 +121,7 @@ require (
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.4
|
||||
xorm.io/xorm v1.3.6
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -165,7 +165,7 @@ require (
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/couchbase/go-couchbase v0.1.1 // indirect
|
||||
github.com/couchbase/gomemcached v0.2.1 // indirect
|
||||
github.com/couchbase/goutils v0.1.2 // indirect
|
||||
|
||||
@@ -227,8 +227,9 @@ github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86c
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -1649,5 +1650,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
|
||||
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/xorm v1.3.4 h1:vWFKzR3DhGUDl5b4srhUjhDwjxkZAc4C7BFszpu0swI=
|
||||
xorm.io/xorm v1.3.4/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
|
||||
xorm.io/xorm v1.3.6 h1:hfpWHkDIWWqUi8FRF2H2M9O8lO3Ov47rwFcS9gPzPkU=
|
||||
xorm.io/xorm v1.3.6/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
|
||||
|
||||
@@ -168,13 +168,14 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
|
||||
}
|
||||
|
||||
// CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow.
|
||||
func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error {
|
||||
func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
|
||||
// Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'.
|
||||
runs, total, err := FindRuns(ctx, FindRunOptions{
|
||||
RepoID: repoID,
|
||||
Ref: ref,
|
||||
WorkflowID: workflowID,
|
||||
Status: []Status{StatusRunning, StatusWaiting},
|
||||
RepoID: repoID,
|
||||
Ref: ref,
|
||||
WorkflowID: workflowID,
|
||||
TriggerEvent: event,
|
||||
Status: []Status{StatusRunning, StatusWaiting},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -71,6 +72,7 @@ type FindRunOptions struct {
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
}
|
||||
@@ -98,6 +100,9 @@ func (opts FindRunOptions) toConds() builder.Cond {
|
||||
if opts.Ref != "" {
|
||||
cond = cond.And(builder.Eq{"ref": opts.Ref})
|
||||
}
|
||||
if opts.TriggerEvent != "" {
|
||||
cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -118,3 +119,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
|
||||
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
||||
// There is no other place we can do this because the app.ini will be changed manually
|
||||
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
||||
return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||
}
|
||||
// cancel running cron jobs of this repository and delete old schedules
|
||||
if err := CancelRunningJobs(
|
||||
ctx,
|
||||
repo.ID,
|
||||
repo.DefaultBranch,
|
||||
"",
|
||||
webhook_module.HookEventSchedule,
|
||||
); err != nil {
|
||||
return fmt.Errorf("CancelRunningJobs: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -446,9 +446,12 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
sess := db.GetEngine(ctx).Where(cond).
|
||||
Select("`action`.*"). // this line will avoid select other joined table's columns
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
sess := db.GetEngine(ctx).Where(cond)
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
sess = sess.IndexHint("USE", "JOIN", "IDX_action_c_u_d")
|
||||
}
|
||||
sess = sess.Select("`action`.*"). // this line will avoid select other joined table's columns
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
|
||||
opts.SetDefaultValues()
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
@@ -275,9 +275,6 @@ func ActiveSources(tp Type) ([]*Source, error) {
|
||||
// IsSSPIEnabled returns true if there is at least one activated login
|
||||
// source of type LoginSSPI
|
||||
func IsSSPIEnabled() bool {
|
||||
if !db.HasEngine {
|
||||
return false
|
||||
}
|
||||
sources, err := ActiveSources(SSPI)
|
||||
if err != nil {
|
||||
log.Error("ActiveSources: %v", err)
|
||||
|
||||
@@ -27,9 +27,6 @@ var (
|
||||
x *xorm.Engine
|
||||
tables []any
|
||||
initFuncs []func() error
|
||||
|
||||
// HasEngine specifies if we have a xorm.Engine
|
||||
HasEngine bool
|
||||
)
|
||||
|
||||
// Engine represents a xorm engine or session.
|
||||
|
||||
@@ -57,6 +57,21 @@ func (err ErrUserOwnPackages) Error() string {
|
||||
return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// ErrDeleteLastAdminUser represents a "DeleteLastAdminUser" kind of error.
|
||||
type ErrDeleteLastAdminUser struct {
|
||||
UID int64
|
||||
}
|
||||
|
||||
// IsErrDeleteLastAdminUser checks if an error is a ErrDeleteLastAdminUser.
|
||||
func IsErrDeleteLastAdminUser(err error) bool {
|
||||
_, ok := err.(ErrDeleteLastAdminUser)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrDeleteLastAdminUser) Error() string {
|
||||
return fmt.Sprintf("can not delete the last admin user [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// ErrNoPendingRepoTransfer is an error type for repositories without a pending
|
||||
// transfer request
|
||||
type ErrNoPendingRepoTransfer struct {
|
||||
|
||||
@@ -283,7 +283,7 @@ func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *
|
||||
}
|
||||
|
||||
// RenameBranch rename a branch
|
||||
func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
|
||||
func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -358,7 +358,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||
}
|
||||
|
||||
// 5. do git action
|
||||
if err = gitAction(isDefault); err != nil {
|
||||
if err = gitAction(ctx, isDefault); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package git_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -133,7 +134,7 @@ func TestRenameBranch(t *testing.T) {
|
||||
}, git_model.WhitelistOptions{}))
|
||||
assert.NoError(t, committer.Commit())
|
||||
|
||||
assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(isDefault bool) error {
|
||||
assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(ctx context.Context, isDefault bool) error {
|
||||
_isDefault = isDefault
|
||||
return nil
|
||||
}))
|
||||
|
||||
+32
-35
@@ -25,7 +25,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// CommitStatus holds a single Status of a single Commit
|
||||
@@ -220,60 +219,58 @@ func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus {
|
||||
// CommitStatusOptions holds the options for query commit statuses
|
||||
type CommitStatusOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
SHA string
|
||||
State string
|
||||
SortType string
|
||||
}
|
||||
|
||||
// GetCommitStatuses returns all statuses for a given commit.
|
||||
func GetCommitStatuses(ctx context.Context, repo *repo_model.Repository, sha string, opts *CommitStatusOptions) ([]*CommitStatus, int64, error) {
|
||||
if opts.Page <= 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize <= 0 {
|
||||
opts.Page = setting.ItemsPerPage
|
||||
func (opts *CommitStatusOptions) ToConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"repo_id": opts.RepoID,
|
||||
"sha": opts.SHA,
|
||||
}
|
||||
|
||||
countSession := listCommitStatusesStatement(ctx, repo, sha, opts)
|
||||
countSession = db.SetSessionPagination(countSession, opts)
|
||||
maxResults, err := countSession.Count(new(CommitStatus))
|
||||
if err != nil {
|
||||
log.Error("Count PRs: %v", err)
|
||||
return nil, maxResults, err
|
||||
}
|
||||
|
||||
statuses := make([]*CommitStatus, 0, opts.PageSize)
|
||||
findSession := listCommitStatusesStatement(ctx, repo, sha, opts)
|
||||
findSession = db.SetSessionPagination(findSession, opts)
|
||||
sortCommitStatusesSession(findSession, opts.SortType)
|
||||
return statuses, maxResults, findSession.Find(&statuses)
|
||||
}
|
||||
|
||||
func listCommitStatusesStatement(ctx context.Context, repo *repo_model.Repository, sha string, opts *CommitStatusOptions) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).Where("repo_id = ?", repo.ID).And("sha = ?", sha)
|
||||
switch opts.State {
|
||||
case "pending", "success", "error", "failure", "warning":
|
||||
sess.And("state = ?", opts.State)
|
||||
cond = cond.And(builder.Eq{
|
||||
"state": opts.State,
|
||||
})
|
||||
}
|
||||
return sess
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
func sortCommitStatusesSession(sess *xorm.Session, sortType string) {
|
||||
switch sortType {
|
||||
func (opts *CommitStatusOptions) ToOrders() string {
|
||||
switch opts.SortType {
|
||||
case "oldest":
|
||||
sess.Asc("created_unix")
|
||||
return "created_unix ASC"
|
||||
case "recentupdate":
|
||||
sess.Desc("updated_unix")
|
||||
return "updated_unix DESC"
|
||||
case "leastupdate":
|
||||
sess.Asc("updated_unix")
|
||||
return "updated_unix ASC"
|
||||
case "leastindex":
|
||||
sess.Desc("index")
|
||||
return "`index` DESC"
|
||||
case "highestindex":
|
||||
sess.Asc("index")
|
||||
return "`index` ASC"
|
||||
default:
|
||||
sess.Desc("created_unix")
|
||||
return "created_unix DESC"
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommitStatuses returns all statuses for a given commit.
|
||||
func GetCommitStatuses(ctx context.Context, opts *CommitStatusOptions) ([]*CommitStatus, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Where(opts.ToConds()).
|
||||
OrderBy(opts.ToOrders())
|
||||
|
||||
db.SetSessionPagination(sess, opts)
|
||||
|
||||
statuses := make([]*CommitStatus, 0, opts.PageSize)
|
||||
count, err := sess.FindAndCount(&statuses)
|
||||
return statuses, count, err
|
||||
}
|
||||
|
||||
// CommitStatusIndex represents a table for commit status index
|
||||
type CommitStatusIndex struct {
|
||||
ID int64
|
||||
|
||||
@@ -22,7 +22,11 @@ func TestGetCommitStatuses(t *testing.T) {
|
||||
|
||||
sha1 := "1234123412341234123412341234123412341234"
|
||||
|
||||
statuses, maxResults, err := git_model.GetCommitStatuses(db.DefaultContext, repo1, sha1, &git_model.CommitStatusOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 50}})
|
||||
statuses, maxResults, err := git_model.GetCommitStatuses(db.DefaultContext, &git_model.CommitStatusOptions{
|
||||
ListOptions: db.ListOptions{Page: 1, PageSize: 50},
|
||||
RepoID: repo1.ID,
|
||||
SHA: sha1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(maxResults), 5)
|
||||
assert.Len(t, statuses, 5)
|
||||
@@ -46,4 +50,13 @@ func TestGetCommitStatuses(t *testing.T) {
|
||||
assert.Equal(t, "deploy/awesomeness", statuses[4].Context)
|
||||
assert.Equal(t, structs.CommitStatusError, statuses[4].State)
|
||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL(db.DefaultContext))
|
||||
|
||||
statuses, maxResults, err = git_model.GetCommitStatuses(db.DefaultContext, &git_model.CommitStatusOptions{
|
||||
ListOptions: db.ListOptions{Page: 2, PageSize: 50},
|
||||
RepoID: repo1.ID,
|
||||
SHA: sha1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(maxResults), 5)
|
||||
assert.Empty(t, statuses)
|
||||
}
|
||||
|
||||
@@ -1153,14 +1153,9 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
|
||||
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
|
||||
func UpdateCommentsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
|
||||
_, err := db.GetEngine(ctx).Table("comment").
|
||||
Where(builder.In("issue_id",
|
||||
builder.Select("issue.id").
|
||||
From("issue").
|
||||
InnerJoin("repository", "issue.repo_id = repository.id").
|
||||
Where(builder.Eq{
|
||||
"repository.original_service_type": tp,
|
||||
}),
|
||||
)).
|
||||
Join("INNER", "issue", "issue.id = comment.issue_id").
|
||||
Join("INNER", "repository", "issue.repo_id = repository.id").
|
||||
Where("repository.original_service_type = ?", tp).
|
||||
And("comment.original_author_id = ?", originalAuthorID).
|
||||
Update(map[string]any{
|
||||
"poster_id": posterID,
|
||||
|
||||
@@ -109,9 +109,11 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
||||
|
||||
var err error
|
||||
if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: issue.Repo.Link(),
|
||||
Metas: issue.Repo.ComposeMetas(),
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: issue.Repo.Link(),
|
||||
},
|
||||
Metas: issue.Repo.ComposeMetas(),
|
||||
}, comment.Content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+1
-2
@@ -578,8 +578,7 @@ func (repo *Repository) CanEnableEditor() bool {
|
||||
// DescriptionHTML does special handles to description and return HTML string.
|
||||
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
||||
desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: repo.HTMLURL(),
|
||||
Ctx: ctx,
|
||||
// Don't use Metas to speedup requests
|
||||
}, repo.Description)
|
||||
if err != nil {
|
||||
|
||||
@@ -283,29 +283,3 @@ func UpdateRepoUnit(unit *RepoUnit) error {
|
||||
_, err := db.GetEngine(db.DefaultContext).ID(unit.ID).Update(unit)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepositoryUnits updates a repository's units
|
||||
func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
// Delete existing settings of units before adding again
|
||||
for _, u := range units {
|
||||
deleteUnitTypes = append(deleteUnitTypes, u.Type)
|
||||
}
|
||||
|
||||
if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(units) > 0 {
|
||||
if err = db.Insert(ctx, units); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
+27
-4
@@ -705,9 +705,18 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// IsLastAdminUser check whether user is the last admin
|
||||
func IsLastAdminUser(ctx context.Context, user *User) bool {
|
||||
if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: util.OptionalBoolTrue}) <= 1 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CountUserFilter represent optional filters for CountUsers
|
||||
type CountUserFilter struct {
|
||||
LastLoginSince *int64
|
||||
IsAdmin util.OptionalBool
|
||||
}
|
||||
|
||||
// CountUsers returns number of users.
|
||||
@@ -716,13 +725,25 @@ func CountUsers(ctx context.Context, opts *CountUserFilter) int64 {
|
||||
}
|
||||
|
||||
func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
|
||||
sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"})
|
||||
sess := db.GetEngine(ctx)
|
||||
cond := builder.NewCond()
|
||||
cond = cond.And(builder.Eq{"type": UserTypeIndividual})
|
||||
|
||||
if opts != nil && opts.LastLoginSince != nil {
|
||||
sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince})
|
||||
if opts != nil {
|
||||
if opts.LastLoginSince != nil {
|
||||
cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince})
|
||||
}
|
||||
|
||||
if !opts.IsAdmin.IsNone() {
|
||||
cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
|
||||
}
|
||||
}
|
||||
|
||||
count, err := sess.Where(cond).Count(new(User))
|
||||
if err != nil {
|
||||
log.Error("user.countUsers: %v", err)
|
||||
}
|
||||
|
||||
count, _ := sess.Count(new(User))
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -1205,6 +1226,8 @@ func isUserVisibleToViewerCond(viewer *User) builder.Cond {
|
||||
return builder.Neq{
|
||||
"`user`.visibility": structs.VisibleTypePrivate,
|
||||
}.Or(
|
||||
// viewer self
|
||||
builder.Eq{"`user`.id": viewer.ID},
|
||||
// viewer's following
|
||||
builder.In("`user`.id",
|
||||
builder.
|
||||
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
GithubEventRelease = "release"
|
||||
GithubEventPullRequestComment = "pull_request_comment"
|
||||
GithubEventGollum = "gollum"
|
||||
GithubEventSchedule = "schedule"
|
||||
)
|
||||
|
||||
// canGithubEventMatch check if the input Github event can match any Gitea event.
|
||||
@@ -69,6 +70,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
|
||||
return false
|
||||
}
|
||||
|
||||
case GithubEventSchedule:
|
||||
return triggedEvent == webhook_module.HookEventSchedule
|
||||
|
||||
default:
|
||||
return eventName == string(triggedEvent)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
type DetectedWorkflow struct {
|
||||
EntryName string
|
||||
TriggerEvent string
|
||||
TriggerEvent *jobparser.Event
|
||||
Content []byte
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ func DetectWorkflows(
|
||||
commit *git.Commit,
|
||||
triggedEvent webhook_module.HookEventType,
|
||||
payload api.Payloader,
|
||||
detectSchedule bool,
|
||||
) ([]*DetectedWorkflow, []*DetectedWorkflow, error) {
|
||||
entries, err := ListWorkflows(commit)
|
||||
if err != nil {
|
||||
@@ -114,6 +115,7 @@ func DetectWorkflows(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// one workflow may have multiple events
|
||||
events, err := GetEventsFromContent(content)
|
||||
if err != nil {
|
||||
log.Warn("ignore invalid workflow %q: %v", entry.Name(), err)
|
||||
@@ -122,17 +124,18 @@ func DetectWorkflows(
|
||||
for _, evt := range events {
|
||||
log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent)
|
||||
if evt.IsSchedule() {
|
||||
dwf := &DetectedWorkflow{
|
||||
EntryName: entry.Name(),
|
||||
TriggerEvent: evt.Name,
|
||||
Content: content,
|
||||
if detectSchedule {
|
||||
dwf := &DetectedWorkflow{
|
||||
EntryName: entry.Name(),
|
||||
TriggerEvent: evt,
|
||||
Content: content,
|
||||
}
|
||||
schedules = append(schedules, dwf)
|
||||
}
|
||||
schedules = append(schedules, dwf)
|
||||
}
|
||||
if detectMatched(gitRepo, commit, triggedEvent, payload, evt) {
|
||||
} else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) {
|
||||
dwf := &DetectedWorkflow{
|
||||
EntryName: entry.Name(),
|
||||
TriggerEvent: evt.Name,
|
||||
TriggerEvent: evt,
|
||||
Content: content,
|
||||
}
|
||||
workflows = append(workflows, dwf)
|
||||
@@ -153,7 +156,8 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
|
||||
webhook_module.HookEventCreate,
|
||||
webhook_module.HookEventDelete,
|
||||
webhook_module.HookEventFork,
|
||||
webhook_module.HookEventWiki:
|
||||
webhook_module.HookEventWiki,
|
||||
webhook_module.HookEventSchedule:
|
||||
if len(evt.Acts()) != 0 {
|
||||
log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts())
|
||||
}
|
||||
|
||||
@@ -118,6 +118,13 @@ func TestDetectMatched(t *testing.T) {
|
||||
yamlOn: "on: gollum",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)",
|
||||
triggedEvent: webhook_module.HookEventSchedule,
|
||||
payload: nil,
|
||||
yamlOn: "on: schedule",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -49,9 +49,9 @@ func TestFormat_Flag(t *testing.T) {
|
||||
{
|
||||
name: "multiple fields",
|
||||
|
||||
givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"),
|
||||
givenFormat: foreachref.NewFormat("refname:lstrip=2", "objecttype", "objectname"),
|
||||
|
||||
wantFlag: "refname:short %(refname:short)%00objecttype %(objecttype)%00objectname %(objectname)%00%00",
|
||||
wantFlag: "refname:lstrip=2 %(refname:lstrip=2)%00objecttype %(objecttype)%00objectname %(objectname)%00%00",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -148,6 +148,9 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
|
||||
cmd.AddArguments("--all")
|
||||
}
|
||||
|
||||
// interpret search string keywords as string instead of regex
|
||||
cmd.AddArguments("-F")
|
||||
|
||||
// add remaining keywords from search string
|
||||
// note this is done only for command created above
|
||||
if len(opts.Keywords) > 0 {
|
||||
|
||||
@@ -112,7 +112,9 @@ func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) {
|
||||
|
||||
// GetTagInfos returns all tag infos of the repository.
|
||||
func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
|
||||
forEachRefFmt := foreachref.NewFormat("objecttype", "refname:short", "object", "objectname", "creator", "contents", "contents:signature")
|
||||
// Generally, refname:short should be equal to refname:lstrip=2 except core.warnAmbiguousRefs is used to select the strict abbreviation mode.
|
||||
// https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname
|
||||
forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature")
|
||||
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
defer stdoutReader.Close()
|
||||
@@ -162,7 +164,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
|
||||
func parseTagRef(ref map[string]string) (tag *Tag, err error) {
|
||||
tag = &Tag{
|
||||
Type: ref["objecttype"],
|
||||
Name: ref["refname:short"],
|
||||
Name: ref["refname:lstrip=2"],
|
||||
}
|
||||
|
||||
tag.ID, err = NewIDFromString(ref["objectname"])
|
||||
|
||||
@@ -209,8 +209,8 @@ func TestRepository_parseTagRef(t *testing.T) {
|
||||
name: "lightweight tag",
|
||||
|
||||
givenRef: map[string]string{
|
||||
"objecttype": "commit",
|
||||
"refname:short": "v1.9.1",
|
||||
"objecttype": "commit",
|
||||
"refname:lstrip=2": "v1.9.1",
|
||||
// object will be empty for lightweight tags
|
||||
"object": "",
|
||||
"objectname": "ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889",
|
||||
@@ -238,8 +238,8 @@ func TestRepository_parseTagRef(t *testing.T) {
|
||||
name: "annotated tag",
|
||||
|
||||
givenRef: map[string]string{
|
||||
"objecttype": "tag",
|
||||
"refname:short": "v0.0.1",
|
||||
"objecttype": "tag",
|
||||
"refname:lstrip=2": "v0.0.1",
|
||||
// object will refer to commit hash for annotated tag
|
||||
"object": "3325fd8a973321fd59455492976c042dde3fd1ca",
|
||||
"objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9",
|
||||
@@ -267,11 +267,11 @@ func TestRepository_parseTagRef(t *testing.T) {
|
||||
name: "annotated tag with signature",
|
||||
|
||||
givenRef: map[string]string{
|
||||
"objecttype": "tag",
|
||||
"refname:short": "v0.0.1",
|
||||
"object": "3325fd8a973321fd59455492976c042dde3fd1ca",
|
||||
"objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9",
|
||||
"creator": "Foo Bar <foo@bar.com> 1565789218 +0300",
|
||||
"objecttype": "tag",
|
||||
"refname:lstrip=2": "v0.0.1",
|
||||
"object": "3325fd8a973321fd59455492976c042dde3fd1ca",
|
||||
"objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9",
|
||||
"creator": "Foo Bar <foo@bar.com> 1565789218 +0300",
|
||||
"contents": `Add changelog of v1.9.1 (#7859)
|
||||
|
||||
* add changelog of v1.9.1
|
||||
|
||||
Vendored
+7
-6
@@ -79,9 +79,10 @@ func envMark(envName string) string {
|
||||
// Render renders the data of the document to HTML via the external tool.
|
||||
func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
var (
|
||||
urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1)
|
||||
command = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), ctx.URLPrefix,
|
||||
envMark("GITEA_PREFIX_RAW"), urlRawPrefix).Replace(p.Command)
|
||||
command = strings.NewReplacer(
|
||||
envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(),
|
||||
envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(),
|
||||
).Replace(p.Command)
|
||||
commands = strings.Fields(command)
|
||||
args = commands[1:]
|
||||
)
|
||||
@@ -121,14 +122,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
|
||||
ctx.Ctx = graceful.GetManager().ShutdownContext()
|
||||
}
|
||||
|
||||
processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix))
|
||||
processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink()))
|
||||
defer finished()
|
||||
|
||||
cmd := exec.CommandContext(processCtx, commands[0], args...)
|
||||
cmd.Env = append(
|
||||
os.Environ(),
|
||||
"GITEA_PREFIX_SRC="+ctx.URLPrefix,
|
||||
"GITEA_PREFIX_RAW="+urlRawPrefix,
|
||||
"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(),
|
||||
"GITEA_PREFIX_RAW="+ctx.Links.RawLink(),
|
||||
)
|
||||
if !p.IsInputFile {
|
||||
cmd.Stdin = input
|
||||
|
||||
+16
-41
@@ -80,15 +80,10 @@ const keywordClass = "issue-keyword"
|
||||
|
||||
// IsLink reports whether link fits valid format.
|
||||
func IsLink(link []byte) bool {
|
||||
return isLink(link)
|
||||
}
|
||||
|
||||
// isLink reports whether link fits valid format.
|
||||
func isLink(link []byte) bool {
|
||||
return validLinksPattern.Match(link)
|
||||
}
|
||||
|
||||
func isLinkStr(link string) bool {
|
||||
func IsLinkStr(link string) bool {
|
||||
return validLinksPattern.MatchString(link)
|
||||
}
|
||||
|
||||
@@ -344,7 +339,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
|
||||
node = node.FirstChild
|
||||
}
|
||||
|
||||
visitNode(ctx, procs, procs, node)
|
||||
visitNode(ctx, procs, node)
|
||||
|
||||
newNodes := make([]*html.Node, 0, 5)
|
||||
|
||||
@@ -375,7 +370,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
|
||||
return nil
|
||||
}
|
||||
|
||||
func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node) {
|
||||
func visitNode(ctx *RenderContext, procs []processor, node *html.Node) {
|
||||
// Add user-content- to IDs and "#" links if they don't already have them
|
||||
for idx, attr := range node.Attr {
|
||||
val := strings.TrimPrefix(attr.Val, "#")
|
||||
@@ -390,35 +385,29 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node
|
||||
}
|
||||
|
||||
if attr.Key == "class" && attr.Val == "emoji" {
|
||||
textProcs = nil
|
||||
procs = nil
|
||||
}
|
||||
}
|
||||
|
||||
// We ignore code and pre.
|
||||
switch node.Type {
|
||||
case html.TextNode:
|
||||
textNode(ctx, textProcs, node)
|
||||
textNode(ctx, procs, node)
|
||||
case html.ElementNode:
|
||||
if node.Data == "img" {
|
||||
for i, attr := range node.Attr {
|
||||
if attr.Key != "src" {
|
||||
continue
|
||||
}
|
||||
if len(attr.Val) > 0 && !isLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") {
|
||||
prefix := ctx.URLPrefix
|
||||
if ctx.IsWiki {
|
||||
prefix = util.URLJoin(prefix, "wiki", "raw")
|
||||
}
|
||||
prefix = strings.Replace(prefix, "/src/", "/media/", 1)
|
||||
|
||||
attr.Val = util.URLJoin(prefix, attr.Val)
|
||||
if len(attr.Val) > 0 && !IsLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") {
|
||||
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val)
|
||||
}
|
||||
attr.Val = camoHandleLink(attr.Val)
|
||||
node.Attr[i] = attr
|
||||
}
|
||||
} else if node.Data == "a" {
|
||||
// Restrict text in links to emojis
|
||||
textProcs = emojiProcessors
|
||||
procs = emojiProcessors
|
||||
} else if node.Data == "code" || node.Data == "pre" {
|
||||
return
|
||||
} else if node.Data == "i" {
|
||||
@@ -444,7 +433,7 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node
|
||||
}
|
||||
}
|
||||
for n := node.FirstChild; n != nil; n = n.NextSibling {
|
||||
visitNode(ctx, procs, textProcs, n)
|
||||
visitNode(ctx, procs, n)
|
||||
}
|
||||
}
|
||||
// ignore everything else
|
||||
@@ -641,10 +630,6 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
||||
}
|
||||
|
||||
func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
||||
shortLinkProcessorFull(ctx, node, false)
|
||||
}
|
||||
|
||||
func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
|
||||
next := node.NextSibling
|
||||
for node != nil && node != next {
|
||||
m := shortLinkPattern.FindStringSubmatchIndex(node.Data)
|
||||
@@ -665,7 +650,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
|
||||
if equalPos := strings.IndexByte(v, '='); equalPos == -1 {
|
||||
// There is no equal in this argument; this is a mandatory arg
|
||||
if props["name"] == "" {
|
||||
if isLinkStr(v) {
|
||||
if IsLinkStr(v) {
|
||||
// If we clearly see it is a link, we save it so
|
||||
|
||||
// But first we need to ensure, that if both mandatory args provided
|
||||
@@ -740,7 +725,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
|
||||
DataAtom: atom.A,
|
||||
}
|
||||
childNode.Parent = linkNode
|
||||
absoluteLink := isLinkStr(link)
|
||||
absoluteLink := IsLinkStr(link)
|
||||
if !absoluteLink {
|
||||
if image {
|
||||
link = strings.ReplaceAll(link, " ", "+")
|
||||
@@ -751,16 +736,9 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
|
||||
link = url.PathEscape(link)
|
||||
}
|
||||
}
|
||||
urlPrefix := ctx.URLPrefix
|
||||
if image {
|
||||
if !absoluteLink {
|
||||
if IsSameDomain(urlPrefix) {
|
||||
urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1)
|
||||
}
|
||||
if ctx.IsWiki {
|
||||
link = util.URLJoin("wiki", "raw", link)
|
||||
}
|
||||
link = util.URLJoin(urlPrefix, link)
|
||||
link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), link)
|
||||
}
|
||||
title := props["title"]
|
||||
if title == "" {
|
||||
@@ -789,18 +767,15 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
|
||||
} else {
|
||||
if !absoluteLink {
|
||||
if ctx.IsWiki {
|
||||
link = util.URLJoin("wiki", link)
|
||||
link = util.URLJoin(ctx.Links.WikiLink(), link)
|
||||
} else {
|
||||
link = util.URLJoin(ctx.Links.SrcLink(), link)
|
||||
}
|
||||
link = util.URLJoin(urlPrefix, link)
|
||||
}
|
||||
childNode.Type = html.TextNode
|
||||
childNode.Data = name
|
||||
}
|
||||
if noLink {
|
||||
linkNode = childNode
|
||||
} else {
|
||||
linkNode.Attr = []html.Attribute{{Key: "href", Val: link}}
|
||||
}
|
||||
linkNode.Attr = []html.Attribute{{Key: "href", Val: link}}
|
||||
replaceContent(node, m[0], m[1], linkNode)
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
|
||||
@@ -287,8 +287,8 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) {
|
||||
}
|
||||
|
||||
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
||||
if ctx.URLPrefix == "" {
|
||||
ctx.URLPrefix = TestAppURL
|
||||
if ctx.Links.Base == "" {
|
||||
ctx.Links.Base = TestRepoURL
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
@@ -303,19 +303,23 @@ func TestRender_AutoLink(t *testing.T) {
|
||||
test := func(input, expected string) {
|
||||
var buffer strings.Builder
|
||||
err := PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: TestRepoURL,
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(input), &buffer)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
||||
|
||||
buffer.Reset()
|
||||
err = PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: TestRepoURL,
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
}, strings.NewReader(input), &buffer)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
||||
@@ -342,9 +346,11 @@ func TestRender_FullIssueURLs(t *testing.T) {
|
||||
test := func(input, expected string) {
|
||||
var result strings.Builder
|
||||
err := postProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: TestRepoURL,
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, result.String())
|
||||
|
||||
+71
-46
@@ -42,8 +42,10 @@ func TestRender_Commits(t *testing.T) {
|
||||
buffer, err := RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
RelativePath: ".md",
|
||||
URLPrefix: TestRepoURL,
|
||||
Metas: localMetas,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
@@ -93,8 +95,10 @@ func TestRender_CrossReferences(t *testing.T) {
|
||||
buffer, err := RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
RelativePath: "a.md",
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Metas: localMetas,
|
||||
Links: Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
@@ -138,7 +142,9 @@ func TestRender_links(t *testing.T) {
|
||||
buffer, err := RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
RelativePath: "a.md",
|
||||
URLPrefix: TestRepoURL,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
@@ -238,7 +244,9 @@ func TestRender_email(t *testing.T) {
|
||||
res, err := RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
RelativePath: "a.md",
|
||||
URLPrefix: TestRepoURL,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
|
||||
@@ -309,7 +317,9 @@ func TestRender_emoji(t *testing.T) {
|
||||
buffer, err := RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
RelativePath: "a.md",
|
||||
URLPrefix: TestRepoURL,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
@@ -371,29 +381,34 @@ func TestRender_ShortLinks(t *testing.T) {
|
||||
|
||||
test := func(input, expected, expectedWiki string) {
|
||||
buffer, err := markdown.RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: tree,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
BranchPath: "master",
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
buffer, err = markdown.RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: TestRepoURL,
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
|
||||
}
|
||||
|
||||
rawtree := util.URLJoin(TestRepoURL, "raw", "master")
|
||||
mediatree := util.URLJoin(TestRepoURL, "media", "master")
|
||||
url := util.URLJoin(tree, "Link")
|
||||
otherURL := util.URLJoin(tree, "Other-Link")
|
||||
encodedURL := util.URLJoin(tree, "Link%3F")
|
||||
imgurl := util.URLJoin(rawtree, "Link.jpg")
|
||||
otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg")
|
||||
encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg")
|
||||
notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg")
|
||||
imgurl := util.URLJoin(mediatree, "Link.jpg")
|
||||
otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg")
|
||||
encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg")
|
||||
notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg")
|
||||
urlWiki := util.URLJoin(TestRepoURL, "wiki", "Link")
|
||||
otherURLWiki := util.URLJoin(TestRepoURL, "wiki", "Other-Link")
|
||||
encodedURLWiki := util.URLJoin(TestRepoURL, "wiki", "Link%3F")
|
||||
@@ -475,21 +490,25 @@ func TestRender_ShortLinks(t *testing.T) {
|
||||
|
||||
func TestRender_RelativeImages(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
tree := util.URLJoin(TestRepoURL, "src", "master")
|
||||
|
||||
test := func(input, expected, expectedWiki string) {
|
||||
buffer, err := markdown.RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: tree,
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
BranchPath: "master",
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
buffer, err = markdown.RenderString(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: TestRepoURL,
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: TestRepoURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
|
||||
@@ -521,9 +540,11 @@ func Test_ParseClusterFuzz(t *testing.T) {
|
||||
|
||||
var res strings.Builder
|
||||
err := PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: "https://example.com",
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: "https://example.com",
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(data), &res)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, res.String(), "<html")
|
||||
@@ -532,9 +553,11 @@ func Test_ParseClusterFuzz(t *testing.T) {
|
||||
|
||||
res.Reset()
|
||||
err = PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: "https://example.com",
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: "https://example.com",
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(data), &res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -543,6 +566,7 @@ func Test_ParseClusterFuzz(t *testing.T) {
|
||||
|
||||
func TestPostProcess_RenderDocument(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
setting.StaticURLPrefix = TestAppURL // can't run standalone
|
||||
|
||||
localMetas := map[string]string{
|
||||
"user": "go-gitea",
|
||||
@@ -553,9 +577,11 @@ func TestPostProcess_RenderDocument(t *testing.T) {
|
||||
test := func(input, expected string) {
|
||||
var res strings.Builder
|
||||
err := PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: "https://example.com",
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: "https://example.com",
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(input), &res)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
||||
@@ -589,9 +615,8 @@ func TestIssue16020(t *testing.T) {
|
||||
|
||||
var res strings.Builder
|
||||
err := PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: "https://example.com",
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(data), &res)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, data, res.String())
|
||||
@@ -606,9 +631,8 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var res strings.Builder
|
||||
err := PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: "https://example.com",
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(data), &res)
|
||||
assert.NoError(b, err)
|
||||
}
|
||||
@@ -617,8 +641,10 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
|
||||
func TestFuzz(t *testing.T) {
|
||||
s := "t/l/issues/8#/../../a"
|
||||
renderContext := RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: "https://example.com/go-gitea/gitea",
|
||||
Ctx: git.DefaultContext,
|
||||
Links: Links{
|
||||
Base: "https://example.com/go-gitea/gitea",
|
||||
},
|
||||
Metas: map[string]string{
|
||||
"user": "go-gitea",
|
||||
"repo": "gitea",
|
||||
@@ -635,9 +661,8 @@ func TestIssue18471(t *testing.T) {
|
||||
|
||||
var res strings.Builder
|
||||
err := PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: "https://example.com",
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(data), &res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -87,18 +87,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||
// Check if the destination is a real link
|
||||
link := v.Destination
|
||||
if len(link) > 0 && !markup.IsLink(link) {
|
||||
prefix := pc.Get(urlPrefixKey).(string)
|
||||
if pc.Get(isWikiKey).(bool) {
|
||||
prefix = giteautil.URLJoin(prefix, "wiki", "raw")
|
||||
}
|
||||
prefix = strings.Replace(prefix, "/src/", "/media/", 1)
|
||||
|
||||
lnk := strings.TrimLeft(string(link), "/")
|
||||
|
||||
lnk = giteautil.URLJoin(prefix, lnk)
|
||||
link = []byte(lnk)
|
||||
v.Destination = []byte(giteautil.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), string(link)))
|
||||
}
|
||||
v.Destination = link
|
||||
|
||||
parent := n.Parent()
|
||||
// Create a link around image only if parent is not already a link
|
||||
@@ -107,7 +97,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||
|
||||
// Create a link wrapper
|
||||
wrap := ast.NewLink()
|
||||
wrap.Destination = link
|
||||
wrap.Destination = v.Destination
|
||||
wrap.Title = v.Title
|
||||
wrap.SetAttributeString("target", []byte("_blank"))
|
||||
|
||||
@@ -143,11 +133,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||
link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
|
||||
// special case: this is not a link, a hash link or a mailto:, so it's a
|
||||
// relative URL
|
||||
lnk := string(link)
|
||||
if pc.Get(isWikiKey).(bool) {
|
||||
lnk = giteautil.URLJoin("wiki", lnk)
|
||||
|
||||
var base string
|
||||
if ctx.IsWiki {
|
||||
base = ctx.Links.WikiLink()
|
||||
} else {
|
||||
base = ctx.Links.Base
|
||||
}
|
||||
link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk))
|
||||
|
||||
link = []byte(giteautil.URLJoin(base, string(link)))
|
||||
}
|
||||
if len(link) > 0 && link[0] == '#' {
|
||||
link = []byte("#user-content-" + string(link)[1:])
|
||||
@@ -188,9 +182,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||
applyElementDir(v)
|
||||
case *ast.Text:
|
||||
if v.SoftLineBreak() && !v.HardLineBreak() {
|
||||
renderMetas := pc.Get(renderMetasKey).(map[string]string)
|
||||
mode := renderMetas["mode"]
|
||||
if mode != "document" {
|
||||
if ctx.Metas["mode"] != "document" {
|
||||
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
|
||||
} else {
|
||||
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
|
||||
|
||||
@@ -34,9 +34,6 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
urlPrefixKey = parser.NewContextKey()
|
||||
isWikiKey = parser.NewContextKey()
|
||||
renderMetasKey = parser.NewContextKey()
|
||||
renderContextKey = parser.NewContextKey()
|
||||
renderConfigKey = parser.NewContextKey()
|
||||
)
|
||||
@@ -66,9 +63,6 @@ func (l *limitWriter) Write(data []byte) (int, error) {
|
||||
// newParserContext creates a parser.Context with the render context set
|
||||
func newParserContext(ctx *markup.RenderContext) parser.Context {
|
||||
pc := parser.NewContext(parser.WithIDs(newPrefixedIDs()))
|
||||
pc.Set(urlPrefixKey, ctx.URLPrefix)
|
||||
pc.Set(isWikiKey, ctx.IsWiki)
|
||||
pc.Set(renderMetasKey, ctx.Metas)
|
||||
pc.Set(renderContextKey, ctx)
|
||||
return pc
|
||||
}
|
||||
|
||||
@@ -52,16 +52,20 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||
|
||||
test := func(input, expected, expectedWiki string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
|
||||
buffer, err = RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
IsWiki: true,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
IsWiki: true,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
|
||||
@@ -83,8 +87,10 @@ func TestRender_Images(t *testing.T) {
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
@@ -107,7 +113,6 @@ func TestRender_Images(t *testing.T) {
|
||||
"[]("+href+")",
|
||||
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
|
||||
|
||||
url = "/../../.images/src/02/train.jpg"
|
||||
test(
|
||||
"",
|
||||
`<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
|
||||
@@ -286,14 +291,16 @@ 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(AppSubURL, "wiki"), util.URLJoin(AppSubURL, "wiki", "raw"))
|
||||
|
||||
for i := 0; i < len(sameCases); i++ {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: AppSubURL,
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
IsWiki: true,
|
||||
}, sameCases[i])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, answers[i], line)
|
||||
@@ -314,9 +321,11 @@ func TestTotal_RenderWiki(t *testing.T) {
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: AppSubURL,
|
||||
IsWiki: true,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
IsWiki: true,
|
||||
}, testCases[i])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testCases[i+1], line)
|
||||
@@ -327,13 +336,16 @@ func TestTotal_RenderString(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
answers := testAnswers(util.URLJoin(AppSubURL, "src", "master/"), util.URLJoin(AppSubURL, "raw", "master/"))
|
||||
answers := testAnswers(util.URLJoin(AppSubURL, "src", "master"), util.URLJoin(AppSubURL, "media", "master"))
|
||||
|
||||
for i := 0; i < len(sameCases); i++ {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: util.URLJoin(AppSubURL, "src", "master/"),
|
||||
Metas: localMetas,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: AppSubURL,
|
||||
BranchPath: "master",
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, sameCases[i])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, answers[i], line)
|
||||
@@ -343,8 +355,10 @@ func TestTotal_RenderString(t *testing.T) {
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
line, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: AppSubURL,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: AppSubURL,
|
||||
},
|
||||
}, testCases[i])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testCases[i+1], line)
|
||||
@@ -556,3 +570,367 @@ foo: bar
|
||||
assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderLinks(t *testing.T) {
|
||||
input := ` space @mention-user
|
||||
/just/a/path.bin
|
||||
https://example.com/file.bin
|
||||
[local link](file.bin)
|
||||
[remote link](https://example.com)
|
||||
[[local link|file.bin]]
|
||||
[[remote link|https://example.com]]
|
||||

|
||||

|
||||
[[local image|image.jpg]]
|
||||
[[remote link|https://example.com/image.jpg]]
|
||||
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
:+1:
|
||||
mail@domain.com
|
||||
@mention-user test
|
||||
#123
|
||||
space
|
||||
`
|
||||
cases := []struct {
|
||||
Links markup.Links
|
||||
IsWiki bool
|
||||
Expected string
|
||||
}{
|
||||
{ // 0
|
||||
Links: markup.Links{},
|
||||
IsWiki: false,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/src/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 1
|
||||
Links: markup.Links{},
|
||||
IsWiki: true,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/wiki/raw/image.jpg" rel="nofollow"><img src="/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 2
|
||||
Links: markup.Links{
|
||||
Base: "https://gitea.io/",
|
||||
},
|
||||
IsWiki: false,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="https://gitea.io/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="https://gitea.io/src/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="https://gitea.io/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="https://gitea.io/image.jpg" rel="nofollow"><img src="https://gitea.io/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 3
|
||||
Links: markup.Links{
|
||||
Base: "https://gitea.io/",
|
||||
},
|
||||
IsWiki: true,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="https://gitea.io/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="https://gitea.io/wiki/raw/image.jpg" rel="nofollow"><img src="https://gitea.io/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 4
|
||||
Links: markup.Links{
|
||||
Base: "/relative/path",
|
||||
},
|
||||
IsWiki: false,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/relative/path/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/src/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/relative/path/image.jpg" rel="nofollow"><img src="/relative/path/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 5
|
||||
Links: markup.Links{
|
||||
Base: "/relative/path",
|
||||
},
|
||||
IsWiki: true,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 6
|
||||
Links: markup.Links{
|
||||
Base: "/user/repo",
|
||||
BranchPath: "branch/main",
|
||||
},
|
||||
IsWiki: false,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/user/repo/src/branch/main/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/user/repo/media/branch/main/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/user/repo/media/branch/main/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 7
|
||||
Links: markup.Links{
|
||||
Base: "/relative/path",
|
||||
BranchPath: "branch/main",
|
||||
},
|
||||
IsWiki: true,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 8
|
||||
Links: markup.Links{
|
||||
Base: "/user/repo",
|
||||
TreePath: "sub/folder",
|
||||
},
|
||||
IsWiki: false,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/user/repo/src/sub/folder/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/user/repo/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/user/repo/image.jpg" rel="nofollow"><img src="/user/repo/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 9
|
||||
Links: markup.Links{
|
||||
Base: "/relative/path",
|
||||
TreePath: "sub/folder",
|
||||
},
|
||||
IsWiki: true,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 10
|
||||
Links: markup.Links{
|
||||
Base: "/user/repo",
|
||||
BranchPath: "branch/main",
|
||||
TreePath: "sub/folder",
|
||||
},
|
||||
IsWiki: false,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/user/repo/src/branch/main/sub/folder/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/user/repo/media/branch/main/sub/folder/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/user/repo/media/branch/main/sub/folder/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
{ // 11
|
||||
Links: markup.Links{
|
||||
Base: "/relative/path",
|
||||
BranchPath: "branch/main",
|
||||
TreePath: "sub/folder",
|
||||
},
|
||||
IsWiki: true,
|
||||
Expected: `<p>space @mention-user<br/>
|
||||
/just/a/path.bin<br/>
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
|
||||
<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||
@mention-user test<br/>
|
||||
#123<br/>
|
||||
space</p>
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
result, err := RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
|
||||
assert.NoError(t, err, "Unexpected error in testcase: %v", i)
|
||||
assert.Equal(t, c.Expected, result, "Unexpected result in testcase %v", i)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
@@ -101,8 +100,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
||||
|
||||
w := &Writer{
|
||||
HTMLWriter: htmlWriter,
|
||||
URLPrefix: ctx.URLPrefix,
|
||||
IsWiki: ctx.IsWiki,
|
||||
Ctx: ctx,
|
||||
}
|
||||
|
||||
htmlWriter.ExtendingWriter = w
|
||||
@@ -132,63 +130,53 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
|
||||
// Writer implements org.Writer
|
||||
type Writer struct {
|
||||
*org.HTMLWriter
|
||||
URLPrefix string
|
||||
IsWiki bool
|
||||
Ctx *markup.RenderContext
|
||||
}
|
||||
|
||||
var byteMailto = []byte("mailto:")
|
||||
const mailto = "mailto:"
|
||||
|
||||
// WriteRegularLink renders images, links or videos
|
||||
func (r *Writer) WriteRegularLink(l org.RegularLink) {
|
||||
link := []byte(html.EscapeString(l.URL))
|
||||
func (r *Writer) resolveLink(l org.RegularLink) string {
|
||||
link := html.EscapeString(l.URL)
|
||||
if l.Protocol == "file" {
|
||||
link = link[len("file:"):]
|
||||
}
|
||||
if len(link) > 0 && !markup.IsLink(link) &&
|
||||
link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
|
||||
lnk := string(link)
|
||||
if r.IsWiki {
|
||||
lnk = util.URLJoin("wiki", lnk)
|
||||
if len(link) > 0 && !markup.IsLinkStr(link) &&
|
||||
link[0] != '#' && !strings.HasPrefix(link, mailto) {
|
||||
base := r.Ctx.Links.Base
|
||||
switch l.Kind() {
|
||||
case "image", "video":
|
||||
base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki)
|
||||
}
|
||||
link = []byte(util.URLJoin(r.URLPrefix, lnk))
|
||||
link = util.URLJoin(base, link)
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
// WriteRegularLink renders images, links or videos
|
||||
func (r *Writer) WriteRegularLink(l org.RegularLink) {
|
||||
link := r.resolveLink(l)
|
||||
|
||||
// Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427
|
||||
switch l.Kind() {
|
||||
case "image":
|
||||
if l.Description == nil {
|
||||
imageSrc := getMediaURL(link)
|
||||
fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, link, link)
|
||||
fmt.Fprintf(r, `<img src="%s" alt="%s" />`, link, link)
|
||||
} else {
|
||||
description := strings.TrimPrefix(org.String(l.Description...), "file:")
|
||||
imageSrc := getMediaURL([]byte(description))
|
||||
imageSrc := r.resolveLink(l.Description[0].(org.RegularLink))
|
||||
fmt.Fprintf(r, `<a href="%s"><img src="%s" alt="%s" /></a>`, link, imageSrc, imageSrc)
|
||||
}
|
||||
case "video":
|
||||
if l.Description == nil {
|
||||
imageSrc := getMediaURL(link)
|
||||
fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, imageSrc, link, link)
|
||||
fmt.Fprintf(r, `<video src="%s">%s</video>`, link, link)
|
||||
} else {
|
||||
description := strings.TrimPrefix(org.String(l.Description...), "file:")
|
||||
videoSrc := getMediaURL([]byte(description))
|
||||
fmt.Fprintf(r, `<a href="%s"><video src="%s" title="%s"></video></a>`, link, videoSrc, videoSrc)
|
||||
videoSrc := r.resolveLink(l.Description[0].(org.RegularLink))
|
||||
fmt.Fprintf(r, `<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc)
|
||||
}
|
||||
default:
|
||||
description := string(link)
|
||||
description := link
|
||||
if l.Description != nil {
|
||||
description = r.WriteNodesAsString(l.Description...)
|
||||
}
|
||||
fmt.Fprintf(r, `<a href="%s" title="%s">%s</a>`, link, description, description)
|
||||
}
|
||||
}
|
||||
|
||||
func getMediaURL(l []byte) string {
|
||||
srcURL := string(l)
|
||||
|
||||
// Check if link is valid
|
||||
if len(srcURL) > 0 && !markup.IsLink(l) {
|
||||
srcURL = strings.Replace(srcURL, "/src/", "/media/", 1)
|
||||
}
|
||||
|
||||
return srcURL
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ func TestRender_StandardLinks(t *testing.T) {
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
@@ -48,8 +50,10 @@ func TestRender_Media(t *testing.T) {
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
@@ -59,19 +63,19 @@ func TestRender_Media(t *testing.T) {
|
||||
result := util.URLJoin(AppSubURL, url)
|
||||
|
||||
test("[[file:"+url+"]]",
|
||||
"<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>")
|
||||
"<p><img src=\""+result+"\" alt=\""+result+"\" /></p>")
|
||||
|
||||
// With description.
|
||||
test("[[https://example.com][https://example.com/example.svg]]",
|
||||
`<p><a href="https://example.com"><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></a></p>`)
|
||||
test("[[https://example.com][https://example.com/example.mp4]]",
|
||||
`<p><a href="https://example.com"><video src="https://example.com/example.mp4" title="https://example.com/example.mp4"></video></a></p>`)
|
||||
`<p><a href="https://example.com"><video src="https://example.com/example.mp4">https://example.com/example.mp4</video></a></p>`)
|
||||
|
||||
// Without description.
|
||||
test("[[https://example.com/example.svg]]",
|
||||
`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" title="https://example.com/example.svg" /></p>`)
|
||||
`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></p>`)
|
||||
test("[[https://example.com/example.mp4]]",
|
||||
`<p><video src="https://example.com/example.mp4" title="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`)
|
||||
`<p><video src="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`)
|
||||
}
|
||||
|
||||
func TestRender_Source(t *testing.T) {
|
||||
@@ -80,8 +84,7 @@ func TestRender_Source(t *testing.T) {
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Ctx: git.DefaultContext,
|
||||
}, input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
)
|
||||
@@ -69,7 +70,7 @@ type RenderContext struct {
|
||||
RelativePath string // relative path from tree root of the branch
|
||||
Type string
|
||||
IsWiki bool
|
||||
URLPrefix string
|
||||
Links Links
|
||||
Metas map[string]string
|
||||
DefaultLink string
|
||||
GitRepo *git.Repository
|
||||
@@ -80,6 +81,45 @@ type RenderContext struct {
|
||||
InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
|
||||
}
|
||||
|
||||
type Links struct {
|
||||
Base string
|
||||
BranchPath string
|
||||
TreePath string
|
||||
}
|
||||
|
||||
func (l *Links) HasBranchInfo() bool {
|
||||
return l.BranchPath != ""
|
||||
}
|
||||
|
||||
func (l *Links) SrcLink() string {
|
||||
return util.URLJoin(l.Base, "src", l.BranchPath, l.TreePath)
|
||||
}
|
||||
|
||||
func (l *Links) MediaLink() string {
|
||||
return util.URLJoin(l.Base, "media", l.BranchPath, l.TreePath)
|
||||
}
|
||||
|
||||
func (l *Links) RawLink() string {
|
||||
return util.URLJoin(l.Base, "raw", l.BranchPath, l.TreePath)
|
||||
}
|
||||
|
||||
func (l *Links) WikiLink() string {
|
||||
return util.URLJoin(l.Base, "wiki")
|
||||
}
|
||||
|
||||
func (l *Links) WikiRawLink() string {
|
||||
return util.URLJoin(l.Base, "wiki/raw")
|
||||
}
|
||||
|
||||
func (l *Links) ResolveMediaLink(isWiki bool) string {
|
||||
if isWiki {
|
||||
return l.WikiRawLink()
|
||||
} else if l.HasBranchInfo() {
|
||||
return l.MediaLink()
|
||||
}
|
||||
return l.Base
|
||||
}
|
||||
|
||||
// Cancel runs any cleanup functions that have been registered for this Ctx
|
||||
func (ctx *RenderContext) Cancel() {
|
||||
if ctx == nil {
|
||||
|
||||
@@ -22,5 +22,8 @@ func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, Resp
|
||||
})
|
||||
|
||||
resp, extra := requestJSONResp(req, &responseText{})
|
||||
if extra.HasError() {
|
||||
return "", extra
|
||||
}
|
||||
return resp.Text, extra
|
||||
}
|
||||
|
||||
@@ -27,5 +27,8 @@ func AuthorizedPublicKeyByContent(ctx context.Context, content string) (string,
|
||||
req := newInternalRequest(ctx, reqURL, "POST")
|
||||
req.Param("content", content)
|
||||
resp, extra := requestJSONResp(req, &responseText{})
|
||||
if extra.HasError() {
|
||||
return "", extra
|
||||
}
|
||||
return resp.Text, extra
|
||||
}
|
||||
|
||||
@@ -30,5 +30,8 @@ func SendEmail(ctx context.Context, subject, message string, to []string) (strin
|
||||
})
|
||||
|
||||
resp, extra := requestJSONResp(req, &responseText{})
|
||||
if extra.HasError() {
|
||||
return "", extra
|
||||
}
|
||||
return resp.Text, extra
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ func (re responseError) Error() string {
|
||||
// requestJSONResp sends a request to the gitea server and then parses the response.
|
||||
// If the status code is not 2xx, or any error occurs, the ResponseExtra.Error field is guaranteed to be non-nil,
|
||||
// and the ResponseExtra.UserMsg field will be set to a message for the end user.
|
||||
// Caller should check the ResponseExtra.HasError() first to see whether the request fails.
|
||||
//
|
||||
// * If the "res" is a struct pointer, the response will be parsed as JSON
|
||||
// * If the "res" is responseText pointer, the response will be stored as text in it
|
||||
|
||||
@@ -33,7 +33,7 @@ func FileHandlerFunc() http.HandlerFunc {
|
||||
assetFS := AssetFS()
|
||||
return func(resp http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "GET" && req.Method != "HEAD" {
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
handleRequest(resp, req, assetFS, req.URL.Path)
|
||||
|
||||
@@ -12,9 +12,7 @@ import (
|
||||
// CORSConfig defines CORS settings
|
||||
var CORSConfig = struct {
|
||||
Enabled bool
|
||||
Scheme string
|
||||
AllowDomain []string
|
||||
AllowSubdomain bool
|
||||
AllowDomain []string // FIXME: this option is from legacy code, it actually works as "AllowedOrigins". When refactoring in the future, the config option should also be renamed together.
|
||||
Methods []string
|
||||
MaxAge time.Duration
|
||||
AllowCredentials bool
|
||||
|
||||
@@ -168,7 +168,7 @@ func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbsslMode s
|
||||
RawQuery: dbParam,
|
||||
}
|
||||
query := connURL.Query()
|
||||
if dbHost[0] == '/' { // looks like a unix socket
|
||||
if strings.HasPrefix(dbHost, "/") { // looks like a unix socket
|
||||
query.Add("host", dbHost)
|
||||
connURL.Host = ":" + port
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ func Test_getPostgreSQLConnectionString(t *testing.T) {
|
||||
SSLMode string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
Host: "", // empty means default
|
||||
Output: "postgres://:@127.0.0.1:5432?sslmode=",
|
||||
},
|
||||
{
|
||||
Host: "/tmp/pg.sock",
|
||||
User: "testuser",
|
||||
|
||||
@@ -343,8 +343,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
||||
LandingPageURL = LandingPageOrganizations
|
||||
case "login":
|
||||
LandingPageURL = LandingPageLogin
|
||||
case "":
|
||||
case "home":
|
||||
case "", "home":
|
||||
LandingPageURL = LandingPageHome
|
||||
default:
|
||||
LandingPageURL = LandingPage(landingPage)
|
||||
|
||||
@@ -157,7 +157,6 @@ func NewFuncMap() template.FuncMap {
|
||||
"RenderEmoji": RenderEmoji,
|
||||
"RenderEmojiPlain": emoji.ReplaceAliases,
|
||||
"ReactionToEmoji": ReactionToEmoji,
|
||||
"RenderNote": RenderNote,
|
||||
|
||||
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
||||
"RenderLabel": RenderLabel,
|
||||
|
||||
@@ -24,21 +24,13 @@ import (
|
||||
)
|
||||
|
||||
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
||||
func RenderCommitMessage(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
|
||||
return RenderCommitMessageLink(ctx, msg, urlPrefix, "", metas)
|
||||
}
|
||||
|
||||
// RenderCommitMessageLink renders commit message as a XXS-safe link to the provided
|
||||
// default url, handling for special links.
|
||||
func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
|
||||
func RenderCommitMessage(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
||||
cleanMsg := template.HTMLEscapeString(msg)
|
||||
// we can safely assume that it will not return any error, since there
|
||||
// shouldn't be any special HTML.
|
||||
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: urlPrefix,
|
||||
DefaultLink: urlDefault,
|
||||
Metas: metas,
|
||||
Ctx: ctx,
|
||||
Metas: metas,
|
||||
}, cleanMsg)
|
||||
if err != nil {
|
||||
log.Error("RenderCommitMessage: %v", err)
|
||||
@@ -51,9 +43,9 @@ func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault str
|
||||
return template.HTML(msgLines[0])
|
||||
}
|
||||
|
||||
// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to
|
||||
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
|
||||
// the provided default url, handling for special links without email to links.
|
||||
func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
|
||||
func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
|
||||
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
|
||||
lineEnd := strings.IndexByte(msgLine, '\n')
|
||||
if lineEnd > 0 {
|
||||
@@ -68,7 +60,6 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefa
|
||||
// shouldn't be any special HTML.
|
||||
renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: urlPrefix,
|
||||
DefaultLink: urlDefault,
|
||||
Metas: metas,
|
||||
}, template.HTMLEscapeString(msgLine))
|
||||
@@ -80,7 +71,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefa
|
||||
}
|
||||
|
||||
// RenderCommitBody extracts the body of a commit message without its title.
|
||||
func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
|
||||
func RenderCommitBody(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
||||
msgLine := strings.TrimSpace(msg)
|
||||
lineEnd := strings.IndexByte(msgLine, '\n')
|
||||
if lineEnd > 0 {
|
||||
@@ -94,9 +85,8 @@ func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[stri
|
||||
}
|
||||
|
||||
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: urlPrefix,
|
||||
Metas: metas,
|
||||
Ctx: ctx,
|
||||
Metas: metas,
|
||||
}, template.HTMLEscapeString(msgLine))
|
||||
if err != nil {
|
||||
log.Error("RenderCommitMessage: %v", err)
|
||||
@@ -115,11 +105,10 @@ func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
|
||||
}
|
||||
|
||||
// RenderIssueTitle renders issue/pull title with defined post processors
|
||||
func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML {
|
||||
func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) template.HTML {
|
||||
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: urlPrefix,
|
||||
Metas: metas,
|
||||
Ctx: ctx,
|
||||
Metas: metas,
|
||||
}, template.HTMLEscapeString(text))
|
||||
if err != nil {
|
||||
log.Error("RenderIssueTitle: %v", err)
|
||||
@@ -143,7 +132,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
|
||||
|
||||
if labelScope == "" {
|
||||
// Regular label
|
||||
s := fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</div>",
|
||||
s := fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' data-tooltip-content title='%s'>%s</div>",
|
||||
textColor, label.Color, description, RenderEmoji(ctx, label.Name))
|
||||
return template.HTML(s)
|
||||
}
|
||||
@@ -177,7 +166,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
|
||||
itemColor := "#" + hex.EncodeToString(itemBytes)
|
||||
scopeColor := "#" + hex.EncodeToString(scopeBytes)
|
||||
|
||||
s := fmt.Sprintf("<span class='ui label scope-parent' title='%s'>"+
|
||||
s := fmt.Sprintf("<span class='ui label scope-parent' data-tooltip-content title='%s'>"+
|
||||
"<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
||||
"<div class='ui label scope-right' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
||||
"</span>",
|
||||
@@ -211,25 +200,10 @@ func ReactionToEmoji(reaction string) template.HTML {
|
||||
return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
|
||||
}
|
||||
|
||||
// RenderNote renders the contents of a git-notes file as a commit message.
|
||||
func RenderNote(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
|
||||
cleanMsg := template.HTMLEscapeString(msg)
|
||||
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: urlPrefix,
|
||||
Metas: metas,
|
||||
}, cleanMsg)
|
||||
if err != nil {
|
||||
log.Error("RenderNote: %v", err)
|
||||
return ""
|
||||
}
|
||||
return template.HTML(fullMessage)
|
||||
}
|
||||
|
||||
func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive
|
||||
output, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Ctx: ctx,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
}, input)
|
||||
if err != nil {
|
||||
log.Error("RenderString: %v", err)
|
||||
|
||||
@@ -6,17 +6,64 @@ package templates
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const testInput = ` space @mention-user
|
||||
/just/a/path.bin
|
||||
https://example.com/file.bin
|
||||
[local link](file.bin)
|
||||
[remote link](https://example.com)
|
||||
[[local link|file.bin]]
|
||||
[[remote link|https://example.com]]
|
||||

|
||||

|
||||
[[local image|image.jpg]]
|
||||
[[remote link|https://example.com/image.jpg]]
|
||||
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
:+1:
|
||||
mail@domain.com
|
||||
@mention-user test
|
||||
#123
|
||||
space
|
||||
`
|
||||
|
||||
var testMetas = map[string]string{
|
||||
"user": "user13",
|
||||
"repo": "repo11",
|
||||
"repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
|
||||
"mode": "comment",
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.InitSettings()
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git init failed, err: %v", err)
|
||||
}
|
||||
markup.Init(&markup.ProcessorHelper{
|
||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||
return username == "mention-user"
|
||||
},
|
||||
})
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRenderCommitBody(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
msg string
|
||||
urlPrefix string
|
||||
metas map[string]string
|
||||
ctx context.Context
|
||||
msg string
|
||||
metas map[string]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -50,7 +97,91 @@ func TestRenderCommitBody(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, RenderCommitBody(tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas), "RenderCommitBody(%v, %v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas)
|
||||
assert.Equalf(t, tt.want, RenderCommitBody(tt.args.ctx, tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.metas)
|
||||
})
|
||||
}
|
||||
|
||||
expected := `/just/a/path.bin
|
||||
<a href="https://example.com/file.bin" class="link">https://example.com/file.bin</a>
|
||||
[local link](file.bin)
|
||||
[remote link](<a href="https://example.com" class="link">https://example.com</a>)
|
||||
[[local link|file.bin]]
|
||||
[[remote link|<a href="https://example.com" class="link">https://example.com</a>]]
|
||||

|
||||

|
||||
[[local image|image.jpg]]
|
||||
[[remote link|<a href="https://example.com/image.jpg" class="link">https://example.com/image.jpg</a>]]
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" class="compare"><code class="nohighlight">88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code class="nohighlight">88fc37a3c0</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
|
||||
<a href="http://localhost:3000/mention-user" class="mention">@mention-user</a> test
|
||||
<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
space`
|
||||
|
||||
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
func TestRenderCommitMessage(t *testing.T) {
|
||||
expected := `space <a href="http://localhost:3000/mention-user" class="mention">@mention-user</a> `
|
||||
|
||||
assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
||||
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="http://localhost:3000/mention-user" class="mention">@mention-user</a>`
|
||||
|
||||
assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas))
|
||||
}
|
||||
|
||||
func TestRenderIssueTitle(t *testing.T) {
|
||||
expected := ` space @mention-user
|
||||
/just/a/path.bin
|
||||
https://example.com/file.bin
|
||||
[local link](file.bin)
|
||||
[remote link](https://example.com)
|
||||
[[local link|file.bin]]
|
||||
[[remote link|https://example.com]]
|
||||

|
||||

|
||||
[[local image|image.jpg]]
|
||||
[[remote link|https://example.com/image.jpg]]
|
||||
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
mail@domain.com
|
||||
@mention-user test
|
||||
<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
space
|
||||
`
|
||||
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||
expected := `<p>space <a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a><br/>
|
||||
/just/a/path.bin
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
||||
<a href="/file.bin" rel="nofollow">local link</a>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a>
|
||||
<a href="/src/file.bin" rel="nofollow">local link</a>
|
||||
<a href="https://example.com" rel="nofollow">remote link</a>
|
||||
<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a>
|
||||
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
|
||||
<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a>
|
||||
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a>
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
||||
<a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a> test
|
||||
#123
|
||||
space</p>
|
||||
`
|
||||
assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput))
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DateTime renders an absolute time HTML element by datetime.
|
||||
func DateTime(format string, datetime any) template.HTML {
|
||||
func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
|
||||
if p, ok := datetime.(*time.Time); ok {
|
||||
datetime = *p
|
||||
}
|
||||
@@ -48,13 +49,20 @@ func DateTime(format string, datetime any) template.HTML {
|
||||
panic(fmt.Sprintf("Unsupported time type %T", datetime))
|
||||
}
|
||||
|
||||
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"`)
|
||||
|
||||
switch format {
|
||||
case "short":
|
||||
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped))
|
||||
attrs = append(attrs, `month="short"`, `day="numeric"`)
|
||||
case "long":
|
||||
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="long" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped))
|
||||
attrs = append(attrs, `month="long"`, `day="numeric"`)
|
||||
case "full":
|
||||
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped))
|
||||
attrs = append(attrs, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported format %s", format))
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -8,16 +8,14 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDateTime(t *testing.T) {
|
||||
oldTz := setting.DefaultUILocation
|
||||
setting.DefaultUILocation, _ = time.LoadLocation("America/New_York")
|
||||
defer func() {
|
||||
setting.DefaultUILocation = oldTz
|
||||
}()
|
||||
testTz, _ := time.LoadLocation("America/New_York")
|
||||
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
||||
|
||||
refTimeStr := "2018-01-01T00:00:00Z"
|
||||
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
|
||||
@@ -29,17 +27,17 @@ func TestDateTime(t *testing.T) {
|
||||
assert.EqualValues(t, "-", DateTime("short", TimeStamp(0)))
|
||||
|
||||
actual := DateTime("short", "invalid")
|
||||
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="invalid">invalid</relative-time>`, actual)
|
||||
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)
|
||||
|
||||
actual = DateTime("short", refTimeStr)
|
||||
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</relative-time>`, actual)
|
||||
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)
|
||||
|
||||
actual = DateTime("short", refTime)
|
||||
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01</relative-time>`, actual)
|
||||
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)
|
||||
|
||||
actual = DateTime("short", refTimeStamp)
|
||||
assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2017-12-31T19:00:00-05:00">2017-12-31</relative-time>`, actual)
|
||||
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)
|
||||
|
||||
actual = DateTime("full", refTimeStamp)
|
||||
assert.EqualValues(t, `<relative-time 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 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)
|
||||
}
|
||||
|
||||
+6
-18
@@ -101,16 +101,18 @@ func (r *Route) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Han
|
||||
return middlewares, handlerFunc
|
||||
}
|
||||
|
||||
func (r *Route) Methods(method, pattern string, h ...any) {
|
||||
// Methods adds the same handlers for multiple http "methods" (separated by ",").
|
||||
// If any method is invalid, the lower level router will panic.
|
||||
func (r *Route) Methods(methods, pattern string, h ...any) {
|
||||
middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
|
||||
fullPattern := r.getPattern(pattern)
|
||||
if strings.Contains(method, ",") {
|
||||
methods := strings.Split(method, ",")
|
||||
if strings.Contains(methods, ",") {
|
||||
methods := strings.Split(methods, ",")
|
||||
for _, method := range methods {
|
||||
r.R.With(middlewares...).Method(strings.TrimSpace(method), fullPattern, handlerFunc)
|
||||
}
|
||||
} else {
|
||||
r.R.With(middlewares...).Method(method, fullPattern, handlerFunc)
|
||||
r.R.With(middlewares...).Method(methods, fullPattern, handlerFunc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,20 +138,6 @@ func (r *Route) Get(pattern string, h ...any) {
|
||||
r.Methods("GET", pattern, h...)
|
||||
}
|
||||
|
||||
func (r *Route) Options(pattern string, h ...any) {
|
||||
r.Methods("OPTIONS", pattern, h...)
|
||||
}
|
||||
|
||||
// GetOptions delegate get and options method
|
||||
func (r *Route) GetOptions(pattern string, h ...any) {
|
||||
r.Methods("GET,OPTIONS", pattern, h...)
|
||||
}
|
||||
|
||||
// PostOptions delegate post and options method
|
||||
func (r *Route) PostOptions(pattern string, h ...any) {
|
||||
r.Methods("POST,OPTIONS", pattern, h...)
|
||||
}
|
||||
|
||||
// Head delegate head method
|
||||
func (r *Route) Head(pattern string, h ...any) {
|
||||
r.Methods("HEAD", pattern, h...)
|
||||
|
||||
@@ -31,6 +31,7 @@ const (
|
||||
HookEventRepository HookEventType = "repository"
|
||||
HookEventRelease HookEventType = "release"
|
||||
HookEventPackage HookEventType = "package"
|
||||
HookEventSchedule HookEventType = "schedule"
|
||||
)
|
||||
|
||||
// Event returns the HookEventType as an event string
|
||||
|
||||
@@ -422,6 +422,7 @@ authorization_failed_desc = The authorization failed because we detected an inva
|
||||
sspi_auth_failed = SSPI authentication failed
|
||||
password_pwned = The password you chose is on a <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">list of stolen passwords</a> previously exposed in public data breaches. Please try again with a different password and consider changing this password elsewhere too.
|
||||
password_pwned_err = Could not complete request to HaveIBeenPwned
|
||||
last_admin = You cannot remove the last admin. There must be at least one admin.
|
||||
|
||||
[mail]
|
||||
view_it_on = View it on %s
|
||||
@@ -587,6 +588,8 @@ org_still_own_packages = "This organization still owns one or more packages, del
|
||||
|
||||
target_branch_not_exist = Target branch does not exist.
|
||||
|
||||
admin_cannot_delete_self = You cannot delete yourself when you are an admin. Please remove your admin privileges first.
|
||||
|
||||
[user]
|
||||
change_avatar = Change your avatar…
|
||||
joined_on = Joined on %s
|
||||
|
||||
Generated
+7
-7
@@ -30,7 +30,7 @@
|
||||
"katex": "0.16.9",
|
||||
"license-checker-webpack-plugin": "0.2.1",
|
||||
"lightningcss-loader": "2.1.0",
|
||||
"mermaid": "10.5.0",
|
||||
"mermaid": "10.6.1",
|
||||
"mini-css-extract-plugin": "2.7.6",
|
||||
"minimatch": "9.0.3",
|
||||
"monaco-editor": "0.44.0",
|
||||
@@ -1000,9 +1000,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@github/combobox-nav": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.0.tgz",
|
||||
"integrity": "sha512-5CX03DbsLZ41dX5hKHyQKtg133U6lruX4TD9G0Zs4W8BpWy7lN8DJ6TYaeZN/V7x8K34coaqNYk/Y5ic7stfkg=="
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.1.tgz",
|
||||
"integrity": "sha512-gwxPzLw8XKecy1nP63i9lOBritS3bWmxl02UX6G0TwMQZbMem1BCS1tEZgYd3mkrkiDrUMWaX+DbFCuDFo3K+A=="
|
||||
},
|
||||
"node_modules/@github/markdown-toolbar-element": {
|
||||
"version": "2.2.1",
|
||||
@@ -7530,9 +7530,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mermaid": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.5.0.tgz",
|
||||
"integrity": "sha512-9l0o1uUod78D3/FVYPGSsgV+Z0tSnzLBDiC9rVzvelPxuO80HbN1oDr9ofpPETQy9XpypPQa26fr09VzEPfvWA==",
|
||||
"version": "10.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-10.6.1.tgz",
|
||||
"integrity": "sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.1",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
"katex": "0.16.9",
|
||||
"license-checker-webpack-plugin": "0.2.1",
|
||||
"lightningcss-loader": "2.1.0",
|
||||
"mermaid": "10.5.0",
|
||||
"mermaid": "10.6.1",
|
||||
"mini-css-extract-plugin": "2.7.6",
|
||||
"minimatch": "9.0.3",
|
||||
"monaco-editor": "0.44.0",
|
||||
|
||||
@@ -183,6 +183,8 @@ func EditUser(ctx *context.APIContext) {
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/User"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "422":
|
||||
@@ -264,6 +266,10 @@ func EditUser(ctx *context.APIContext) {
|
||||
ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
|
||||
}
|
||||
if form.Admin != nil {
|
||||
if !*form.Admin && user_model.IsLastAdminUser(ctx, ctx.ContextUser) {
|
||||
ctx.Error(http.StatusBadRequest, "LastAdmin", ctx.Tr("auth.last_admin"))
|
||||
return
|
||||
}
|
||||
ctx.ContextUser.IsAdmin = *form.Admin
|
||||
}
|
||||
if form.AllowGitHook != nil {
|
||||
@@ -341,7 +347,8 @@ func DeleteUser(ctx *context.APIContext) {
|
||||
if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil {
|
||||
if models.IsErrUserOwnRepos(err) ||
|
||||
models.IsErrUserHasOrgs(err) ||
|
||||
models.IsErrUserOwnPackages(err) {
|
||||
models.IsErrUserOwnPackages(err) ||
|
||||
models.IsErrDeleteLastAdminUser(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteUser", err)
|
||||
|
||||
@@ -821,9 +821,7 @@ func Routes() *web.Route {
|
||||
m.Use(securityHeaders())
|
||||
if setting.CORSConfig.Enabled {
|
||||
m.Use(cors.Handler(cors.Options{
|
||||
// Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
|
||||
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
||||
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
|
||||
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
||||
AllowedMethods: setting.CORSConfig.Methods,
|
||||
AllowCredentials: setting.CORSConfig.AllowCredentials,
|
||||
AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
|
||||
@@ -1145,9 +1143,9 @@ func Routes() *web.Route {
|
||||
m.Get("/subscribers", repo.ListSubscribers)
|
||||
m.Group("/subscription", func() {
|
||||
m.Get("", user.IsWatching)
|
||||
m.Put("", reqToken(), user.Watch)
|
||||
m.Delete("", reqToken(), user.Unwatch)
|
||||
})
|
||||
m.Put("", user.Watch)
|
||||
m.Delete("", user.Unwatch)
|
||||
}, reqToken())
|
||||
m.Group("/releases", func() {
|
||||
m.Combo("").Get(repo.ListReleases).
|
||||
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
|
||||
|
||||
@@ -102,23 +102,24 @@ func GetIssueDependencies(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var lastRepoID int64
|
||||
var lastPerm access_model.Permission
|
||||
repoPerms := make(map[int64]access_model.Permission)
|
||||
repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
|
||||
for _, blocker := range blockersInfo {
|
||||
// Get the permissions for this repository
|
||||
perm := lastPerm
|
||||
if lastRepoID != blocker.Repository.ID {
|
||||
if blocker.Repository.ID == ctx.Repo.Repository.ID {
|
||||
perm = ctx.Repo.Permission
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
// If the repo ID exists in the map, return the exist permissions
|
||||
// else get the permission and add it to the map
|
||||
var perm access_model.Permission
|
||||
existPerm, ok := repoPerms[blocker.RepoID]
|
||||
if ok {
|
||||
perm = existPerm
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
lastRepoID = blocker.Repository.ID
|
||||
repoPerms[blocker.RepoID] = perm
|
||||
}
|
||||
|
||||
// check permission
|
||||
@@ -341,29 +342,31 @@ func GetIssueBlocks(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var lastRepoID int64
|
||||
var lastPerm access_model.Permission
|
||||
|
||||
var issues []*issues_model.Issue
|
||||
|
||||
repoPerms := make(map[int64]access_model.Permission)
|
||||
repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
|
||||
|
||||
for i, depMeta := range deps {
|
||||
if i < skip || i >= max {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the permissions for this repository
|
||||
perm := lastPerm
|
||||
if lastRepoID != depMeta.Repository.ID {
|
||||
if depMeta.Repository.ID == ctx.Repo.Repository.ID {
|
||||
perm = ctx.Repo.Permission
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
// If the repo ID exists in the map, return the exist permissions
|
||||
// else get the permission and add it to the map
|
||||
var perm access_model.Permission
|
||||
existPerm, ok := repoPerms[depMeta.RepoID]
|
||||
if ok {
|
||||
perm = existPerm
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
lastRepoID = depMeta.Repository.ID
|
||||
repoPerms[depMeta.RepoID] = perm
|
||||
}
|
||||
|
||||
if !perm.CanReadIssuesOrPulls(depMeta.Issue.IsPull) {
|
||||
|
||||
@@ -982,7 +982,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
}
|
||||
|
||||
if len(units)+len(deleteUnitTypes) > 0 {
|
||||
if err := repo_model.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil {
|
||||
if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -194,8 +194,10 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
statuses, maxResults, err := git_model.GetCommitStatuses(ctx, repo, sha, &git_model.CommitStatusOptions{
|
||||
statuses, maxResults, err := git_model.GetCommitStatuses(ctx, &git_model.CommitStatusOptions{
|
||||
ListOptions: listOptions,
|
||||
RepoID: repo.ID,
|
||||
SHA: sha,
|
||||
SortType: ctx.FormTrim("sort"),
|
||||
State: ctx.FormTrim("state"),
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ package utils
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -162,6 +163,7 @@ func pullHook(events []string, event string) bool {
|
||||
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
|
||||
// an error, write to `ctx` accordingly. Return (webhook, ok)
|
||||
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
|
||||
var isSystemWebhook bool
|
||||
if !checkCreateHookOption(ctx, form) {
|
||||
return nil, false
|
||||
}
|
||||
@@ -169,13 +171,22 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
||||
if len(form.Events) == 0 {
|
||||
form.Events = []string{"push"}
|
||||
}
|
||||
if form.Config["is_system_webhook"] != "" {
|
||||
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid is_system_webhook value")
|
||||
return nil, false
|
||||
}
|
||||
isSystemWebhook = sw
|
||||
}
|
||||
w := &webhook.Webhook{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
URL: form.Config["url"],
|
||||
ContentType: webhook.ToHookContentType(form.Config["content_type"]),
|
||||
Secret: form.Config["secret"],
|
||||
HTTPMethod: "POST",
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
URL: form.Config["url"],
|
||||
ContentType: webhook.ToHookContentType(form.Config["content_type"]),
|
||||
Secret: form.Config["secret"],
|
||||
HTTPMethod: "POST",
|
||||
IsSystemWebhook: isSystemWebhook,
|
||||
HookEvent: &webhook_module.HookEvent{
|
||||
ChooseEvents: true,
|
||||
HookEvents: webhook_module.HookEvents{
|
||||
|
||||
@@ -37,7 +37,6 @@ func InitDBEngine(ctx context.Context) (err error) {
|
||||
log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second))
|
||||
time.Sleep(setting.Database.DBConnectBackoff)
|
||||
}
|
||||
db.HasEngine = true
|
||||
config.SetDynGetter(system_model.NewDatabaseDynKeyGetter())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -32,8 +32,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
||||
case "markdown":
|
||||
// Raw markdown
|
||||
if err := markdown.RenderRaw(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: urlPrefix,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: urlPrefix,
|
||||
},
|
||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
@@ -75,8 +77,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
||||
}
|
||||
|
||||
if err := markup.Render(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: urlPrefix,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: urlPrefix,
|
||||
},
|
||||
Metas: meta,
|
||||
IsWiki: wiki,
|
||||
Type: markupType,
|
||||
|
||||
@@ -58,4 +58,11 @@ func MonitorDiagnosis(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
_ = pprof.Lookup("goroutine").WriteTo(f, 1)
|
||||
|
||||
f, err = zipWriter.CreateHeader(&zip.FileHeader{Name: "heap.dat", Method: zip.Deflate, Modified: time.Now()})
|
||||
if err != nil {
|
||||
ctx.ServerError("Failed to create zip file", err)
|
||||
return
|
||||
}
|
||||
_ = pprof.Lookup("heap").WriteTo(f, 0)
|
||||
}
|
||||
|
||||
@@ -429,6 +429,12 @@ func EditUserPost(ctx *context.Context) {
|
||||
|
||||
}
|
||||
|
||||
// Check whether user is the last admin
|
||||
if !form.Admin && user_model.IsLastAdminUser(ctx, u) {
|
||||
ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
|
||||
return
|
||||
}
|
||||
|
||||
u.LoginName = form.LoginName
|
||||
u.FullName = form.FullName
|
||||
emailChanged := !strings.EqualFold(u.Email, form.Email)
|
||||
@@ -496,7 +502,10 @@ func DeleteUser(ctx *context.Context) {
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
|
||||
case models.IsErrUserOwnPackages(err):
|
||||
ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages"))
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/users/" + ctx.Params(":userid"))
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
|
||||
case models.IsErrDeleteLastAdminUser(err):
|
||||
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
|
||||
default:
|
||||
ctx.ServerError("DeleteUser", err)
|
||||
}
|
||||
|
||||
@@ -45,10 +45,6 @@ const (
|
||||
|
||||
// AutoSignIn reads cookie and try to auto-login.
|
||||
func AutoSignIn(ctx *context.Context) (bool, error) {
|
||||
if !db.HasEngine {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
uname := ctx.GetSiteCookie(setting.CookieUserName)
|
||||
if len(uname) == 0 {
|
||||
return false, nil
|
||||
@@ -130,7 +126,11 @@ func checkAutoLogin(ctx *context.Context) bool {
|
||||
|
||||
if isSucceed {
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))
|
||||
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)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +51,11 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
|
||||
// If rendering fails, the original markdown text is returned
|
||||
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) string {
|
||||
markdownCtx := &markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: act.GetRepoLink(ctx),
|
||||
Type: markdown.MarkupName,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: act.GetRepoLink(ctx),
|
||||
},
|
||||
Type: markdown.MarkupName,
|
||||
Metas: map[string]string{
|
||||
"user": act.GetRepoUserName(ctx),
|
||||
"repo": act.GetRepoName(ctx),
|
||||
@@ -199,7 +201,6 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
||||
switch act.OpType {
|
||||
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
|
||||
push := templates.ActionContent2Commits(act)
|
||||
repoLink := act.GetRepoAbsoluteLink(ctx)
|
||||
|
||||
for _, commit := range push.Commits {
|
||||
if len(desc) != 0 {
|
||||
@@ -208,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
||||
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
|
||||
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
|
||||
commit.Sha1,
|
||||
templates.RenderCommitMessage(ctx, commit.Message, repoLink, nil),
|
||||
templates.RenderCommitMessage(ctx, commit.Message, nil),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -288,9 +289,11 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, i
|
||||
|
||||
link := &feeds.Link{Href: rel.HTMLURL()}
|
||||
content, err = markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: rel.Repo.Link(),
|
||||
Metas: rel.Repo.ComposeMetas(),
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: rel.Repo.Link(),
|
||||
},
|
||||
Metas: rel.Repo.ComposeMetas(),
|
||||
}, rel.Note)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -42,8 +42,10 @@ func showUserFeed(ctx *context.Context, formatType string) {
|
||||
}
|
||||
|
||||
ctxUserDescription, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: ctx.ContextUser.HTMLURL(),
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.ContextUser.HTMLURL(),
|
||||
},
|
||||
Metas: map[string]string{
|
||||
"user": ctx.ContextUser.GetDisplayName(),
|
||||
},
|
||||
|
||||
+11
-11
@@ -28,16 +28,16 @@ func requireSignIn(ctx *context.Context) {
|
||||
|
||||
func gitHTTPRouters(m *web.Route) {
|
||||
m.Group("", func() {
|
||||
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
|
||||
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
|
||||
m.GetOptions("/info/refs", repo.GetInfoRefs)
|
||||
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
|
||||
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
|
||||
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
|
||||
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
|
||||
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
|
||||
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
|
||||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
|
||||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
|
||||
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
|
||||
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
|
||||
m.Methods("GET,OPTIONS", "/info/refs", repo.GetInfoRefs)
|
||||
m.Methods("GET,OPTIONS", "/HEAD", repo.GetTextFile("HEAD"))
|
||||
m.Methods("GET,OPTIONS", "/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
|
||||
m.Methods("GET,OPTIONS", "/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
|
||||
m.Methods("GET,OPTIONS", "/objects/info/packs", repo.GetInfoPacks)
|
||||
m.Methods("GET,OPTIONS", "/objects/info/{file:[^/]*}", repo.GetTextFile(""))
|
||||
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
|
||||
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
|
||||
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
|
||||
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
|
||||
}
|
||||
|
||||
@@ -33,10 +33,6 @@ func DummyOK(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func DummyBadRequest(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func RobotsTxt(w http.ResponseWriter, req *http.Request) {
|
||||
robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt")
|
||||
if ok, _ := util.IsExist(robotsTxt); !ok {
|
||||
|
||||
@@ -44,10 +44,8 @@ func Home(ctx *context.Context) {
|
||||
ctx.Data["Title"] = org.DisplayName()
|
||||
if len(org.Description) != 0 {
|
||||
desc, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
}, org.Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
|
||||
@@ -125,7 +125,7 @@ func RefBlame(ctx *context.Context) {
|
||||
}
|
||||
|
||||
type blameResult struct {
|
||||
Parts []git.BlamePart
|
||||
Parts []*git.BlamePart
|
||||
UsesIgnoreRevs bool
|
||||
FaultyIgnoreRevsFile bool
|
||||
}
|
||||
@@ -170,7 +170,9 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil
|
||||
func fillBlameResult(br *git.BlameReader, r *blameResult) error {
|
||||
r.UsesIgnoreRevs = br.UsesIgnoreRevs()
|
||||
|
||||
r.Parts = make([]git.BlamePart, 0, 5)
|
||||
previousHelper := make(map[string]*git.BlamePart)
|
||||
|
||||
r.Parts = make([]*git.BlamePart, 0, 5)
|
||||
for {
|
||||
blamePart, err := br.NextPart()
|
||||
if err != nil {
|
||||
@@ -179,13 +181,23 @@ func fillBlameResult(br *git.BlameReader, r *blameResult) error {
|
||||
if blamePart == nil {
|
||||
break
|
||||
}
|
||||
r.Parts = append(r.Parts, *blamePart)
|
||||
|
||||
if prev, ok := previousHelper[blamePart.Sha]; ok {
|
||||
if blamePart.PreviousSha == "" {
|
||||
blamePart.PreviousSha = prev.PreviousSha
|
||||
blamePart.PreviousPath = prev.PreviousPath
|
||||
}
|
||||
} else {
|
||||
previousHelper[blamePart.Sha] = blamePart
|
||||
}
|
||||
|
||||
r.Parts = append(r.Parts, blamePart)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[string]*user_model.UserCommit {
|
||||
func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[string]*user_model.UserCommit {
|
||||
// store commit data by SHA to look up avatar info etc
|
||||
commitNames := make(map[string]*user_model.UserCommit)
|
||||
// and as blameParts can reference the same commits multiple
|
||||
@@ -227,7 +239,7 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[str
|
||||
return commitNames
|
||||
}
|
||||
|
||||
func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit) {
|
||||
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
|
||||
repoLink := ctx.Repo.RepoLink
|
||||
|
||||
language := ""
|
||||
|
||||
@@ -7,7 +7,9 @@ package repo
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
@@ -21,7 +23,9 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitgraph"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
git_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
@@ -370,9 +374,21 @@ func Diff(ctx *context.Context) {
|
||||
note := &git.Note{}
|
||||
err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
|
||||
if err == nil {
|
||||
ctx.Data["Note"] = string(charset.ToUTF8WithFallback(note.Message))
|
||||
ctx.Data["NoteCommit"] = note.Commit
|
||||
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
||||
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message))))
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderCommitMessage", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["BranchName"], err = commit.GetBranchName()
|
||||
|
||||
@@ -844,6 +844,7 @@ func CompareDiff(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanWrite(unit.TypeProjects)
|
||||
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||
upload.AddUploadContext(ctx, "comment")
|
||||
|
||||
|
||||
+53
-48
@@ -1424,12 +1424,13 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
ctx.Data["IssueWatch"] = iw
|
||||
|
||||
issue.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, issue.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -1589,10 +1590,12 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
|
||||
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, comment.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -1666,10 +1669,12 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
} else if comment.Type.HasContentSupport() {
|
||||
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, comment.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -1948,7 +1953,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["BlockingDependencies"], ctx.Data["BlockingByDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
|
||||
ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -2009,38 +2014,34 @@ func ViewIssue(ctx *context.Context) {
|
||||
|
||||
// checkBlockedByIssues return canRead and notPermitted
|
||||
func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) {
|
||||
var (
|
||||
lastRepoID int64
|
||||
lastPerm access_model.Permission
|
||||
)
|
||||
for i, blocker := range blockers {
|
||||
repoPerms := make(map[int64]access_model.Permission)
|
||||
repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
|
||||
for _, blocker := range blockers {
|
||||
// Get the permissions for this repository
|
||||
perm := lastPerm
|
||||
if lastRepoID != blocker.Repository.ID {
|
||||
if blocker.Repository.ID == ctx.Repo.Repository.ID {
|
||||
perm = ctx.Repo.Permission
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return nil, nil
|
||||
}
|
||||
// If the repo ID exists in the map, return the exist permissions
|
||||
// else get the permission and add it to the map
|
||||
var perm access_model.Permission
|
||||
existPerm, ok := repoPerms[blocker.RepoID]
|
||||
if ok {
|
||||
perm = existPerm
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return nil, nil
|
||||
}
|
||||
lastRepoID = blocker.Repository.ID
|
||||
repoPerms[blocker.RepoID] = perm
|
||||
}
|
||||
|
||||
// check permission
|
||||
if !perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
|
||||
blockers[len(notPermitted)], blockers[i] = blocker, blockers[len(notPermitted)]
|
||||
notPermitted = blockers[:len(notPermitted)+1]
|
||||
if perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
|
||||
canRead = append(canRead, blocker)
|
||||
} else {
|
||||
notPermitted = append(notPermitted, blocker)
|
||||
}
|
||||
}
|
||||
blockers = blockers[len(notPermitted):]
|
||||
sortDependencyInfo(blockers)
|
||||
sortDependencyInfo(canRead)
|
||||
sortDependencyInfo(notPermitted)
|
||||
|
||||
return blockers, notPermitted
|
||||
return canRead, notPermitted
|
||||
}
|
||||
|
||||
func sortDependencyInfo(blockers []*issues_model.DependencyInfo) {
|
||||
@@ -2224,10 +2225,12 @@ func UpdateIssueContent(ctx *context.Context) {
|
||||
}
|
||||
|
||||
content, err := markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, issue.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -3133,10 +3136,12 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||
}
|
||||
|
||||
content, err := markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, comment.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
|
||||
@@ -81,10 +81,12 @@ func Milestones(ctx *context.Context) {
|
||||
}
|
||||
for _, m := range miles {
|
||||
m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, m.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -275,10 +277,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
|
||||
}
|
||||
|
||||
milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, milestone.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
|
||||
@@ -86,10 +86,12 @@ func Projects(ctx *context.Context) {
|
||||
|
||||
for i := range projects {
|
||||
projects[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, projects[i].Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -353,10 +355,12 @@ func ViewProject(ctx *context.Context) {
|
||||
ctx.Data["LinkedPRs"] = linkedPrsMap
|
||||
|
||||
project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, project.Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
|
||||
+42
-26
@@ -637,7 +637,15 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
||||
if pb != nil && pb.EnableStatusCheck {
|
||||
ctx.Data["is_context_required"] = func(context string) bool {
|
||||
for _, c := range pb.StatusCheckContexts {
|
||||
if gp, err := glob.Compile(c); err == nil && gp.Match(context) {
|
||||
if c == context {
|
||||
return true
|
||||
}
|
||||
if gp, err := glob.Compile(c); err != nil {
|
||||
// All newly created status_check_contexts are checked to ensure they are valid glob expressions before being stored in the database.
|
||||
// But some old status_check_context created before glob was introduced may be invalid glob expressions.
|
||||
// So log the error here for debugging.
|
||||
log.Error("compile glob %q: %v", c, err)
|
||||
} else if gp.Match(context) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1125,30 +1133,28 @@ func MergePullRequest(ctx *context.Context) {
|
||||
switch {
|
||||
case errors.Is(err, pull_service.ErrIsClosed):
|
||||
if issue.IsPull {
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.is_closed"))
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
|
||||
ctx.JSONError(ctx.Tr("repo.issues.closed_title"))
|
||||
}
|
||||
case errors.Is(err, pull_service.ErrUserNotAllowedToMerge):
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.update_not_allowed"))
|
||||
case errors.Is(err, pull_service.ErrHasMerged):
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.has_merged"))
|
||||
case errors.Is(err, pull_service.ErrIsWorkInProgress):
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip"))
|
||||
case errors.Is(err, pull_service.ErrNotMergableState):
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
|
||||
case models.IsErrDisallowedToMerge(err):
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
|
||||
case asymkey_service.IsErrWontSign(err):
|
||||
ctx.Flash.Error(err.Error()) // has no translation ...
|
||||
ctx.JSONError(err.Error()) // has no translation ...
|
||||
case errors.Is(err, pull_service.ErrDependenciesLeft):
|
||||
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
|
||||
ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
|
||||
default:
|
||||
ctx.ServerError("WebCheck", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(issue.Link())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1156,18 +1162,18 @@ func MergePullRequest(ctx *context.Context) {
|
||||
if manuallyMerged {
|
||||
if err := pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
|
||||
switch {
|
||||
|
||||
case models.IsErrInvalidMergeStyle(err):
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
|
||||
case strings.Contains(err.Error(), "Wrong commit ID"):
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.wrong_commit_id"))
|
||||
default:
|
||||
ctx.ServerError("MergedManually", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(issue.Link())
|
||||
ctx.JSONRedirect(issue.Link())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1197,15 +1203,14 @@ func MergePullRequest(ctx *context.Context) {
|
||||
} else if scheduled {
|
||||
// nothing more to do ...
|
||||
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
|
||||
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
|
||||
ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
|
||||
if models.IsErrInvalidMergeStyle(err) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
|
||||
ctx.Redirect(issue.Link())
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
|
||||
} else if models.IsErrMergeConflicts(err) {
|
||||
conflictError := err.(models.ErrMergeConflicts)
|
||||
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
|
||||
@@ -1218,7 +1223,7 @@ func MergePullRequest(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Flash.Error(flashError)
|
||||
ctx.Redirect(issue.Link())
|
||||
ctx.JSONRedirect(issue.Link())
|
||||
} else if models.IsErrRebaseConflicts(err) {
|
||||
conflictError := err.(models.ErrRebaseConflicts)
|
||||
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
|
||||
@@ -1262,7 +1267,7 @@ func MergePullRequest(ctx *context.Context) {
|
||||
}
|
||||
ctx.Flash.Error(flashError)
|
||||
}
|
||||
ctx.Redirect(issue.Link())
|
||||
ctx.JSONRedirect(issue.Link())
|
||||
} else {
|
||||
ctx.ServerError("Merge", err)
|
||||
}
|
||||
@@ -1271,7 +1276,7 @@ func MergePullRequest(ctx *context.Context) {
|
||||
log.Trace("Pull request merged: %d", pr.ID)
|
||||
|
||||
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
|
||||
ctx.ServerError("CreateOrStopIssueStopwatch", err)
|
||||
ctx.ServerError("stopTimerIfAvailable", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1285,7 +1290,7 @@ func MergePullRequest(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
if exist {
|
||||
ctx.Redirect(issue.Link())
|
||||
ctx.JSONRedirect(issue.Link())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1303,7 +1308,7 @@ func MergePullRequest(ctx *context.Context) {
|
||||
deleteBranch(ctx, pr, headRepo)
|
||||
}
|
||||
|
||||
ctx.Redirect(issue.Link())
|
||||
ctx.JSONRedirect(issue.Link())
|
||||
}
|
||||
|
||||
// CancelAutoMergePullRequest cancels a scheduled pr
|
||||
@@ -1363,7 +1368,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
|
||||
labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, true)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -1441,6 +1446,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if projectID > 0 {
|
||||
if !ctx.Repo.CanWrite(unit.TypeProjects) {
|
||||
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 {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
|
||||
ctx.JSONRedirect(pullIssue.Link())
|
||||
}
|
||||
|
||||
@@ -175,10 +175,12 @@ func releasesOrTags(ctx *context.Context, isTagList bool) {
|
||||
}
|
||||
|
||||
r.Note, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, r.Note)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -281,10 +283,12 @@ func SingleRelease(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
release.Note, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
}, release.Note)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
|
||||
@@ -57,16 +57,15 @@ func RenderFile(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
treeLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
if ctx.Repo.TreePath != "" {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
|
||||
err = markup.Render(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
RelativePath: ctx.Repo.TreePath,
|
||||
URLPrefix: path.Dir(treeLink),
|
||||
Ctx: ctx,
|
||||
RelativePath: ctx.Repo.TreePath,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
InStandalonePage: true,
|
||||
|
||||
@@ -6,13 +6,12 @@ package setting
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
||||
// SetDefaultBranchPost set default branch
|
||||
@@ -35,23 +34,14 @@ func SetDefaultBranchPost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
branch := ctx.FormString("branch")
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(branch) {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
} else if repo.DefaultBranch != branch {
|
||||
repo.DefaultBranch = branch
|
||||
if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
|
||||
if !git.IsErrUnsupportedVersion(err) {
|
||||
ctx.ServerError("SetDefaultBranch", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
|
||||
if err := repo_service.SetRepoDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, branch); err != nil {
|
||||
switch {
|
||||
case git_model.IsErrBranchNotExist(err):
|
||||
ctx.Status(http.StatusNotFound)
|
||||
default:
|
||||
ctx.ServerError("SetDefaultBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
notify_service.ChangeDefaultBranch(ctx, repo)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
@@ -594,7 +594,7 @@ func SettingsPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := repo_model.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil {
|
||||
if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
|
||||
ctx.ServerError("UpdateRepositoryUnits", err)
|
||||
return
|
||||
}
|
||||
|
||||
+34
-24
@@ -158,7 +158,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
|
||||
return "", readmeFile, nil
|
||||
}
|
||||
|
||||
func renderDirectory(ctx *context.Context, treeLink string) {
|
||||
func renderDirectory(ctx *context.Context) {
|
||||
entries := renderDirectoryFiles(ctx, 1*time.Second)
|
||||
if ctx.Written() {
|
||||
return
|
||||
@@ -175,7 +175,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
||||
return
|
||||
}
|
||||
|
||||
renderReadmeFile(ctx, subfolder, readmeFile, treeLink)
|
||||
renderReadmeFile(ctx, subfolder, readmeFile)
|
||||
}
|
||||
|
||||
// localizedExtensions prepends the provided language code with and without a
|
||||
@@ -259,7 +259,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
|
||||
return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
|
||||
}
|
||||
|
||||
func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry, readmeTreelink string) {
|
||||
func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
|
||||
target := readmeFile
|
||||
if readmeFile != nil && readmeFile.IsLink() {
|
||||
target, _ = readmeFile.FollowLinks()
|
||||
@@ -312,9 +312,13 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.Name()), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
|
||||
URLPrefix: path.Join(readmeTreelink, subfolder),
|
||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||
TreePath: path.Join(ctx.Repo.TreePath, subfolder),
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
}, rd)
|
||||
if err != nil {
|
||||
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
||||
@@ -333,7 +337,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
|
||||
}
|
||||
}
|
||||
|
||||
func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
|
||||
func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
ctx.Data["IsViewFile"] = true
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
blob := entry.Blob()
|
||||
@@ -347,7 +351,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||
ctx.Data["FileIsSymlink"] = entry.IsLink()
|
||||
ctx.Data["FileName"] = blob.Name()
|
||||
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
|
||||
if ctx.Repo.TreePath == ".editorconfig" {
|
||||
_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
|
||||
@@ -475,9 +479,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
||||
Ctx: ctx,
|
||||
Type: markupType,
|
||||
RelativePath: ctx.Repo.TreePath,
|
||||
URLPrefix: path.Dir(treeLink),
|
||||
Metas: metas,
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
||||
},
|
||||
Metas: metas,
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
}, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
@@ -576,9 +584,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
RelativePath: ctx.Repo.TreePath,
|
||||
URLPrefix: path.Dir(treeLink),
|
||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
||||
},
|
||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
}, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
@@ -936,14 +948,6 @@ func renderCode(ctx *context.Context) {
|
||||
}
|
||||
ctx.Data["Title"] = title
|
||||
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
treeLink := branchLink
|
||||
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
|
||||
|
||||
if len(ctx.Repo.TreePath) > 0 {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
|
||||
// Get Topics of this repo
|
||||
renderRepoTopics(ctx)
|
||||
if ctx.Written() {
|
||||
@@ -968,9 +972,9 @@ func renderCode(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
renderDirectory(ctx, treeLink)
|
||||
renderDirectory(ctx)
|
||||
} else {
|
||||
renderFile(ctx, entry, treeLink, rawLink)
|
||||
renderFile(ctx, entry)
|
||||
}
|
||||
if ctx.Written() {
|
||||
return
|
||||
@@ -1011,6 +1015,12 @@ func renderCode(ctx *context.Context) {
|
||||
}
|
||||
|
||||
ctx.Data["Paths"] = paths
|
||||
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
treeLink := branchLink
|
||||
if len(ctx.Repo.TreePath) > 0 {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
ctx.Data["TreeLink"] = treeLink
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["BranchLink"] = branchLink
|
||||
|
||||
@@ -238,10 +238,12 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||
}
|
||||
|
||||
rctx := &markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
|
||||
IsWiki: true,
|
||||
Ctx: ctx,
|
||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
},
|
||||
IsWiki: true,
|
||||
}
|
||||
buf := &strings.Builder{}
|
||||
|
||||
|
||||
@@ -44,10 +44,8 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
|
||||
|
||||
if len(ctx.ContextUser.Description) != 0 {
|
||||
content, err := markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
GitRepo: ctx.Repo.GitRepo,
|
||||
Ctx: ctx,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
Ctx: ctx,
|
||||
}, ctx.ContextUser.Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@@ -84,7 +82,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func FindUserProfileReadme(ctx *context.Context) (profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) {
|
||||
func FindUserProfileReadme(ctx *context.Context) (profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) {
|
||||
profileDbRepo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile")
|
||||
if err == nil && !profileDbRepo.IsEmpty && !profileDbRepo.IsPrivate {
|
||||
if profileGitRepo, err = git.OpenRepository(ctx, profileDbRepo.RepoPath()); err != nil {
|
||||
@@ -97,7 +95,7 @@ func FindUserProfileReadme(ctx *context.Context) (profileGitRepo *git.Repository
|
||||
}
|
||||
}
|
||||
}
|
||||
return profileGitRepo, profileReadmeBlob, func() {
|
||||
return profileDbRepo, profileGitRepo, profileReadmeBlob, func() {
|
||||
if profileGitRepo != nil {
|
||||
_ = profileGitRepo.Close()
|
||||
}
|
||||
@@ -107,7 +105,7 @@ func FindUserProfileReadme(ctx *context.Context) (profileGitRepo *git.Repository
|
||||
func RenderUserHeader(ctx *context.Context) {
|
||||
prepareContextForCommonProfile(ctx)
|
||||
|
||||
_, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx)
|
||||
_, _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx)
|
||||
defer profileClose()
|
||||
ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil
|
||||
}
|
||||
|
||||
@@ -246,9 +246,11 @@ func Milestones(ctx *context.Context) {
|
||||
}
|
||||
|
||||
milestones[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
URLPrefix: milestones[i].Repo.Link(),
|
||||
Metas: milestones[i].Repo.ComposeMetas(),
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: milestones[i].Repo.Link(),
|
||||
},
|
||||
Metas: milestones[i].Repo.ComposeMetas(),
|
||||
Ctx: ctx,
|
||||
}, milestones[i].Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
|
||||
@@ -7,6 +7,7 @@ package user
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
@@ -64,17 +65,17 @@ func userProfile(ctx *context.Context) {
|
||||
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
|
||||
}
|
||||
|
||||
profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx)
|
||||
profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx)
|
||||
defer profileClose()
|
||||
|
||||
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
|
||||
prepareUserProfileTabData(ctx, showPrivate, profileGitRepo, profileReadmeBlob)
|
||||
prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileGitRepo, profileReadmeBlob)
|
||||
// call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing
|
||||
shared_user.PrepareContextForProfileBigAvatar(ctx)
|
||||
ctx.HTML(http.StatusOK, tplProfile)
|
||||
}
|
||||
|
||||
func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGitRepo *git.Repository, profileReadme *git.Blob) {
|
||||
func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadme *git.Blob) {
|
||||
// if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page
|
||||
// if there is not a profile readme, the overview tab should be treated as the repositories tab
|
||||
tab := ctx.FormString("tab")
|
||||
@@ -236,7 +237,16 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGi
|
||||
if profileContent, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
GitRepo: profileGitRepo,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
Links: markup.Links{
|
||||
// Give the repo link to the markdown render for the full link of media element.
|
||||
// the media link usually be like /[user]/[repoName]/media/branch/[branchName],
|
||||
// Eg. /Tom/.profile/media/branch/main
|
||||
// The branch shown on the profile page is the default branch, this need to be in sync with doc, see:
|
||||
// https://docs.gitea.com/usage/profile-readme
|
||||
Base: profileDbRepo.Link(),
|
||||
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
||||
},
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
}, bytes); err != nil {
|
||||
log.Error("failed to RenderString: %v", err)
|
||||
} else {
|
||||
|
||||
@@ -246,6 +246,13 @@ func DeleteAccount(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// admin should not delete themself
|
||||
if ctx.Doer.IsAdmin {
|
||||
ctx.Flash.Error(ctx.Tr("form.admin_cannot_delete_self"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
return
|
||||
}
|
||||
|
||||
if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil {
|
||||
switch {
|
||||
case models.IsErrUserOwnRepos(err):
|
||||
@@ -257,6 +264,9 @@ func DeleteAccount(ctx *context.Context) {
|
||||
case models.IsErrUserOwnPackages(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.still_own_packages"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
case models.IsErrDeleteLastAdminUser(err):
|
||||
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
default:
|
||||
ctx.ServerError("DeleteUser", err)
|
||||
}
|
||||
|
||||
+31
-17
@@ -59,13 +59,12 @@ const (
|
||||
GzipMinSize = 1400
|
||||
)
|
||||
|
||||
// CorsHandler return a http handler who set CORS options if enabled by config
|
||||
func CorsHandler() func(next http.Handler) http.Handler {
|
||||
// optionsCorsHandler return a http handler which sets CORS options if enabled by config, it blocks non-CORS OPTIONS requests.
|
||||
func optionsCorsHandler() func(next http.Handler) http.Handler {
|
||||
var corsHandler func(next http.Handler) http.Handler
|
||||
if setting.CORSConfig.Enabled {
|
||||
return cors.Handler(cors.Options{
|
||||
// Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
|
||||
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
||||
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
|
||||
corsHandler = cors.Handler(cors.Options{
|
||||
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
||||
AllowedMethods: setting.CORSConfig.Methods,
|
||||
AllowCredentials: setting.CORSConfig.AllowCredentials,
|
||||
AllowedHeaders: setting.CORSConfig.Headers,
|
||||
@@ -74,7 +73,23 @@ func CorsHandler() func(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return next
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodOptions {
|
||||
if corsHandler != nil && r.Header.Get("Access-Control-Request-Method") != "" {
|
||||
corsHandler(next).ServeHTTP(w, r)
|
||||
} else {
|
||||
// it should explicitly deny OPTIONS requests if CORS handler is not executed, to avoid the next GET/POST handler being incorrectly called by the OPTIONS request
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
return
|
||||
}
|
||||
// for non-OPTIONS requests, call the CORS handler to add some related headers like "Vary"
|
||||
if corsHandler != nil {
|
||||
corsHandler(next).ServeHTTP(w, r)
|
||||
} else {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +232,7 @@ func Routes() *web.Route {
|
||||
routes := web.NewRoute()
|
||||
|
||||
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
|
||||
routes.Methods("GET, HEAD", "/assets/*", CorsHandler(), public.FileHandlerFunc())
|
||||
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", optionsCorsHandler(), public.FileHandlerFunc())
|
||||
routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
|
||||
routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
|
||||
routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
|
||||
@@ -457,8 +472,8 @@ func registerRoutes(m *web.Route) {
|
||||
m.Get("/change-password", func(ctx *context.Context) {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
})
|
||||
m.Any("/*", CorsHandler(), public.FileHandlerFunc())
|
||||
}, CorsHandler())
|
||||
m.Methods("GET, HEAD", "/*", public.FileHandlerFunc())
|
||||
}, optionsCorsHandler())
|
||||
|
||||
m.Group("/explore", func() {
|
||||
m.Get("", func(ctx *context.Context) {
|
||||
@@ -531,12 +546,11 @@ func registerRoutes(m *web.Route) {
|
||||
// TODO manage redirection
|
||||
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
||||
}, ignSignInAndCsrf, reqSignIn)
|
||||
m.Get("/login/oauth/userinfo", ignSignInAndCsrf, auth.InfoOAuth)
|
||||
m.Options("/login/oauth/access_token", CorsHandler(), misc.DummyBadRequest)
|
||||
m.Post("/login/oauth/access_token", CorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
|
||||
m.Get("/login/oauth/keys", ignSignInAndCsrf, auth.OIDCKeys)
|
||||
m.Options("/login/oauth/introspect", CorsHandler(), misc.DummyBadRequest)
|
||||
m.Post("/login/oauth/introspect", CorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
|
||||
|
||||
m.Methods("GET, OPTIONS", "/login/oauth/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth)
|
||||
m.Methods("POST, OPTIONS", "/login/oauth/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
|
||||
m.Methods("GET, OPTIONS", "/login/oauth/keys", optionsCorsHandler(), ignSignInAndCsrf, auth.OIDCKeys)
|
||||
m.Methods("POST, OPTIONS", "/login/oauth/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
|
||||
|
||||
m.Group("/user/settings", func() {
|
||||
m.Get("", user_setting.Profile)
|
||||
@@ -766,7 +780,7 @@ func registerRoutes(m *web.Route) {
|
||||
|
||||
m.Group("", func() {
|
||||
m.Get("/{username}", user.UsernameSubRoute)
|
||||
m.Get("/attachments/{uuid}", repo.GetAttachment)
|
||||
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment)
|
||||
}, ignSignIn)
|
||||
|
||||
m.Post("/{username}", reqSignIn, context_service.UserAssignmentWeb(), user.Action)
|
||||
|
||||
@@ -114,6 +114,9 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
return nil
|
||||
}
|
||||
if unit_model.TypeActions.UnitGlobalDisabled() {
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := input.Repo.LoadUnits(ctx); err != nil {
|
||||
@@ -146,7 +149,11 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
|
||||
var detectedWorkflows []*actions_module.DetectedWorkflow
|
||||
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
|
||||
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload)
|
||||
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
|
||||
input.Event,
|
||||
input.Payload,
|
||||
input.Event == webhook_module.HookEventPush && input.Ref == input.Repo.DefaultBranch,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DetectWorkflows: %w", err)
|
||||
}
|
||||
@@ -160,7 +167,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget {
|
||||
if wf.TriggerEvent.Name != actions_module.GithubEventPullRequestTarget {
|
||||
detectedWorkflows = append(detectedWorkflows, wf)
|
||||
}
|
||||
}
|
||||
@@ -173,7 +180,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
||||
}
|
||||
baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload)
|
||||
baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DetectWorkflows: %w", err)
|
||||
}
|
||||
@@ -181,14 +188,14 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RepoPath(), baseCommit.ID)
|
||||
} else {
|
||||
for _, wf := range baseWorkflows {
|
||||
if wf.TriggerEvent == actions_module.GithubEventPullRequestTarget {
|
||||
if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget {
|
||||
detectedWorkflows = append(detectedWorkflows, wf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := handleSchedules(ctx, schedules, commit, input); err != nil {
|
||||
if err := handleSchedules(ctx, schedules, commit, input, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -239,7 +246,7 @@ func handleWorkflows(
|
||||
IsForkPullRequest: isForkPullRequest,
|
||||
Event: input.Event,
|
||||
EventPayload: string(p),
|
||||
TriggerEvent: dwf.TriggerEvent,
|
||||
TriggerEvent: dwf.TriggerEvent.Name,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil {
|
||||
@@ -263,6 +270,7 @@ func handleWorkflows(
|
||||
run.RepoID,
|
||||
run.Ref,
|
||||
run.WorkflowID,
|
||||
run.Event,
|
||||
); err != nil {
|
||||
log.Error("CancelRunningJobs: %v", err)
|
||||
}
|
||||
@@ -373,6 +381,7 @@ func handleSchedules(
|
||||
detectedWorkflows []*actions_module.DetectedWorkflow,
|
||||
commit *git.Commit,
|
||||
input *notifyInput,
|
||||
ref string,
|
||||
) error {
|
||||
branch, err := commit.GetBranchName()
|
||||
if err != nil {
|
||||
@@ -387,8 +396,8 @@ func handleSchedules(
|
||||
log.Error("CountSchedules: %v", err)
|
||||
return err
|
||||
} else if count > 0 {
|
||||
if err := actions_model.DeleteScheduleTaskByRepo(ctx, input.Repo.ID); err != nil {
|
||||
log.Error("DeleteCronTaskByRepo: %v", err)
|
||||
if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
|
||||
log.Error("CleanRepoScheduleTasks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,26 +431,13 @@ func handleSchedules(
|
||||
OwnerID: input.Repo.OwnerID,
|
||||
WorkflowID: dwf.EntryName,
|
||||
TriggerUserID: input.Doer.ID,
|
||||
Ref: input.Ref,
|
||||
Ref: ref,
|
||||
CommitSHA: commit.ID.String(),
|
||||
Event: input.Event,
|
||||
EventPayload: string(p),
|
||||
Specs: schedules,
|
||||
Content: dwf.Content,
|
||||
}
|
||||
|
||||
// cancel running jobs if the event is push
|
||||
if run.Event == webhook_module.HookEventPush {
|
||||
// cancel running jobs of the same workflow
|
||||
if err := actions_model.CancelRunningJobs(
|
||||
ctx,
|
||||
run.RepoID,
|
||||
run.Ref,
|
||||
run.WorkflowID,
|
||||
); err != nil {
|
||||
log.Error("CancelRunningJobs: %v", err)
|
||||
}
|
||||
}
|
||||
crons = append(crons, run)
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ func startTasks(ctx context.Context) error {
|
||||
row.RepoID,
|
||||
row.Schedule.Ref,
|
||||
row.Schedule.WorkflowID,
|
||||
webhook_module.HookEventSchedule,
|
||||
); err != nil {
|
||||
log.Error("CancelRunningJobs: %v", err)
|
||||
}
|
||||
@@ -113,6 +114,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
|
||||
CommitSHA: cron.CommitSHA,
|
||||
Event: cron.Event,
|
||||
EventPayload: cron.EventPayload,
|
||||
TriggerEvent: string(webhook_module.HookEventSchedule),
|
||||
ScheduleID: cron.ID,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
|
||||
+18
-20
@@ -308,40 +308,38 @@ func ToTeam(ctx context.Context, team *organization.Team, loadOrg ...bool) (*api
|
||||
|
||||
// ToTeams convert models.Team list to api.Team list
|
||||
func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]*api.Team, error) {
|
||||
if len(teams) == 0 || teams[0] == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cache := make(map[int64]*api.Organization)
|
||||
apiTeams := make([]*api.Team, len(teams))
|
||||
for i := range teams {
|
||||
if err := teams[i].LoadUnits(ctx); err != nil {
|
||||
apiTeams := make([]*api.Team, 0, len(teams))
|
||||
for _, t := range teams {
|
||||
if err := t.LoadUnits(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiTeams[i] = &api.Team{
|
||||
ID: teams[i].ID,
|
||||
Name: teams[i].Name,
|
||||
Description: teams[i].Description,
|
||||
IncludesAllRepositories: teams[i].IncludesAllRepositories,
|
||||
CanCreateOrgRepo: teams[i].CanCreateOrgRepo,
|
||||
Permission: teams[i].AccessMode.String(),
|
||||
Units: teams[i].GetUnitNames(),
|
||||
UnitsMap: teams[i].GetUnitsMap(),
|
||||
apiTeam := &api.Team{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Description: t.Description,
|
||||
IncludesAllRepositories: t.IncludesAllRepositories,
|
||||
CanCreateOrgRepo: t.CanCreateOrgRepo,
|
||||
Permission: t.AccessMode.String(),
|
||||
Units: t.GetUnitNames(),
|
||||
UnitsMap: t.GetUnitsMap(),
|
||||
}
|
||||
|
||||
if loadOrgs {
|
||||
apiOrg, ok := cache[teams[i].OrgID]
|
||||
apiOrg, ok := cache[t.OrgID]
|
||||
if !ok {
|
||||
org, err := organization.GetOrgByID(ctx, teams[i].OrgID)
|
||||
org, err := organization.GetOrgByID(ctx, t.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiOrg = ToOrganization(ctx, org)
|
||||
cache[teams[i].OrgID] = apiOrg
|
||||
cache[t.OrgID] = apiOrg
|
||||
}
|
||||
apiTeams[i].Organization = apiOrg
|
||||
apiTeam.Organization = apiOrg
|
||||
}
|
||||
|
||||
apiTeams = append(apiTeams, apiTeam)
|
||||
}
|
||||
return apiTeams, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user