Compare commits

...

112 Commits

Author SHA1 Message Date
Lunny Xiao
f4923854f6 Add changelog for 1.21.4 (#28812) 2024-01-17 10:46:50 +08:00
Giteabot
d590607106 Render code block in activity tab (#28816) (#28818)
Backport #28816 by @JakobDev

This is a little bugfix. Inline code is usually rendered in issue
titles, but it is missing in the activity tab.

Before:
![Screenshot 2024-01-16 at 14-20-51
Test](https://github.com/go-gitea/gitea/assets/15185051/383370f3-0fb2-49de-81cc-014e5cf86727)
After:

![grafik](https://github.com/go-gitea/gitea/assets/15185051/83eaf973-ce9a-44ce-beea-2db49fc8bd73)

Co-authored-by: JakobDev <jakobdev@gmx.de>
2024-01-17 01:35:26 +01:00
Giteabot
4746291b08 Use refname:strip-2 instead of refname:short when syncing tags (#28797) (#28811)
Backport #28797 by @lunny

Fix #28694 

Generally, `refname:short` should be equal to `refname:lstrip=2` except
`core.warnAmbiguousRefs is used to select the strict abbreviation mode.`

ref:
https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-01-16 07:33:33 +00:00
KN4CK3R
022552d5b6 Rework markup link rendering (#26745) (#28803)
Backport #26745
Fixes #26548

This PR refactors the rendering of markup links. The old code uses
`strings.Replace` to change some urls while the new code uses more
context to decide which link should be generated.

The added tests should ensure the same output for the old and new
behaviour (besides the bug).

We may need to refactor the rendering a bit more to make it clear how
the different helper methods render the input string. There are lots of
options (resolve links / images / mentions / git hashes / emojis / ...)
but you don't really know what helper uses which options. For example,
we currently support images in the user description which should not be
allowed I think:

<details>
  <summary>Profile</summary>

https://try.gitea.io/KN4CK3R


![grafik](https://github.com/go-gitea/gitea/assets/1666336/109ae422-496d-4200-b52e-b3a528f553e5)

</details>
2024-01-16 02:13:29 +00:00
Giteabot
376fa0d8c4 Forbid removing the last admin user (#28337) (#28793)
Backport #28337 by @yp05327

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-01-16 01:51:46 +00:00
Giteabot
be541d9877 Fix links in issue card (#28806) (#28807)
Backport #28806 by @denyskon

Fixes_
https://github.com/go-gitea/gitea/issues/23318#issuecomment-1611086747

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2024-01-16 09:30:07 +08:00
KN4CK3R
ae99233db0 Fix GetCommitStatuses (#28787) (#28804)
Backport #28787

Replaces #28802
2024-01-15 19:30:12 +01:00
Giteabot
cbf366643b Use correct has error check for internal responses (#28796) (#28798)
Backport #28796 by @wxiaoguang

`resp != nil` doesn't mean the request really succeeded. Add a comment
for requestJSONResp to clarify the behavior.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-01-15 13:13:35 +01:00
Giteabot
df694f6a7d Fix nil pointer panic when exec some gitea cli command (#28791) (#28795)
Backport #28791 by @yp05327

panic:

![image](https://github.com/go-gitea/gitea/assets/18380374/7fcde2ad-1d42-4b60-b120-3b60a8926e8e)

After:

![image](https://github.com/go-gitea/gitea/assets/18380374/49d9f0ca-e590-4a35-8ca2-1317d1b7c939)

Co-authored-by: yp05327 <576951401@qq.com>
2024-01-15 08:05:30 +00:00
Giteabot
84282c608c Fix when private user following user, private user will not be counted in his own view (#28037) (#28792)
Backport #28037 by @yp05327

Doer: asdasasdasasdasasdasasdasasdasasdasasdas (private user)
Followed: TestUser (public user)

Before:
(From doer's view)

![image](https://github.com/go-gitea/gitea/assets/18380374/9ba16b3b-068c-43c5-a3dd-e3343b5b32f2)
(From followed user's view, can see doer)

![image](https://github.com/go-gitea/gitea/assets/18380374/dfd1b564-d689-4393-b3d3-1e6bf52c94ba)

After:
(From doer's view)

![image](https://github.com/go-gitea/gitea/assets/18380374/1c85c1d1-c9f7-40c8-948c-145f7cae9a04)

Co-authored-by: yp05327 <576951401@qq.com>
2024-01-15 15:07:10 +08:00
Chongyi Zheng
d1db2b7251 Update github.com/cloudflare/circl (#28789) (#28790)
Backport #28789

cloudflare/circl: https://github.com/advisories/GHSA-9763-4f94-gfch

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 13:19:18 +08:00
Giteabot
6493085aee Speed up loading the dashboard on mysql/mariadb (#28546) (#28784)
Backport #28546 by @lunny

Fixes #28155

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-01-15 02:01:25 +00:00
Giteabot
fbf29f29b5 Modernize merge button (#28140) (#28786)
Backport #28140 by @earl-warren

- Make use of the `form-fetch-action` for the merge button, which will
automatically prevent the action from happening multiple times and show
a nice loading indicator as user feedback while the merge request is
being processed by the server.
- Adjust the merge PR code to JSON response as this is required for the
`form-fetch-action` functionality.
- Resolves https://codeberg.org/forgejo/forgejo/issues/774
- Likely resolves the cause of
https://codeberg.org/forgejo/forgejo/issues/1688#issuecomment-1313044

(cherry picked from commit 4ec64c19507caefff7ddaad722b1b5792b97cc5a)

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2024-01-15 09:40:52 +08:00
Lunny Xiao
6e29242ebb Fix schedule tasks bugs (#28691) (#28780)
Fix #28157
Backport #28691 

This PR fix the possible bugs about actions schedule.

- Move `UpdateRepositoryUnit` and `SetRepoDefaultBranch` from models to
service layer
- Remove schedules plan from database and cancel waiting & running
schedules tasks in this repository when actions unit has been disabled
or global disabled.
- Remove schedules plan from database and cancel waiting & running
schedules tasks in this repository when default branch changed.
2024-01-14 23:54:22 +01:00
Jack Hay
56e722f825 Require token for GET subscription endpoint (#28765) (#28778)
Backport #28765 for 1.21
2024-01-13 02:01:15 +00:00
Giteabot
80e564087d Assign pull request to project during creation (#28227) (#28775)
Backport #28227 by @denyskon

When creating a pull request, allow directly assigning it to a project,
as it is already possible for issues.

After:


![grafik](https://github.com/go-gitea/gitea/assets/47871822/01dc2b3d-d56a-4053-b2fc-138725d7633a)

---------

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
Co-authored-by: delvh <dev.lh@web.de>
2024-01-12 21:53:16 +00:00
Giteabot
571822b6ec Fix issue dependencies (#27736) (#28776)
Backport #27736 by @lng2020

Fix #27722 
Fix #27357
Fix #25837 
Fix #28732 
1. Fix the typo `BlockingByDependenciesNotPermitted`, which causes the
`not permitted message` not to show. The correct one is `Blocking` or
`BlockedBy`
2. Rewrite the perm check. The perm check uses a very tricky way to
avoid duplicate checks for a slice of issues, which is confusing. In
fact, it's also the reason causing the bug. It uses `lastRepoID` and
`lastPerm` to avoid duplicate checks, but forgets to assign the
`lastPerm` at the end of the code block. So I rewrote this to avoid this
trick.
![I U1AT{GNFY3
1HZ`6L{(2L](https://github.com/go-gitea/gitea/assets/70063547/79acd02a-a567-4316-ae0d-11c6461becf1)
3. It also reuses the `blocks` slice, which is even more confusing. So I
rewrote this too.

![UARFPXRGGZQFB7J$2`R}5_R](https://github.com/go-gitea/gitea/assets/70063547/f21cff0f-d9ac-4ce4-ae4d-adffc98ecd99)

Co-authored-by: Nanguan Lin <70063547+lng2020@users.noreply.github.com>
2024-01-12 23:29:22 +02:00
Giteabot
2a0fbe23b8 Fix button size in "attached header right" (#28770) (#28774)
Backport #28770 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-01-12 15:04:19 +00:00
Giteabot
95901a99c0 Fix convert.ToTeams on empty input (#28426) (#28767)
Backport #28426 by @KN4CK3R

Fixes #28420

Don't return `nil` if the input was empty.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-01-12 07:12:18 +00:00
Giteabot
cb33623bb6 Require token for GET subscription endpoint (#28765) (#28768)
Backport #28765 by @jackHay22

Fixes  #28756

## Changes
- Require and check API token for `GET
/repos/{owner}/{repo}/subscription` in order to populate `ctx.Doer`.

Co-authored-by: Jack Hay <jack@allspice.io>
2024-01-12 14:51:27 +08:00
Giteabot
9f0c709637 Show description as tooltip instead of title for labels (#28754) (#28766)
Backport #28754 by @delvh

Follow GitHubs behavior of showing the label description as a tooltip
instead of the browser native title.

## Before

![grafik](https://github.com/go-gitea/gitea/assets/51889757/70448327-467b-4bee-b799-40a442a5ce16)


## After

![grafik](https://github.com/go-gitea/gitea/assets/51889757/abe7d700-148b-4cef-a487-6b0f8f20b212)

Co-authored-by: delvh <dev.lh@web.de>
2024-01-12 10:45:07 +08:00
Giteabot
5e9fd0ab5e Hide code related setting options in repository when code unit is disabled (#28631) (#28749)
Backport #28631 by @lunny

Since #20805, code can be hidden.
However, the related settings are still shown even though they don't
have any meaning then.


https://github.com/go-gitea/gitea/assets/81045/5fdee54d-ac81-418a-82f7-eadff048cedd

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-01-11 04:31:24 +00:00
Giteabot
f220f4231a Add -F to commit search to treat keywords as strings (#28744) (#28748)
Backport #28744 by @me-heer

Fixes #28269

The [default
behavior](https://git-scm.com/docs/git-log#Documentation/git-log.txt---basic-regexp)
of --grep in git log is to interpret the keyword as a regular
expression. This causes the search to fail in the cases where the search
keyword contains a `[`, since `[` is a special character used in grep.

If we want our keywords to be interpreted as 'strings', we should use
[-F
flag](https://git-scm.com/docs/git-log#Documentation/git-log.txt---basic-regexp).

Co-authored-by: Mihir Joshi <mihir67mj@gmail.com>
2024-01-10 21:29:01 +01:00
Giteabot
bce27d0a31 Concatenate error in checkIfPRContentChanged (#28731) (#28737)
Backport #28731 by @earl-warren

- If there's a error with the Git command in `checkIfPRContentChanged`
the stderr wasn't concatendated to the error, which results in still not
knowing why an error happend.
- Adds concatenation for stderr to the returned error.
- Ref: https://codeberg.org/forgejo/forgejo/issues/2077

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2024-01-09 14:06:12 +00:00
Giteabot
12f418a7e8 Add download attribute to release attachments (#28739) (#28740)
Backport #28739 by @JakobDev

Fixes #28736

Co-authored-by: JakobDev <jakobdev@gmx.de>
2024-01-09 21:18:13 +08:00
wxiaoguang
9865aa2394 Suggest to use Type=simple for systemd service (#28717) (#28722)
Backport #28717 (only the gitea.service sample)

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-01-07 16:42:43 +01:00
Giteabot
def178ce32 Fix incorrect URL for "Reference in New Issue" (#28716) (#28723)
Backport #28716 by wxiaoguang

Gitea prefers to use relative URLs in code (to make multiple domain work
for some users)

So it needs to use `toAbsoluteUrl` to generate a full URL when click
"Reference in New Issues"

And add some comments in the test code

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-01-07 23:40:29 +08:00
Giteabot
2399b4d483 Avoid unnecessary 500 panic when a commit doesn't exist (#28719) (#28721)
Backport #28719 by wxiaoguang

In #26851, it assumed that `Commit` always exists when
`PageIsDiff==true`.

But for a 404 page, the `Commit` doesn't exist, so the following code
would cause panic because nil value can't be passed as string parameter
to `IsMultilineCommitMessage(string)` (or the StringUtils.Cut in later
PRs)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-01-07 10:40:30 +00:00
Giteabot
ad2cb9863c Improve frontend guideline (#28711) (#28713) 2024-01-06 12:30:36 -05:00
Giteabot
7f833d8f71 Fix panic when parsing empty pgsql host (#28708) (#28709)
Backport #28708 by wxiaoguang

Regression of #27723
Fix #28705

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-01-06 20:05:49 +08:00
Giteabot
e67c819cf4 Fix wrapping of label list (#28684) (#28688)
Backport #28684 by @denyskon

Before:

![grafik](https://github.com/go-gitea/gitea/assets/47871822/2fbd7ef2-22ad-4515-9c66-81c29bfbb7a3)

After:

![grafik](https://github.com/go-gitea/gitea/assets/47871822/df86d1ae-03db-4543-834c-761859c367be)

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2024-01-04 10:21:35 +08:00
wxiaoguang
83457805bb Make template DateTime show proper tooltip (#28677) (#28683)
Backport #28677
2024-01-03 18:41:03 +08:00
Giteabot
3c79315cf2 Fix: system webhooks API bug (#28531) (#28666)
Backport #28531 by @pulltheflower

- Fix the bug about admin/hooks API that `GET /admin/hooks` can only
fetch system_hooks, `POST /admin/hooks` can only create default_hooks.

Co-authored-by: vincent <38434877+pulltheflower@users.noreply.github.com>
2023-12-31 12:53:09 +00:00
Giteabot
3e1bd61000 Fix alpine package files are not rebuilt (#28638) (#28665)
Backport #28638 by @lng2020

I noticed the `BuildAllRepositoryFiles` function under the Alpine folder
is unused and I thought it was a bug.
But I'm not sure about this. Was it on purpose?

Co-authored-by: Nanguan Lin <70063547+lng2020@users.noreply.github.com>
2023-12-31 06:18:26 +00:00
Giteabot
18da3f8483 Upgrade xorm to new version which supported update join for all supported databases (#28590) (#28668)
Backport #28590 by @lunny

Fix https://github.com/go-gitea/gitea/pull/28547#issuecomment-1867740842

Since https://gitea.com/xorm/xorm/pulls/2383 merged, xorm now supports
UPDATE JOIN.
To keep consistent from different databases, xorm use
`engine.Join().Update`, but the actural generated SQL are different
between different databases.

For MySQL, it's `UPDATE talbe1 JOIN table2 ON join_conditions SET xxx
Where xxx`.

For MSSQL, it's `UPDATE table1 SET xxx FROM TABLE1, TABLE2 WHERE
join_conditions`.

For SQLITE per https://www.sqlite.org/lang_update.html, sqlite support
`UPDATE table1 SET xxx FROM table2 WHERE join conditions` from
3.33.0(2020-8-14).

POSTGRES is the same as SQLITE.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-31 13:57:36 +08:00
wxiaoguang
2165729d16 Avoid cycle-redirecting user/login page (#28636) (#28658)
Backport #28636

Fix #28231, and remove some unused code.
2023-12-30 20:50:08 +08:00
Giteabot
683b95f0da fix empty ref for cron workflow runs (#28640) (#28647)
Backport #28640 by @denyskon

Fix #27678 
Please see
https://github.com/go-gitea/gitea/issues/27678#issuecomment-1871445853
for details.

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2023-12-29 09:08:16 +00:00
Lunny Xiao
ff565a787f Remove unnecessary syncbranchToDB with tests (#28624) (#28629)
Replace #28625

Backport #28624 by lunny

#28361 introduced `syncBranchToDB` in `CreateNewBranchFromCommit`. This
PR will revert the change because it's unnecessary. Every push will
already be checked by `syncBranchToDB`.
This PR also created a test to ensure it's right.
2023-12-29 08:47:02 +00:00
Giteabot
f7cca2a290 Improve document for ARTIFACT_RETENTION_DAYS (#28646) (#28648)
Backport #28646 by wxiaoguang

Follow #28626

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-29 15:39:59 +08:00
Giteabot
373e78895e switch destination directory for apt signing keys (#28639) (#28642)
Backport #28639 by @denyskon

According to [Debian
docs](https://wiki.debian.org/DebianRepository/UseThirdParty):

> The certificate MUST NOT be placed in /etc/apt/trusted.gpg.d or loaded
by apt-key add.
> ...
> If future updates to the certificate will be managed by an apt/dpkg
package as recommended below, then it SHOULD be downloaded into
/usr/share/keyrings using the same filename that will be provided by the
package. If it will be managed locally , it SHOULD be downloaded into
/etc/apt/keyrings instead.
> ...
> A sources.list entry SHOULD have the signed-by option set.

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2023-12-29 05:44:47 +00:00
wxiaoguang
8ec232817c Improve 1.21 document for Database Preparation (#28643) (#28644)
Backport #28643

Fix #28247
2023-12-29 13:05:12 +08:00
Giteabot
a5c7ac9980 Extend description for ARTIFACT_RETENTION_DAYS (#28626) (#28630)
Backport #28626 by @hakito

Make it clear that this value is just a default value and that every
artifact can have it's own value.

Co-authored-by: Gerd Katzenbeisser <hakito@users.noreply.github.com>
2023-12-28 18:21:46 +08:00
Giteabot
bf983735fd Use known issue IID to generate new PR index number when migrating from GitLab (#28616) (#28618)
Backport #28616 by wxiaoguang

Fix #13884

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-27 09:59:29 +08:00
wxiaoguang
7a2786ca6c Refactor CORS handler (#28587) (#28611)
Backport #28587, the only conflict is the test file.

The CORS code has been unmaintained for long time, and the behavior is
not correct.

This PR tries to improve it. The key point is written as comment in
code. And add more tests.

Fix #28515
Fix #27642
Fix #17098
2023-12-25 21:01:24 +08:00
Giteabot
b2588338f0 Revert "improve possible performance bottleneck (#28547)" (#28593) (#28608)
Backport #28593 by @lunny

This reverts commit b35d3fddfa.

This is totally wrong. I think `Update join` hasn't been supported well
by xorm.

I just revert the PR and will try to send another one.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-25 08:01:11 +00:00
Giteabot
8a46a6417e Fix the scroll behavior for emoji/mention list (#28597) (#28601)
Backport #28597 by wxiaoguang

Fix #28595 by https://github.com/github/combobox-nav/pull/79 (combobox-nav v2.3.1)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-25 07:02:09 +00:00
Giteabot
5b104a5533 Fix flex container width (#28603) (#28605)
Backport #28603 by wxiaoguang

Fix #28489

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-24 22:44:03 +08:00
Giteabot
f2add36a29 Include heap pprof in diagnosis report to help debugging memory leaks (#28596) (#28599) 2023-12-24 09:18:43 +08:00
Giteabot
564068aa99 Fix wrong due date rendering in issue list page (#28588) (#28591)
Backport #28588 by @yardenshoham

It included the hours, minutes, and seconds. By removing these, the date
renders correctly.

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
Co-authored-by: Yarden Shoham <git@yardenshoham.com>
2023-12-22 17:25:43 +00:00
Giteabot
6a559ad634 Fix status_check_contexts matching bug (#28582) (#28589)
Backport #28582 by @Zettat123

Fix #28570
Follow #24633

---
Copied from
https://github.com/go-gitea/gitea/issues/28570#issuecomment-1867327999

The feature introduced in #24633 should be compatible with
`status_check_contexts`. However, if one or more of
`status_check_contexts` is not a legal glob expressions, `glob.Compile`
will fail and the contexts cannot match.


21229ed2c8/routers/web/repo/pull.go (L653-L663)

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-12-22 23:32:22 +08:00
Giteabot
4dd39eb54a Fix 405 method not allowed CORS / OIDC (#28583) (#28586)
Backport #28583 by @morphelinho

Follow #28184
Follow #28515

Fix problem with 405 method not allowed for CORS wrt OIDC

Co-authored-by: morphelinho <morphelinho@users.noreply.github.com>
2023-12-22 20:55:43 +08:00
Giteabot
acc8100d47 Fix 500 error of searching commits (#28576) (#28579)
Backport #28576 by wxiaoguang

Regression of #28454 . Now the string is escaped HTML, so it doesn't
need `| Safe`.

Fix #28575

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-22 02:29:28 +00:00
Giteabot
1a3803effd improve possible performance bottleneck (#28547) (#28578)
Backport #28547 by @lunny

Replace #28500

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-21 23:37:08 +00:00
Giteabot
1183002b32 Use information from previous blame parts (#28572) (#28577)
Backport #28572 by @KN4CK3R

Fixes #28545

`git blame` output can contain blocks without commit information if it
was outputted before (the `0dafa97ea3f6d9662299579e5be1875cd28baaae 48
26 1` line):
```
fec25436488499df7231f63b857f66457c193d5c 24 25 1
author Bastien Montagne
author-mail <bastien@blender.org>
author-time 1660731031
author-tz +0200
committer Bastien Montagne
committer-mail <bastien@blender.org>
committer-time 1660731031
committer-tz +0200
summary LibOverride: Add Make/Reset/Clear entries to IDTemplate contextual menu.
previous 839ece6477203382b7a7483062961540180ff1cd source/blender/editors/interface/interface_ops.c
filename source/blender/editors/interface/interface_ops.c
        #include "BLT_translation.h"
0dafa97ea3f6d9662299579e5be1875cd28baaae 48 26 1

3d57bc4397fca53bc9702a27bbf50102827829b0 27 27 1
author Hans Goudey
author-mail <hans@blender.org>
author-time 1700131315
author-tz +0100
committer Hans Goudey
committer-mail <hooglyboogly@noreply.localhost>
committer-time 1700131315
committer-tz +0100
summary Cleanup: Move several blenkernel headers to C++
previous 451c054d9b7d3148a646caa5a72fb127a5b5c408 source/blender/editors/interface/interface_ops.cc
filename source/blender/editors/interface/interface_ops.cc
        #include "BKE_context.hh"
```
This PR reuses data from the previous blame part to fill these gaps.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-22 06:47:34 +08:00
wxiaoguang
1fc6bc1be2 Update mermaid for 1.21 (#28571)
Try to fix #28170
2023-12-21 20:14:50 +08:00
Lunny Xiao
2360c7ec6c Add changelog for 1.21.3 (#28569) 2023-12-21 07:47:15 +00:00
Giteabot
8ca32dc873 Fix merging artifact chunks error when minio storage basepath is set (#28555) (#28568)
Backport #28555 by @fuxiaohei

Related to  https://github.com/go-gitea/gitea/issues/28279

When merging artifact chunks, it lists chunks from storage. When storage
is minio, chunk's path contains `MINIO_BASE_PATH` that makes merging
break.

<del>So trim the `MINIO_BASE_PATH` when handle chunks.</del>

Update the chunk file's basename to retain necessary information. It
ensures that the directory in the chunk's path remains unaffected.

Co-authored-by: FuXiaoHei <fuxiaohei@vip.qq.com>
2023-12-21 15:38:39 +08:00
Giteabot
47f9b3f484 Update actions document about comparsion as Github Actions (#28560) (#28564)
Backport #28560 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-20 16:00:30 -05:00
Giteabot
16263af971 Fix inperformant query on retrifing review from database. (#28552) (#28562)
Backport #28552 by @6543

can we please PLEAS PLEASE only use raw SQL statements if it is relay
needed!!!

source is https://github.com/go-gitea/gitea/pull/28544 (before
refactoring)

Co-authored-by: 6543 <m.huber@kithara.com>
2023-12-20 16:55:08 +01:00
Giteabot
f096635622 Fix the issue ref rendering for wiki (#28556) (#28559)
Backport #28556 by wxiaoguang

Fix #28526, regression of 
* #26365

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-20 22:51:25 +08:00
Giteabot
932e282e15 Fix duplicate ID when deleting repo (#28520) (#28528)
Backport #28520 by @framitdavid


There is an accessibility issue in the interface when attempting to
delete a repository. When I click on "Delete repository," a dialog box
appears, requiring confirmation to proceed with the repository deletion.
However, when I press the "Repo name" label, the wrong input field gains
focus. The focused field is located behind the dialog and is intended
for renaming the repository.

I am submitting these pull requests to ensure that the correct input
field is focused when the user clicks on the label. This change will
also facilitate the writing of tests using Playwright or Testing Library
to retrieve elements based on roles. This PR will also improve
acessibility of this area.

Co-authored-by: David Øvrelid <46874830+framitdavid@users.noreply.github.com>
2023-12-19 16:40:03 +08:00
Giteabot
d9aeb1f09d Only check online runner when detecting matching runners in workflows (#28286) (#28512)
Backport #28286 by @yp05327

Mentioned:
[#28277](https://github.com/go-gitea/gitea/issues/28277#issuecomment-1831325276)

We should only check online runner when detecting matching runners in
workflows,
as if runner is not online, the workflow will not run.


![image](https://github.com/go-gitea/gitea/assets/18380374/11855e9d-7241-4b7a-b8d7-49dbb94ba1c5)

Co-authored-by: yp05327 <576951401@qq.com>
2023-12-19 04:06:31 +00:00
Giteabot
411310d698 chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525)
Backport #28491 by @appleboy

- Modify the `Password` field in `CreateUserOption` struct to remove the
`Required` tag
- Update the `v1_json.tmpl` template to include the `email` field and
remove the `password` field

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2023-12-19 11:14:06 +08:00
wxiaoguang
6d002f8e1e Update golang.org/x/crypto (#28519)
ref: https://groups.google.com/g/golang-announce/c/qA3XtxvMUyg,
CVE-2023-48795, https://go.dev/issue/64784
2023-12-19 07:04:21 +08:00
Giteabot
4462628a26 Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
Backport #28509 by wxiaoguang

Close #28505

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-18 15:25:52 +00:00
wxiaoguang
a3f403f438 Add option to disable ambiguous unicode characters detection (#28454) (#28499)
Backport #28454 (the only conflict is caused by some comments)

* Close #24483
* Close #28123
* Close #23682
* Close #23149
2023-12-18 12:20:37 +08:00
Giteabot
8ee1ed877b Initalize stroage for orphaned repository doctor (#28487) (#28490)
Backport #28487 by @earl-warren

- When a repository is orphaned and has objects stored in any of the
storages such as repository avatar or attachments the delete function
would error, because the storage module wasn't initalized.
- Add code to initialize the storage module.

Refs: https://codeberg.org/forgejo/forgejo/pulls/1954

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2023-12-16 22:06:37 +08:00
Giteabot
2c2e00899d Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488)
Backport #28485 by @kdumontnu

As described
[here](https://github.com/go-gitea/gitea/pull/28390#issuecomment-1857553331).

Co-authored-by: Kyle D <kdumontnu@gmail.com>
2023-12-16 00:04:05 -05:00
Giteabot
6cbb6f303a Refactor SSH clone URL generation code (#28421) (#28480)
Backport #28421 by wxiaoguang

Refactor the code and add tests, keep the old logic.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-15 14:54:40 +08:00
Giteabot
6af698fb81 Polyfill SubmitEvent for PaleMoon (#28441) (#28478)
Backport #28441 by wxiaoguang

Fix #28319

It only polyfills if there is no "SubmitEvent" class, so it has no side
effect for most users.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-15 03:04:37 +00:00
Giteabot
94a05a492d Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
Backport #28472 by @CaiCandong

Co-authored-by: CaiCandong <50507092+CaiCandong@users.noreply.github.com>
2023-12-15 07:54:32 +08:00
Giteabot
6de862abdf Fix documents for "custom/public/assets/" (#28465) (#28467)
Backport #28465 by wxiaoguang

Fix #28463

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-14 17:00:39 +08:00
Giteabot
b47482d58e Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
Backport #28392 by @nekrondev

Windows-based shells will add a CRLF when piping the token into
ssh-keygen command resulting in
verification error. This resolves #21527.

Co-authored-by: nekrondev <heiko@noordsee.de>
Co-authored-by: Heiko Besemann <heiko.besemann@qbeyond.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-14 12:50:26 +08:00
Giteabot
74ab798033 Add endpoint for not implemented Docker auth (#28457) (#28462)
Backport #28457 by @KN4CK3R

Recently Docker started to use the optional `POST /v2/token` endpoint
which should respond with a `404 Not Found` status code instead of the
current `405 Method Not Allowed`.

> Note: Not all token servers implement oauth2. If the request to the
endpoint returns 404 using the HTTP POST method, refer to Token
Documentation for using the HTTP GET method supported by all token
servers.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-13 22:06:24 +01:00
Giteabot
97a0bf151a Fix possible nil pointer access (#28428) (#28440)
Backport #28428 by @KN4CK3R

There could be a nil pointer exception if the file is not found because
that specific error is suppressed but not handled.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-12 23:35:32 +08:00
Giteabot
5e2bae7716 Don't show unnecessary citation JS error on UI (#28433) (#28437)
Backport #28433 by wxiaoguang

Fix #28226

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-12 21:14:04 +08:00
techknowlogick
96d3fcf179 1.21.2 changelog (#28387)
To be rebuilt with latest golang version

---------

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-12 14:23:54 +08:00
Lunny Xiao
265f485295 Do some missing checks (#28423) (#28432)
backport #28423
2023-12-12 06:20:18 +00:00
Giteabot
f144521aea Deprecate query string auth tokens (#28390) (#28430)
Backport #28390 by @jackHay22

## Changes
- Add deprecation warning to `Token` and `AccessToken` authentication
methods in swagger.
- Add deprecation warning header to API response. Example: 
  ```
  HTTP/1.1 200 OK
  ...
  Warning: token and access_token API authentication is deprecated
  ...
  ```
- Add setting `DISABLE_QUERY_AUTH_TOKEN` to reject query string auth
tokens entirely. Default is `false`

## Next steps
- `DISABLE_QUERY_AUTH_TOKEN` should be true in a subsequent release and
the methods should be removed in swagger
- `DISABLE_QUERY_AUTH_TOKEN` should be removed and the implementation of
the auth methods in question should be removed

## Open questions
- Should there be further changes to the swagger documentation?
Deprecation is not yet supported for security definitions (coming in
[OpenAPI Spec version
3.2.0](https://github.com/OAI/OpenAPI-Specification/issues/2506))
- Should the API router logger sanitize urls that use `token` or
`access_token`? (This is obviously an insufficient solution on its own)

Co-authored-by: Jack Hay <jack@allspice.io>
Co-authored-by: delvh <dev.lh@web.de>
2023-12-12 13:45:00 +08:00
Giteabot
6f4d5c0b8c Recover from panic in cron task (#28409) (#28425)
Backport #28409 by @earl-warren

- Currently there's code to recover gracefully from panics that happen
within the execution of cron tasks. However this recover code wasn't
being run, because `RunWithShutdownContext` also contains code to
recover from any panic and then gracefully shutdown Forgejo. Because
`RunWithShutdownContext` registers that code as last, that would get run
first which in this case is not behavior that we want.
- Move the recover code to inside the function, so that is run first
before `RunWithShutdownContext`'s recover code (which is now a noop).

Fixes: https://codeberg.org/forgejo/forgejo/issues/1910

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2023-12-12 03:28:56 +00:00
Giteabot
1ec622db24 Improve doctor cli behavior (#28422) (#28424)
Backport #28422 by wxiaoguang

1. Do not sort the "checks" slice again and again when "Register", it
just wastes CPU when the Gitea instance runs
2. If a check doesn't exist, tell the end user
3. Add some tests

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-11 16:28:27 +00:00
Giteabot
40d51188c0 Fix links in docs (#28302) (#28418)
Backport #28302 by @yp05327

Close #28287

## How to test it in local
convert Makefile L34 into:
```
cd .tmp/upstream-docs && git clean -f && git reset --hard && git fetch origin pull/28302/head:pr28302 && git switch pr28302
```

Co-authored-by: yp05327 <576951401@qq.com>
2023-12-11 22:53:59 +08:00
Lunny Xiao
87db4a47c8 Also sync DB branches on push if necessary (#28361) (#28403)
Fix #28056
Backport #28361 

This PR will check whether the repo has zero branch when pushing a
branch. If that, it means this repository hasn't been synced.

The reason caused that is after user upgrade from v1.20 -> v1.21, he
just push branches without visit the repository user interface. Because
all repositories routers will check whether a branches sync is necessary
but push has not such check.

For every repository, it has two states, synced or not synced. If there
is zero branch for a repository, then it will be assumed as non-sync
state. Otherwise, it's synced state. So if we think it's synced, we just
need to update branch/insert new branch. Otherwise do a full sync. So
that, for every push, there will be almost no extra load added. It's
high performance than yours.

For the implementation, we in fact will try to update the branch first,
if updated success with affect records > 0, then all are done. Because
that means the branch has been in the database. If no record is
affected, that means the branch does not exist in database. So there are
two possibilities. One is this is a new branch, then we just need to
insert the record. Another is the branches haven't been synced, then we
need to sync all the branches into database.
2023-12-11 06:16:56 +00:00
Giteabot
cd2dd5a67d Fix missing check (#28406) (#28411)
Backport #28406 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-11 09:10:48 +08:00
Giteabot
46beb7f33f enable system users search via the API (#28013) (#28018)
Backport #28013 by @earl-warren

Refs: https://codeberg.org/forgejo/forgejo/issues/1403

(cherry picked from commit dd4d17c159eaf8b642aa9e6105b0532e25972bb7)

---------

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-08 21:46:08 +00:00
Giteabot
3107093394 Fix Docker meta action for releases (#28232) (#28395) 2023-12-07 16:29:17 -08:00
Giteabot
272ae03341 Make gogit Repository.GetBranchNames consistent (#28348) (#28386)
Backport #28348 by @AdamMajer

nogogit GetBranchNames() lists branches sorted in reverse commit date
order. On the other hand the gogit implementation doesn't apply any
ordering resulting in unpredictable behaviour. In my case, the unit
tests requiring particular order fail

    repo_branch_test.go:24:
                Error Trace:
               ./gitea/modules/git/repo_branch_test.go:24
                Error:          elements differ

                                extra elements in list A:
                                ([]interface {}) (len=1) {
                                 (string) (len=6) "master"
                                }

                                extra elements in list B:
                                ([]interface {}) (len=1) {
                                 (string) (len=7) "branch1"
                                }

                                listA:
                                ([]string) (len=2) {
                                 (string) (len=6) "master",
                                 (string) (len=7) "branch2"
                                }

                                listB:
                                ([]string) (len=2) {
                                 (string) (len=7) "branch1",
                                 (string) (len=7) "branch2"
                                }
                Test:           TestRepository_GetBranches

To fix this, we sort branches based on their commit date in gogit
implementation.

Fixes: #28318

Co-authored-by: Adam Majer <amajer@suse.de>
2023-12-07 13:03:27 -05:00
Giteabot
b56a9f6ded Fix margin in server signed signature verification view (#28379) (#28381)
Backport #28379 by @lafriks

Before:

![image](https://github.com/go-gitea/gitea/assets/165205/e2e2256d-03c5-4ab8-8ed9-08ef68571a43)

After:

![image](https://github.com/go-gitea/gitea/assets/165205/804132ef-18f9-4ab8-949d-f6c71e7f4d24)

Co-authored-by: Lauris BH <lauris@nix.lv>
2023-12-07 10:37:12 +08:00
Giteabot
c5c44d0951 Fix object does not exist error when checking citation file (#28314) (#28369)
Backport #28314 by @yp05327

Fix #28264

`DataAsync()` will be called twice.
Caused by https://github.com/go-gitea/gitea/pull/27958.
I'm sorry, I didn't completely remove all unnecessary codes.

Co-authored-by: yp05327 <576951401@qq.com>
2023-12-06 22:06:51 +00:00
Giteabot
8f2805f757 Fix incorrect default value of [attachment].MAX_SIZE (#28373) (#28376)
Backport #28373 by @capvor

In the documents, the `[attachment] MAX_SIZE` default value should be 4.

Reference the source code `modules/setting/attachment.go` line 29.

Co-authored-by: capvor <capvor@sina.com>
2023-12-06 19:32:23 +00:00
Giteabot
5eaf91e919 Use filepath instead of path to create SQLite3 database file (#28374) (#28378)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fix #28300
2023-12-06 11:22:18 -06:00
Giteabot
b7e3adc66c Fix the runs will not be displayed bug when the main branch have no workflows but other branches have (#28359) (#28365)
Backport #28359 by @lunny

The left menu will only display the default branch's workflows but the
right side will display the runs triggered by all branches' workflows.
So we cannot hide right side if default branch has no workflows.

Fix #28332 
Replace #28333

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-06 02:28:00 -05:00
Giteabot
5b5f8aab19 handle repository.size column being NULL in migration v263 (#28336) (#28363)
Co-authored-by: Nate Levesque <nate@thenaterhood.com>
2023-12-05 14:51:56 +00:00
Giteabot
fef34790bb Convert git commit summary to valid UTF8. (#28356) (#28358)
Backport #28356 by @darrinsmart

The summary string ends up in the database, and (at least) MySQL &
PostgreSQL require valid UTF8 strings.

Fixes #28178

Co-authored-by: darrinsmart <darrin@djs.to>
Co-authored-by: Darrin Smart <darrin@filmlight.ltd.uk>
2023-12-05 09:19:08 +00:00
Giteabot
8b590de186 Fix migration panic due to an empty review comment diff (#28334) (#28362)
Backport #28334 by @lng2020

Fix #28328 
```
func (p *PullRequestComment) GetDiffHunk() string {
	if p == nil || p.DiffHunk == nil {
		return ""
	}
	return *p.DiffHunk
}
```
This function in the package `go-github` may return an empty diff. When
it's empty, the following code will panic because it access `ss[1]`

ec1feedbf5/services/migrations/gitea_uploader.go (L861-L867)

ec1feedbf5/modules/git/diff.go (L97-L101)

Co-authored-by: Nanguan Lin <70063547+lng2020@users.noreply.github.com>
2023-12-05 16:58:15 +08:00
Giteabot
5105d2093c Add HEAD support for rpm repo files (#28309) (#28360)
Backport #28309 by @KN4CK3R

Fixes https://codeberg.org/forgejo/forgejo/issues/1810

zypper uses HEAD requests to check file existence.

https://github.com/openSUSE/libzypp/blob/HEAD/zypp/RepoManager.cc#L2549

https://github.com/openSUSE/libzypp/blob/HEAD/zypp-curl/ng/network/private/downloaderstates/basicdownloader_p.cc#L116

@ExplodingDragon fyi

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-05 16:24:57 +08:00
Giteabot
08445d5d86 Refactor template empty checks (#28351) (#28354)
Backport #28351 by @KN4CK3R

Fix #28347

As there is no info how to reproduce it, I can't test it.
Fix may be `section_split.tmpl @ 126/130`.

Other changes are "empty check" refactorings.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-05 06:07:15 +00:00
Giteabot
b71d4c3ec0 Fix RPM/Debian signature key creation (#28352) (#28353)
Backport #28352 by @KN4CK3R

Fixes #28324

The name parameter can't contain some characters
(https://github.com/keybase/go-crypto/blob/master/openpgp/keys.go#L680)
but is optional. Therefore just use an empty string.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-05 13:42:41 +08:00
Giteabot
bf537adf8a Keep profile tab when clicking on Language (#28320) (#28331)
Backport #28320 by @JakobDev

Fixes https://codeberg.org/Codeberg/Community/issues/1355

Co-authored-by: JakobDev <jakobdev@gmx.de>
2023-12-03 14:54:53 +00:00
Giteabot
8c8c24f8eb Fix missing issue search index update when changing status (#28325) (#28330)
Backport #28325 by @brechtvl

Changing an issue status, assignee, labels or milestone without also
adding a comment would not update the index, resulting in wrong search
results.

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
2023-12-03 11:43:17 +00:00
Giteabot
fee9c05ed3 Fix wrong link in protect_branch_name_pattern_desc (#28313) (#28315)
Backport #28313 by @yp05327

The current href will link to
`https://domain/owner/repo/settings/branches/github.com/gobwas/glob`

Co-authored-by: yp05327 <576951401@qq.com>
2023-12-01 20:06:08 +08:00
Giteabot
e15fe85335 Read previous info from git blame (#28306) (#28310)
Backport #28306 by @KN4CK3R

Fixes #28280

Reads the `previous` info from the `git blame` output instead of
calculating it afterwards.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-01 08:27:35 +01:00
wxiaoguang
4f5122a7fe Ignore "non-existing" errors when getDirectorySize calculates the size (#28276) (#28285)
Backport #28276

The git command may operate the git directory (add/remove) files in any
time.

So when the code iterates the directory, some files may disappear during
the "walk". All "IsNotExist" errors should be ignored.
2023-11-30 16:39:16 +00:00
Giteabot
84e65afffd Use appSubUrl for OAuth2 callback URL tip (#28266) (#28275)
Backport #28266 by @earl-warren

- When crafting the OAuth2 callbackURL take into account `appSubUrl`,
which is quite safe given that its strictly formatted.
- No integration testing as this is all done in Javascript.
- Resolves https://codeberg.org/forgejo/forgejo/issues/1795

(cherry picked from commit 27cb6b7956136f87aa78067d9adb5a4c4ce28a24)

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2023-11-30 00:26:47 +00:00
Giteabot
d2908b2794 Meilisearch: require all query terms to be matched (#28293) (#28296)
Co-authored-by: Brecht Van Lommel <brecht@blender.org>
2023-11-29 09:38:04 -06:00
Giteabot
24e03a125d Fix required error for token name (#28267) (#28284)
Backport #28267 by @earl-warren

- Say to the binding middleware which locale should be used for the
required error.
- Resolves https://codeberg.org/forgejo/forgejo/issues/1683

(cherry picked from commit 5a2d7966127b5639332038e9925d858ab54fc360)

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2023-11-29 23:00:32 +08:00
Giteabot
76e892317b Fix issue will be detected as pull request when checking First-time contributor (#28237) (#28271)
Backport #28237 by @yp05327

Fix #28224

Co-authored-by: yp05327 <576951401@qq.com>
2023-11-29 02:49:33 +00:00
Giteabot
5001f63c07 Check for v prefix on tags for release clean name (#28257) (#28270)
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2023-11-28 16:02:26 -06:00
Giteabot
6d22ca15ab Use full width for project boards (#28225) (#28245)
Backport #28225 by @denyskon

Inspired by #28182 

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2023-11-27 18:20:53 +00:00
Giteabot
ea9f5a57e4 Increase "version" when update the setting value to a same value as before (#28243) (#28244)
Backport #28243

Setting the same value should not trigger DuplicateKey error, and the
"version" should be increased

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-11-28 01:58:39 +08:00
yp05327
96141e4e55 Revert move installation/upgrade-from-gogs.md in 1.21 (#28235)
https://github.com/go-gitea/gitea/pull/28233#discussion_r1405539630
2023-11-27 15:28:48 +01:00
Giteabot
ca5f0c93c6 Fix links in docs (#28234) (#28238)
Backport #28234 by @yp05327

Follow #28191

Changes:
- `(doc/administration/config-cheat-sheet.md` is incorrect:

![image](https://github.com/go-gitea/gitea/assets/18380374/1c417dd7-61a0-49ba-8d50-871fd4c9bf20)
- remove `../../`

Co-authored-by: yp05327 <576951401@qq.com>
2023-11-27 15:36:15 +08:00
252 changed files with 3070 additions and 1499 deletions

View File

@@ -44,7 +44,7 @@ jobs:
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws
@@ -78,6 +78,8 @@ jobs:
id: meta
with:
images: gitea/gitea
flavor: |
latest=false
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
@@ -109,6 +111,7 @@ jobs:
images: gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
latest=false
suffix=-rootless
# 1.2.3-rc0
tags: |

View File

@@ -46,7 +46,7 @@ jobs:
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws
@@ -86,7 +86,6 @@ jobs:
# 1.2
# 1.2.3
tags: |
type=raw,value=latest
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
@@ -118,14 +117,13 @@ jobs:
images: gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
suffix=-rootless
suffix=-rootless,onlatest=true
# this will generate tags in the following format (with -rootless suffix added):
# latest
# 1
# 1.2
# 1.2.3
tags: |
type=raw,value=latest
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}

View File

@@ -4,6 +4,127 @@ 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
* Update golang.org/x/crypto (#28519)
* API
* chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525)
* Add endpoint for not implemented Docker auth (#28457) (#28462)
* ENHANCEMENTS
* Add option to disable ambiguous unicode characters detection (#28454) (#28499)
* Refactor SSH clone URL generation code (#28421) (#28480)
* Polyfill SubmitEvent for PaleMoon (#28441) (#28478)
* BUGFIXES
* Fix the issue ref rendering for wiki (#28556) (#28559)
* Fix duplicate ID when deleting repo (#28520) (#28528)
* Only check online runner when detecting matching runners in workflows (#28286) (#28512)
* Initalize stroage for orphaned repository doctor (#28487) (#28490)
* Fix possible nil pointer access (#28428) (#28440)
* Don't show unnecessary citation JS error on UI (#28433) (#28437)
* DOCS
* Update actions document about comparsion as Github Actions (#28560) (#28564)
* Fix documents for "custom/public/assets/" (#28465) (#28467)
* MISC
* Fix inperformant query on retrifing review from database. (#28552) (#28562)
* Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
* Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488)
* Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
* Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/1.21.2) - 2023-12-12
* SECURITY
* Rebuild with recently released golang version
* Fix missing check (#28406) (#28411)
* Do some missing checks (#28423) (#28432)
* BUGFIXES
* Fix margin in server signed signature verification view (#28379) (#28381)
* Fix object does not exist error when checking citation file (#28314) (#28369)
* Use `filepath` instead of `path` to create SQLite3 database file (#28374) (#28378)
* Fix the runs will not be displayed bug when the main branch have no workflows but other branches have (#28359) (#28365)
* Handle repository.size column being NULL in migration v263 (#28336) (#28363)
* Convert git commit summary to valid UTF8. (#28356) (#28358)
* Fix migration panic due to an empty review comment diff (#28334) (#28362)
* Add `HEAD` support for rpm repo files (#28309) (#28360)
* Fix RPM/Debian signature key creation (#28352) (#28353)
* Keep profile tab when clicking on Language (#28320) (#28331)
* Fix missing issue search index update when changing status (#28325) (#28330)
* Fix wrong link in `protect_branch_name_pattern_desc` (#28313) (#28315)
* Read `previous` info from git blame (#28306) (#28310)
* Ignore "non-existing" errors when getDirectorySize calculates the size (#28276) (#28285)
* Use appSubUrl for OAuth2 callback URL tip (#28266) (#28275)
* Meilisearch: require all query terms to be matched (#28293) (#28296)
* Fix required error for token name (#28267) (#28284)
* Fix issue will be detected as pull request when checking `First-time contributor` (#28237) (#28271)
* Use full width for project boards (#28225) (#28245)
* Increase "version" when update the setting value to a same value as before (#28243) (#28244)
* Also sync DB branches on push if necessary (#28361) (#28403)
* Make gogit Repository.GetBranchNames consistent (#28348) (#28386)
* Recover from panic in cron task (#28409) (#28425)
* Deprecate query string auth tokens (#28390) (#28430)
* ENHANCEMENTS
* Improve doctor cli behavior (#28422) (#28424)
* Fix margin in server signed signature verification view (#28379) (#28381)
* Refactor template empty checks (#28351) (#28354)
* Read `previous` info from git blame (#28306) (#28310)
* Use full width for project boards (#28225) (#28245)
* Enable system users search via the API (#28013) (#28018)
## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/1.21.1) - 2023-11-26
* SECURITY

View File

@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/migrations"
migrate_base "code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/doctor"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -22,6 +23,19 @@ import (
"xorm.io/xorm"
)
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Subcommands: []*cli.Command{
cmdDoctorCheck,
cmdRecreateTable,
cmdDoctorConvert,
},
}
var cmdDoctorCheck = &cli.Command{
Name: "check",
Usage: "Diagnose and optionally fix problems",
@@ -60,19 +74,6 @@ var cmdDoctorCheck = &cli.Command{
},
}
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Subcommands: []*cli.Command{
cmdDoctorCheck,
cmdRecreateTable,
cmdDoctorConvert,
},
}
var cmdRecreateTable = &cli.Command{
Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.",
@@ -177,6 +178,7 @@ func runDoctorCheck(ctx *cli.Context) error {
if ctx.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
doctor.SortChecks(doctor.Checks)
for _, check := range doctor.Checks {
if check.IsDefault {
_, _ = w.Write([]byte{'*'})
@@ -192,26 +194,20 @@ func runDoctorCheck(ctx *cli.Context) error {
var checks []*doctor.Check
if ctx.Bool("all") {
checks = doctor.Checks
checks = make([]*doctor.Check, len(doctor.Checks))
copy(checks, doctor.Checks)
} else if ctx.IsSet("run") {
addDefault := ctx.Bool("default")
names := ctx.StringSlice("run")
for i, name := range names {
names[i] = strings.ToLower(strings.TrimSpace(name))
}
runNamesSet := container.SetOf(ctx.StringSlice("run")...)
for _, check := range doctor.Checks {
if addDefault && check.IsDefault {
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check)
continue
}
for _, name := range names {
if name == check.Name {
checks = append(checks, check)
break
}
runNamesSet.Remove(check.Name)
}
}
if len(runNamesSet) > 0 {
return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
}
} else {
for _, check := range doctor.Checks {
if check.IsDefault {
@@ -219,6 +215,5 @@ func runDoctorCheck(ctx *cli.Context) error {
}
}
}
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
}

33
cmd/doctor_test.go Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
"code.gitea.io/gitea/modules/doctor"
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
func TestDoctorRun(t *testing.T) {
doctor.Register(&doctor.Check{
Title: "Test Check",
Name: "test-check",
Run: func(ctx context.Context, logger log.Logger, autofix bool) error { return nil },
SkipDatabaseInitialization: true,
})
app := cli.NewApp()
app.Commands = []*cli.Command{cmdDoctorCheck}
err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
assert.NoError(t, err)
err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
}

View File

@@ -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

View File

@@ -491,6 +491,11 @@ INTERNAL_TOKEN=
;; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations.
;; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
;SUCCESSFUL_TOKENS_CACHE_SIZE = 20
;;
;; Reject API tokens sent in URL query string (Accept Header-based API tokens only). This avoids security vulnerabilities
;; stemming from cached/logged plain-text API tokens.
;; In future releases, this will become the default behavior
;DISABLE_QUERY_AUTH_TOKEN = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1151,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
;;
@@ -1205,6 +1204,9 @@ LEVEL = Info
;; Max size of files to be displayed (default is 8MiB)
;MAX_DISPLAY_FILE_SIZE = 8388608
;;
;; Detect ambiguous unicode characters in file contents and show warnings on the UI
;AMBIGUOUS_UNICODE_DETECTION = true
;;
;; Whether the email of the user should be shown in the Explore Users page
;SHOW_USER_EMAIL = true
;;
@@ -2569,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

View File

@@ -19,10 +19,10 @@ Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Poli
## Getting Pages
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/`. For example, to add Privacy Policy:
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/assets/`. For example, to add Privacy Policy:
```
wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
```
Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation.

View File

@@ -19,10 +19,10 @@ menu:
## 获取页面
Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/` 目录下。例如,如果要添加隐私政策:
Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/assets/` 目录下。例如,如果要添加隐私政策:
```
wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
```
现在,你需要编辑该页面以满足你的需求。特别是,你必须更改电子邮件地址、网址以及与 "Your Gitea Instance" 相关的引用,以匹配你的情况。

View File

@@ -196,9 +196,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
## CORS (`cors`)
- `ENABLED`: **false**: enable cors headers (disabled by default)
- `SCHEME`: **http**: scheme of allowed requests
- `ALLOW_DOMAIN`: **\***: list of requesting domains that are allowed
- `ALLOW_SUBDOMAIN`: **false**: allow subdomains of headers listed above to request
- `ALLOW_DOMAIN`: **\***: list of requesting origins that are allowed, eg: "https://*.example.com"
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: list of methods allowed to request
- `MAX_AGE`: **10m**: max time to cache response
- `ALLOW_CREDENTIALS`: **false**: allow request with credentials
@@ -220,6 +218,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `THEMES`: **auto,gitea,arc-green**: All available themes. Allow users select personalized themes.
regardless of the value of `DEFAULT_THEME`.
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
Values can be emoji alias (:smile:) or a unicode emoji.
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
@@ -571,6 +570,7 @@ And the following unique queues:
- off - do not check password complexity
- `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed.
- `SUCCESSFUL_TOKENS_CACHE_SIZE`: **20**: Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
- `DISABLE_QUERY_AUTH_TOKEN`: **false**: Reject API tokens sent in URL query string (Accept Header-based API tokens only). This setting will default to `true` in Gitea 1.23 and be deprecated in Gitea 1.24.
## Camo (`camo`)
@@ -1391,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

View File

@@ -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**: 允许带有凭据的请求
@@ -1040,10 +1038,11 @@ Gitea 创建以下非唯一队列:
## API (`api`)
- `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 (`/api/swagger`, `/api/v1/swagger`, …)。
- `MAX_RESPONSE_ITEMS`: **50**: 单个页面的最大 Feed.
- `ENABLE_OPENID_SIGNIN`: **false**: 允许使用OpenID登录当设置为`true`时可以通过 `/user/login` 页面进行OpenID登录
- `DISABLE_REGISTRATION`: **false**: 关闭用户注册
- `ENABLE_SWAGGER`: **true**: 启用API文档接口 (`/api/swagger`, `/api/v1/swagger`, …). True or false
- `MAX_RESPONSE_ITEMS`: **50**: API分页的最大单页项目数。
- `DEFAULT_PAGING_NUM`: **30**: API分页的默认分页数
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Git trees API的默认单页项目数
- `DEFAULT_MAX_BLOB_SIZE`: **10485760** (10MiB): blobs API的默认最大文件大小。
## OAuth2 (`oauth2`)

View File

@@ -42,11 +42,11 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
将自定义的公共文件(比如页面和图片)作为 webroot 放在 `custom/public/` 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。
举例说明:`image.png` 存放在 `custom/public/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
举例说明:`image.png` 存放在 `custom/public/assets/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
## 修改默认头像
替换以下目录中的 png 图片: `custom/public/img/avatar\_default.png`
替换以下目录中的 png 图片: `custom/public/assets/img/avatar\_default.png`
## 自定义 Gitea 页面

View File

@@ -55,7 +55,7 @@ PASSWD = `password`
要发送测试邮件以验证设置,请转到 Gitea > 站点管理 > 配置 > SMTP 邮件配置。
有关所有选项的完整列表,请查看[配置速查表](doc/administration/config-cheat-sheet.md)。
有关所有选项的完整列表,请查看[配置速查表](administration/config-cheat-sheet.md)。
请注意:只有在使用 TLS 或 `HOST=localhost` 加密 SMTP 服务器通信时才支持身份验证。TLS 加密可以通过以下方式进行:

View File

@@ -194,7 +194,7 @@ ALLOW_DATA_URI_IMAGES = true
}
```
将您的样式表添加到自定义目录中,例如 `custom/public/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
将您的样式表添加到自定义目录中,例如 `custom/public/assets/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
```html
<link rel="stylesheet" href="{{AppSubUrl}}/assets/css/my-style-XXXXX.css" />

View File

@@ -33,7 +33,7 @@ CERT_FILE = cert.pem
KEY_FILE = key.pem
```
请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](../config-cheat-sheet#server-server)。
请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](administration/config-cheat-sheet#server-server)。
对于“CERT_FILE”或“KEY_FILE”字段当文件路径是相对路径时文件路径相对于“GITEA_CUSTOM”环境变量。它也可以是绝对路径。

View File

@@ -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

View File

@@ -19,10 +19,7 @@ menu:
## Enabling/configuring API access
By default, `ENABLE_SWAGGER` is true, and
`MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat
Sheet](administration/config-cheat-sheet.md) for more
information.
By default, `ENABLE_SWAGGER` is true, and `MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat Sheet](administration/config-cheat-sheet.md) for more information.
## Authentication

View File

@@ -19,8 +19,7 @@ menu:
## 开启/配置 API 访问
通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat
Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。
通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。
## 通过 API 认证

View File

@@ -39,6 +39,7 @@ If a bug fix is targeted on 1.20.1 but 1.20.1 is not released yet, you can get t
To migrate from Gogs to Gitea:
- [Gogs version 0.9.146 or less](installation/upgrade-from-gogs.md)
- [Gogs version 0.11.46.0418](https://github.com/go-gitea/gitea/issues/4286)
To migrate from GitHub to Gitea, you can use Gitea's built-in migration form.

View File

@@ -41,6 +41,7 @@ menu:
要从Gogs迁移到Gitea
- [Gogs版本0.9.146或更低](installation/upgrade-from-gogs.md)
- [Gogs版本0.11.46.0418](https://github.com/go-gitea/gitea/issues/4286)
要从GitHub迁移到Gitea您可以使用Gitea内置的迁移表单。
@@ -189,7 +190,7 @@ Gitea 目前支持三个官方主题,分别是 `gitea`(亮色)、`arc-gree
假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到)
`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/css`文件夹中
`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/assets/css`文件夹中
通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题

View File

@@ -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).

View File

@@ -117,7 +117,7 @@ chmod 770 /etc/gitea
- 使用 `gitea generate secret` 创建 `SECRET_KEY``INTERNAL_TOKEN`
- 提供所有必要的密钥
详情参考 [命令行文档](/zh-cn/command-line/) 中有关 `gitea generate secret` 的内容。
详情参考 [命令行文档](administration/command-line.md) 中有关 `gitea generate secret` 的内容。
### 配置 Gitea 工作路径
@@ -209,6 +209,6 @@ remote: ./hooks/pre-receive.d/gitea: line 2: [...]: No such file or directory
如果您没有使用 Gitea 内置的 SSH 服务器,您还需要通过在管理选项中运行任务 `Update the '.ssh/authorized_keys' file with Gitea SSH keys.` 来重新编写授权密钥文件。
> 更多经验总结,请参考英文版 [Troubleshooting](/en-us/install-from-binary/#troubleshooting)
> 更多经验总结,请参考英文版 [Troubleshooting](https://docs.gitea.com/installation/install-from-binary#troubleshooting)
如果从本页中没有找到你需要的内容,请访问 [帮助页面](help/support.md)

View File

@@ -64,7 +64,7 @@ git checkout v@version@ # or git checkout pr-xyz
- `go` @minGoVersion@ 或更高版本,请参阅 [这里](https://golang.org/dl/)
- `node` @minNodeVersion@ 或更高版本,并且安装 `npm`, 请参阅 [这里](https://nodejs.org/zh-cn/download/)
- `make`, 请参阅 [这里](/zh-cn/hacking-on-gitea/)
- `make`, 请参阅 [这里](development/hacking-on-gitea.md)
为了尽可能简化编译过程,提供了各种 [make任务](https://github.com/go-gitea/gitea/blob/main/Makefile)。

View File

@@ -114,7 +114,7 @@ If you cannot see the settings page, please make sure that you have the right pe
The format of the registration token is a random string `D0gvfu2iHfUjNqCYVljVyRV14fISpJxxxxxxxxxx`.
A registration token can also be obtained from the gitea [command-line interface](../../administration/command-line.md#actions-generate-runner-token):
A registration token can also be obtained from the gitea [command-line interface](administration/command-line.md#actions-generate-runner-token):
```
gitea --config /etc/gitea/app.ini actions generate-runner-token

View File

@@ -113,7 +113,7 @@ Runner级别决定了从哪里获取注册令牌。
注册令牌的格式是一个随机字符串 `D0gvfu2iHfUjNqCYVljVyRV14fISpJxxxxxxxxxx`
注册令牌也可以通过 Gitea 的 [命令行](../../administration/command-line.md#actions-generate-runner-token) 获得:
注册令牌也可以通过 Gitea 的 [命令行](administration/command-line.md#actions-generate-runner-token) 获得:
### 注册Runner

View File

@@ -29,6 +29,10 @@ Like `uses: https://github.com/actions/checkout@v3` or `uses: http://your_gitea.
Gitea Actions supports writing actions in Go.
See [Creating Go Actions](https://blog.gitea.com/creating-go-actions/).
### Support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly on schedule
Github Actions doesn't support that. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
## Unsupported workflows syntax
### `concurrency`
@@ -110,6 +114,10 @@ It's ignored by Gitea Actions now.
Pre and Post steps don't have their own section in the job log user interface.
### Services steps
Services steps don't have their own section in the job log user interface.
## Different behavior
### Downloading actions

View File

@@ -29,6 +29,10 @@ Gitea Actions支持通过URL绝对路径定义actions这意味着您可以使
Gitea Actions支持使用Go编写Actions。
请参阅[创建Go Actions](https://blog.gitea.com/creating-go-actions/)。
### 支持非标准的调度语法 @yearly, @monthly, @weekly, @daily, @hourly
Github Actions 不支持这些语法,详见: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
## 不支持的工作流语法
### `concurrency`
@@ -116,6 +120,10 @@ Gitea Actions目前不支持此功能。
预处理和后处理步骤在Job日志用户界面中没有自己的用户界面。
### 服务步骤
服务步骤在Job日志用户界面中没有自己的用户界面。
## 不一样的行为
### 下载Actions

View File

@@ -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:

View File

@@ -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
```
然后更新本地软件包索引:

10
go.mod
View File

@@ -106,12 +106,12 @@ require (
github.com/yuin/goldmark v1.5.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.14.0
golang.org/x/crypto v0.17.0
golang.org/x/image v0.13.0
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.13.0
golang.org/x/sys v0.13.0
golang.org/x/text v0.13.0
golang.org/x/sys v0.15.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.14.0
google.golang.org/grpc v1.58.3
google.golang.org/protobuf v1.31.0
@@ -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

22
go.sum
View File

@@ -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=
@@ -1152,8 +1153,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1348,8 +1349,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1360,8 +1361,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1376,8 +1377,9 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1648,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=

View File

@@ -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

View File

@@ -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
}

View File

@@ -51,6 +51,11 @@ type ActionRunner struct {
Deleted timeutil.TimeStamp `xorm:"deleted"`
}
const (
RunnerOfflineTime = time.Minute
RunnerIdleTime = 10 * time.Second
)
// BelongsToOwnerName before calling, should guarantee that all attributes are loaded
func (r *ActionRunner) BelongsToOwnerName() string {
if r.RepoID != 0 {
@@ -76,11 +81,12 @@ func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
return types.OwnerTypeSystemGlobal
}
// if the logic here changed, you should also modify FindRunnerOptions.ToCond
func (r *ActionRunner) Status() runnerv1.RunnerStatus {
if time.Since(r.LastOnline.AsTime()) > time.Minute {
if time.Since(r.LastOnline.AsTime()) > RunnerOfflineTime {
return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
}
if time.Since(r.LastActive.AsTime()) > 10*time.Second {
if time.Since(r.LastActive.AsTime()) > RunnerIdleTime {
return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
}
return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
@@ -153,6 +159,7 @@ type FindRunnerOptions struct {
OwnerID int64
Sort string
Filter string
IsOnline util.OptionalBool
WithAvailable bool // not only runners belong to, but also runners can be used
}
@@ -178,6 +185,12 @@ func (opts FindRunnerOptions) toCond() builder.Cond {
if opts.Filter != "" {
cond = cond.And(builder.Like{"name", opts.Filter})
}
if opts.IsOnline.IsTrue() {
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
} else if opts.IsOnline.IsFalse() {
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
}
return cond
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -29,10 +29,15 @@ func VerifySSHKey(ownerID int64, fingerprint, token, signature string) (string,
return "", ErrKeyNotExist{}
}
if err := sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea"); err != nil {
log.Error("Unable to validate token signature. Error: %v", err)
return "", ErrSSHInvalidTokenSignature{
Fingerprint: key.Fingerprint,
err = sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea")
if err != nil {
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
// see https://github.com/PowerShell/PowerShell/issues/5974
if sshsig.Verify(bytes.NewBuffer([]byte(token+"\r\n")), []byte(signature), []byte(key.Content), "gitea") != nil {
log.Error("Unable to validate token signature. Error: %v", err)
return "", ErrSSHInvalidTokenSignature{
Fingerprint: key.Fingerprint,
}
}
}

View File

@@ -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)

View File

@@ -178,6 +178,15 @@ func GetByBean(ctx context.Context, bean any) (bool, error) {
return GetEngine(ctx).Get(bean)
}
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
if !cond.IsValid() {
return false, ErrConditionRequired{}
}
var bean T
return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean)
}
// DeleteByBean deletes all records according non-empty fields of the bean as conditions.
func DeleteByBean(ctx context.Context, bean any) (int64, error) {
return GetEngine(ctx).Delete(bean)

View File

@@ -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.

View File

@@ -72,3 +72,21 @@ func (err ErrNotExist) Error() string {
func (err ErrNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrConditionRequired represents an error which require condition.
type ErrConditionRequired struct{}
// IsErrConditionRequired checks if an error is an ErrConditionRequired
func IsErrConditionRequired(err error) bool {
_, ok := err.(ErrConditionRequired)
return ok
}
func (err ErrConditionRequired) Error() string {
return "condition is required"
}
// Unwrap unwraps this as a ErrNotExist err
func (err ErrConditionRequired) Unwrap() error {
return util.ErrInvalidArgument
}

View File

@@ -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 {

View File

@@ -205,10 +205,9 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64
})
}
// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information
// If it doest not exist, insert a new record into database
func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error {
cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
// UpdateBranch updates the branch information in the database.
func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) (int64, error) {
return db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
Update(&Branch{
CommitID: commit.ID.String(),
@@ -217,21 +216,6 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
IsDeleted: false,
})
if err != nil {
return err
}
if cnt > 0 {
return nil
}
return db.Insert(ctx, &Branch{
RepoID: repoID,
Name: branchName,
CommitID: commit.ID.String(),
CommitMessage: commit.Summary(),
PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
})
}
// AddDeletedBranch adds a deleted branch to the database
@@ -299,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
@@ -308,6 +292,17 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
sess := db.GetEngine(ctx)
var branch Branch
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
if err != nil {
return err
} else if !exist || branch.IsDeleted {
return ErrBranchNotExist{
RepoID: repo.ID,
BranchName: from,
}
}
// 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
Name: to,
@@ -363,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
}

View File

@@ -73,7 +73,7 @@ type FindBranchOptions struct {
Keyword string
}
func (opts *FindBranchOptions) Cond() builder.Cond {
func (opts FindBranchOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
@@ -92,7 +92,7 @@ func (opts *FindBranchOptions) Cond() builder.Cond {
}
func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) {
return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{})
return db.GetEngine(ctx).Where(opts.ToConds()).Count(&Branch{})
}
func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
@@ -108,7 +108,7 @@ func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
}
func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) {
sess := db.GetEngine(ctx).Where(opts.Cond())
sess := db.GetEngine(ctx).Where(opts.ToConds())
if opts.PageSize > 0 && !opts.IsListAll() {
sess = db.SetSessionPagination(sess, &opts.ListOptions)
}
@@ -119,7 +119,7 @@ func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, erro
}
func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) {
sess := db.GetEngine(ctx).Select("name").Where(opts.Cond())
sess := db.GetEngine(ctx).Select("name").Where(opts.ToConds())
if opts.PageSize > 0 && !opts.IsListAll() {
sess = db.SetSessionPagination(sess, &opts.ListOptions)
}

View File

@@ -4,6 +4,7 @@
package git_test
import (
"context"
"testing"
"code.gitea.io/gitea/models/db"
@@ -37,7 +38,7 @@ func TestAddDeletedBranch(t *testing.T) {
},
}
err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit)
_, err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit)
assert.NoError(t, err)
}
@@ -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
}))

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -461,8 +461,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) {
review := new(Review)
has, err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))",
issueID, userID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
has, err := db.GetEngine(ctx).Where(
builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})).
Desc("id").
Get(review)
if err != nil {
return nil, err
@@ -476,13 +478,13 @@ func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*R
}
// GetTeamReviewerByIssueIDAndTeamID get the latest review request of reviewer team for a pull request
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (review *Review, err error) {
review = new(Review)
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (*Review, error) {
review := new(Review)
var has bool
if has, err = db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)",
issueID, teamID).
Get(review); err != nil {
has, err := db.GetEngine(ctx).Where(builder.Eq{"issue_id": issueID, "reviewer_team_id": teamID}).
Desc("id").
Get(review)
if err != nil {
return nil, err
}

View File

@@ -32,7 +32,12 @@ func AddGitSizeAndLFSSizeToRepositoryTable(x *xorm.Engine) error {
return err
}
_, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size`)
_, err = sess.Exec(`UPDATE repository SET size = 0 WHERE size IS NULL`)
if err != nil {
return err
}
_, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size WHERE size > lfs_size`)
if err != nil {
return err
}

View File

@@ -47,6 +47,14 @@ func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error {
return util.ErrPermissionDenied
}
type ErrRepoIsArchived struct {
Repo *Repository
}
func (err ErrRepoIsArchived) Error() string {
return fmt.Sprintf("%s is archived", err.Repo.LogString())
}
var (
reservedRepoNames = []string{".", "..", "-"}
reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
@@ -570,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 {
@@ -594,25 +601,23 @@ func ComposeHTTPSCloneURL(owner, repo string) string {
func ComposeSSHCloneURL(ownerName, repoName string) string {
sshUser := setting.SSH.User
// if we have a ipv6 literal we need to put brackets around it
// for the git cloning to work.
sshDomain := setting.SSH.Domain
ip := net.ParseIP(setting.SSH.Domain)
if ip != nil && ip.To4() == nil {
sshDomain = "[" + setting.SSH.Domain + "]"
// non-standard port, it must use full URI
if setting.SSH.Port != 22 {
sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port))
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
}
if setting.SSH.Port != 22 {
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser,
net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)),
url.PathEscape(ownerName),
url.PathEscape(repoName))
// for standard port, it can use a shorter URI (without the port)
sshHost := sshDomain
if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil {
sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets
}
if setting.Repository.UseCompatSSHURI {
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
}
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
}
func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
@@ -654,6 +659,14 @@ func (repo *Repository) GetTrustModel() TrustModelType {
return trustModel
}
// MustNotBeArchived returns ErrRepoIsArchived if the repo is archived
func (repo *Repository) MustNotBeArchived() error {
if repo.IsArchived {
return ErrRepoIsArchived{Repo: repo}
}
return nil
}
// __________ .__ __
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |

View File

@@ -12,6 +12,8 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
@@ -186,3 +188,32 @@ func TestGetRepositoryByURL(t *testing.T) {
test(t, "try.gitea.io:user2/repo2.git")
})
}
func TestComposeSSHCloneURL(t *testing.T) {
defer test.MockVariableValue(&setting.SSH, setting.SSH)()
defer test.MockVariableValue(&setting.Repository, setting.Repository)()
setting.SSH.User = "git"
// test SSH_DOMAIN
setting.SSH.Domain = "domain"
setting.SSH.Port = 22
setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
// test SSH_DOMAIN while use non-standard SSH port
setting.SSH.Port = 123
setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
// test IPv6 SSH_DOMAIN
setting.Repository.UseCompatSSHURI = false
setting.SSH.Domain = "::1"
setting.SSH.Port = 22
assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.SSH.Port = 123
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
}

View File

@@ -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()
}

View File

@@ -81,7 +81,7 @@ func SetSettings(ctx context.Context, settings map[string]string) error {
return err
}
for k, v := range settings {
res, err := e.Exec("UPDATE system_setting SET setting_value=? WHERE setting_key=?", v, k)
res, err := e.Exec("UPDATE system_setting SET version=version+1, setting_value=? WHERE setting_key=?", v, k)
if err != nil {
return err
}

View File

@@ -39,4 +39,16 @@ func TestSettings(t *testing.T) {
assert.EqualValues(t, 3, rev)
assert.Len(t, settings, 2)
assert.EqualValues(t, "false", settings[keyName])
// setting the same value should not trigger DuplicateKey error, and the "version" should be increased
setting := &system.Setting{SettingKey: keyName}
_, err = db.GetByBean(db.DefaultContext, setting)
assert.NoError(t, err)
assert.EqualValues(t, 2, setting.Version)
err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "false"})
assert.NoError(t, err)
setting = &system.Setting{SettingKey: keyName}
_, err = db.GetByBean(db.DefaultContext, setting)
assert.NoError(t, err)
assert.EqualValues(t, 3, setting.Version)
}

View File

@@ -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.

View File

@@ -36,6 +36,7 @@ func NewReplaceUser(name string) *User {
}
const (
GhostUserID = -1
ActionsUserID = -2
ActionsUserName = "gitea-actions"
ActionsFullName = "Gitea Actions"

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -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 {

View File

@@ -8,11 +8,12 @@
package charset
import (
"bufio"
"html/template"
"io"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
)
@@ -20,20 +21,18 @@ import (
const RuneNBSP = 0xa0
// EscapeControlHTML escapes the unicode control sequences in a provided html document
func EscapeControlHTML(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) {
sb := &strings.Builder{}
outputStream := &HTMLStreamerWriter{Writer: sb}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
if err := StreamHTML(strings.NewReader(text), streamer); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
}
return streamer.escaped, sb.String()
escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader
return escaped, template.HTML(sb.String())
}
// EscapeControlReaders escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
// EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus
func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
if !setting.UI.AmbiguousUnicodeDetection {
_, err = io.Copy(writer, reader)
return &EscapeStatus{}, err
}
outputStream := &HTMLStreamerWriter{Writer: writer}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
@@ -43,41 +42,3 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.
}
return streamer.escaped, err
}
// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte. HTML line breaks are not inserted after every newline by this method.
func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
bufRd := bufio.NewReader(reader)
outputStream := &HTMLStreamerWriter{Writer: writer}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
for {
line, rdErr := bufRd.ReadString('\n')
if len(line) > 0 {
if err := streamer.Text(line); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
return streamer.escaped, err
}
}
if rdErr != nil {
if rdErr != io.EOF {
err = rdErr
}
break
}
}
return streamer.escaped, err
}
// EscapeControlString escapes the unicode control sequences in a provided string and returns the findings as an EscapeStatus and the escaped string
func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
sb := &strings.Builder{}
outputStream := &HTMLStreamerWriter{Writer: sb}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
if err := streamer.Text(text); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
}
return streamer.escaped, sb.String()
}

View File

@@ -64,7 +64,7 @@ func (e *escapeStreamer) Text(data string) error {
until, next = nextIdxs[0]+pos, nextIdxs[1]+pos
}
// from pos until until we know that the runes are not \r\t\n or even ' '
// from pos until we know that the runes are not \r\t\n or even ' '
runes := make([]rune, 0, next-until)
positions := make([]int, 0, next-until+1)

View File

@@ -4,11 +4,14 @@
package charset
import (
"reflect"
"strings"
"testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert"
)
type escapeControlTest struct {
@@ -132,22 +135,8 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
},
}
func TestEscapeControlString(t *testing.T) {
for _, tt := range escapeControlTests {
t.Run(tt.name, func(t *testing.T) {
status, result := EscapeControlString(tt.text, &translation.MockLocale{})
if !reflect.DeepEqual(*status, tt.status) {
t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status)
}
if result != tt.result {
t.Errorf("EscapeControlString()\nresult= %v,\nwanted= %v", result, tt.result)
}
})
}
}
func TestEscapeControlReader(t *testing.T) {
// lets add some control characters to the tests
// add some control characters to the tests
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
copy(tests, escapeControlTests)
@@ -169,29 +158,20 @@ func TestEscapeControlReader(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := strings.NewReader(tt.text)
output := &strings.Builder{}
status, err := EscapeControlReader(input, output, &translation.MockLocale{})
result := output.String()
if err != nil {
t.Errorf("EscapeControlReader(): err = %v", err)
}
if !reflect.DeepEqual(*status, tt.status) {
t.Errorf("EscapeControlReader() status = %v, wanted= %v", status, tt.status)
}
if result != tt.result {
t.Errorf("EscapeControlReader()\nresult= %v,\nwanted= %v", result, tt.result)
}
status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{})
assert.NoError(t, err)
assert.Equal(t, tt.status, *status)
assert.Equal(t, tt.result, output.String())
})
}
}
func TestEscapeControlReader_panic(t *testing.T) {
bs := make([]byte, 0, 20479)
bs = append(bs, 'A')
for i := 0; i < 6826; i++ {
bs = append(bs, []byte("—")...)
}
_, _ = EscapeControlString(string(bs), &translation.MockLocale{})
func TestSettingAmbiguousUnicodeDetection(t *testing.T) {
defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)()
_, out := EscapeControlHTML("a test", &translation.MockLocale{})
assert.EqualValues(t, `a<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>test`, out)
setting.UI.AmbiguousUnicodeDetection = false
_, out = EscapeControlHTML("a test", &translation.MockLocale{})
assert.EqualValues(t, `a test`, out)
}

View File

@@ -79,6 +79,7 @@ var Checks []*Check
// RunChecks runs the doctor checks for the provided list
func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error {
SortChecks(checks)
// the checks output logs by a special logger, they do not use the default logger
logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
@@ -104,20 +105,23 @@ func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) err
logger.Info("OK")
}
}
logger.Info("\nAll done.")
logger.Info("\nAll done (checks: %d).", len(checks))
return nil
}
// Register registers a command with the list
func Register(command *Check) {
Checks = append(Checks, command)
sort.SliceStable(Checks, func(i, j int) bool {
if Checks[i].Priority == Checks[j].Priority {
return Checks[i].Name < Checks[j].Name
}
func SortChecks(checks []*Check) {
sort.SliceStable(checks, func(i, j int) bool {
if checks[i].Priority == checks[j].Priority {
return checks[i].Name < checks[j].Name
}
if Checks[i].Priority == 0 {
if checks[i].Priority == 0 {
return false
}
return Checks[i].Priority < Checks[j].Priority
return checks[i].Priority < checks[j].Priority
})
}

View File

@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
repo_service "code.gitea.io/gitea/services/repository"
"xorm.io/builder"
@@ -31,6 +32,10 @@ func countOrphanedRepos(ctx context.Context) (int64, error) {
// deleteOrphanedRepos delete repository where user of owner_id do not exist
func deleteOrphanedRepos(ctx context.Context) (int64, error) {
if err := storage.Init(); err != nil {
return 0, err
}
batchSize := db.MaxBatchInsertSize("repository")
e := db.GetEngine(ctx)
var deleted int64

View File

@@ -11,6 +11,7 @@ import (
"io"
"os"
"regexp"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
@@ -18,8 +19,10 @@ import (
// BlamePart represents block of blame - continuous lines with one sha
type BlamePart struct {
Sha string
Lines []string
Sha string
Lines []string
PreviousSha string
PreviousPath string
}
// BlameReader returns part of file blame one by one
@@ -43,30 +46,38 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart
if r.lastSha != nil {
blamePart = &BlamePart{*r.lastSha, make([]string, 0)}
blamePart = &BlamePart{
Sha: *r.lastSha,
Lines: make([]string, 0),
}
}
var line []byte
var lineBytes []byte
var isPrefix bool
var err error
for err != io.EOF {
line, isPrefix, err = r.bufferedReader.ReadLine()
lineBytes, isPrefix, err = r.bufferedReader.ReadLine()
if err != nil && err != io.EOF {
return blamePart, err
}
if len(line) == 0 {
if len(lineBytes) == 0 {
// isPrefix will be false
continue
}
lines := shaLineRegex.FindSubmatch(line)
line := string(lineBytes)
lines := shaLineRegex.FindStringSubmatch(line)
if lines != nil {
sha1 := string(lines[1])
sha1 := lines[1]
if blamePart == nil {
blamePart = &BlamePart{sha1, make([]string, 0)}
blamePart = &BlamePart{
Sha: sha1,
Lines: make([]string, 0),
}
}
if blamePart.Sha != sha1 {
@@ -81,9 +92,11 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
return blamePart, nil
}
} else if line[0] == '\t' {
code := line[1:]
blamePart.Lines = append(blamePart.Lines, string(code))
blamePart.Lines = append(blamePart.Lines, line[1:])
} else if strings.HasPrefix(line, "previous ") {
parts := strings.SplitN(line[len("previous "):], " ", 2)
blamePart.PreviousSha = parts[0]
blamePart.PreviousPath = parts[1]
}
// need to munch to end of line...

View File

@@ -24,15 +24,17 @@ func TestReadingBlameOutput(t *testing.T) {
parts := []*BlamePart{
{
"72866af952e98d02a73003501836074b286a78f6",
[]string{
Sha: "72866af952e98d02a73003501836074b286a78f6",
Lines: []string{
"# test_repo",
"Test repository for testing migration from github to gitea",
},
},
{
"f32b0a9dfd09a60f616f29158f772cedd89942d2",
[]string{"", "Do not make any changes to this repo it is used for unit testing"},
Sha: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"},
PreviousSha: "72866af952e98d02a73003501836074b286a78f6",
PreviousPath: "README.md",
},
}
@@ -64,16 +66,18 @@ func TestReadingBlameOutput(t *testing.T) {
full := []*BlamePart{
{
"af7486bd54cfc39eea97207ca666aa69c9d6df93",
[]string{"line", "line"},
Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
Lines: []string{"line", "line"},
},
{
"45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
[]string{"changed line"},
Sha: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
Lines: []string{"changed line"},
PreviousSha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
PreviousPath: "blame.txt",
},
{
"af7486bd54cfc39eea97207ca666aa69c9d6df93",
[]string{"line", "line", ""},
Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
Lines: []string{"line", "line", ""},
},
}
@@ -89,8 +93,8 @@ func TestReadingBlameOutput(t *testing.T) {
Bypass: false,
Parts: []*BlamePart{
{
"af7486bd54cfc39eea97207ca666aa69c9d6df93",
[]string{"line", "line", "changed line", "line", "line", ""},
Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
Lines: []string{"line", "line", "changed line", "line", "line", ""},
},
},
},

View File

@@ -14,7 +14,6 @@ import (
"os/exec"
"strings"
"time"
"unsafe"
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
"code.gitea.io/gitea/modules/log"
@@ -389,15 +388,11 @@ func (r *runStdError) IsExitCode(code int) bool {
return false
}
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b)) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go)
}
// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr).
func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) {
stdoutBytes, stderrBytes, err := c.RunStdBytes(opts)
stdout = bytesToString(stdoutBytes)
stderr = bytesToString(stderrBytes)
stdout = util.UnsafeBytesToString(stdoutBytes)
stderr = util.UnsafeBytesToString(stderrBytes)
if err != nil {
return stdout, stderr, &runStdError{err: err, stderr: stderr}
}
@@ -432,7 +427,7 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
err := c.Run(newOpts)
stderr = stderrBuf.Bytes()
if err != nil {
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)}
}
// even if there is no err, there could still be some stderr output
return stdoutBuf.Bytes(), stderr, nil

View File

@@ -43,8 +43,9 @@ func (c *Commit) Message() string {
}
// Summary returns first line of commit message.
// The string is forced to be valid UTF8
func (c *Commit) Summary() string {
return strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0]
return strings.ToValidUTF8(strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0], "?")
}
// ParentID returns oid of n-th parent (0-based index).

View File

@@ -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",
},
}

View File

@@ -8,6 +8,7 @@ package git
import (
"context"
"sort"
"strings"
"github.com/go-git/go-git/v5/plumbing"
@@ -52,32 +53,46 @@ func (repo *Repository) IsBranchExist(name string) bool {
// GetBranches returns branches from the repository, skipping "skip" initial branches and
// returning at most "limit" branches, or all branches if "limit" is 0.
// Branches are returned with sort of `-commiterdate` as the nogogit
// implementation. This requires full fetch, sort and then the
// skip/limit applies later as gogit returns in undefined order.
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
var branchNames []string
type BranchData struct {
name string
committerDate int64
}
var branchData []BranchData
branches, err := repo.gogitRepo.Branches()
branchIter, err := repo.gogitRepo.Branches()
if err != nil {
return nil, 0, err
}
i := 0
count := 0
_ = branches.ForEach(func(branch *plumbing.Reference) error {
count++
if i < skip {
i++
return nil
} else if limit != 0 && count > skip+limit {
_ = branchIter.ForEach(func(branch *plumbing.Reference) error {
obj, err := repo.gogitRepo.CommitObject(branch.Hash())
if err != nil {
// skip branch if can't find commit
return nil
}
branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix))
branchData = append(branchData, BranchData{strings.TrimPrefix(branch.Name().String(), BranchPrefix), obj.Committer.When.Unix()})
return nil
})
// TODO: Sort?
sort.Slice(branchData, func(i, j int) bool {
return !(branchData[i].committerDate < branchData[j].committerDate)
})
return branchNames, count, nil
var branchNames []string
maxPos := len(branchData)
if limit > 0 {
maxPos = min(skip+limit, maxPos)
}
for i := skip; i < maxPos; i++ {
branchNames = append(branchNames, branchData[i].name)
}
return branchNames, len(branchData), nil
}
// WalkReferences walks all the references from the repository

View File

@@ -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 {

View File

@@ -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"])

View File

@@ -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

View File

@@ -9,6 +9,7 @@ import (
"bytes"
"fmt"
gohtml "html"
"html/template"
"io"
"path/filepath"
"strings"
@@ -55,7 +56,7 @@ func NewContext() {
}
// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name
func Code(fileName, language, code string) (string, string) {
func Code(fileName, language, code string) (output template.HTML, lexerName string) {
NewContext()
// diff view newline will be passed as empty, change to literal '\n' so it can be copied
@@ -65,7 +66,7 @@ func Code(fileName, language, code string) (string, string) {
}
if len(code) > sizeLimit {
return code, ""
return template.HTML(template.HTMLEscapeString(code)), ""
}
var lexer chroma.Lexer
@@ -102,13 +103,11 @@ func Code(fileName, language, code string) (string, string) {
cache.Add(fileName, lexer)
}
lexerName := formatLexerName(lexer.Config().Name)
return CodeFromLexer(lexer, code), lexerName
return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name)
}
// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes
func CodeFromLexer(lexer chroma.Lexer, code string) string {
func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML {
formatter := html.New(html.WithClasses(true),
html.WithLineNumbers(false),
html.PreventSurroundingPre(true),
@@ -120,23 +119,23 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string {
iterator, err := lexer.Tokenise(nil, code)
if err != nil {
log.Error("Can't tokenize code: %v", err)
return code
return template.HTML(template.HTMLEscapeString(code))
}
// style not used for live site but need to pass something
err = formatter.Format(htmlw, githubStyles, iterator)
if err != nil {
log.Error("Can't format code: %v", err)
return code
return template.HTML(template.HTMLEscapeString(code))
}
_ = htmlw.Flush()
// Chroma will add newlines for certain lexers in order to highlight them properly
// Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output
return strings.TrimSuffix(htmlbuf.String(), "\n")
return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n"))
}
// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
func File(fileName, language string, code []byte) ([]string, string, error) {
func File(fileName, language string, code []byte) ([]template.HTML, string, error) {
NewContext()
if len(code) > sizeLimit {
@@ -183,14 +182,14 @@ func File(fileName, language string, code []byte) ([]string, string, error) {
tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens())
htmlBuf := &bytes.Buffer{}
lines := make([]string, 0, len(tokensLines))
lines := make([]template.HTML, 0, len(tokensLines))
for _, tokens := range tokensLines {
iterator = chroma.Literator(tokens...)
err = formatter.Format(htmlBuf, githubStyles, iterator)
if err != nil {
return nil, "", fmt.Errorf("can't format code: %w", err)
}
lines = append(lines, htmlBuf.String())
lines = append(lines, template.HTML(htmlBuf.String()))
htmlBuf.Reset()
}
@@ -198,9 +197,9 @@ func File(fileName, language string, code []byte) ([]string, string, error) {
}
// PlainText returns non-highlighted HTML for code
func PlainText(code []byte) []string {
func PlainText(code []byte) []template.HTML {
r := bufio.NewReader(bytes.NewReader(code))
m := make([]string, 0, bytes.Count(code, []byte{'\n'})+1)
m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1)
for {
content, err := r.ReadString('\n')
if err != nil && err != io.EOF {
@@ -210,7 +209,7 @@ func PlainText(code []byte) []string {
if content == "" && err == io.EOF {
break
}
s := gohtml.EscapeString(content)
s := template.HTML(gohtml.EscapeString(content))
m = append(m, s)
}
return m

View File

@@ -4,21 +4,36 @@
package highlight
import (
"html/template"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func lines(s string) []string {
return strings.Split(strings.ReplaceAll(strings.TrimSpace(s), `\n`, "\n"), "\n")
func lines(s string) (out []template.HTML) {
// "" => [], "a" => ["a"], "a\n" => ["a\n"], "a\nb" => ["a\n", "b"] (each line always includes EOL "\n" if it exists)
out = make([]template.HTML, 0)
s = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(s), "\n", ""), `\n`, "\n")
for {
if p := strings.IndexByte(s, '\n'); p != -1 {
out = append(out, template.HTML(s[:p+1]))
s = s[p+1:]
} else {
break
}
}
if s != "" {
out = append(out, template.HTML(s))
}
return out
}
func TestFile(t *testing.T) {
tests := []struct {
name string
code string
want []string
want []template.HTML
lexerName string
}{
{
@@ -99,10 +114,7 @@ c=2
t.Run(tt.name, func(t *testing.T) {
out, lexerName, err := File(tt.name, "", []byte(tt.code))
assert.NoError(t, err)
expected := strings.Join(tt.want, "\n")
actual := strings.Join(out, "\n")
assert.Equal(t, strings.Count(actual, "<span"), strings.Count(actual, "</span>"))
assert.EqualValues(t, expected, actual)
assert.EqualValues(t, tt.want, out)
assert.Equal(t, tt.lexerName, lexerName)
})
}
@@ -112,7 +124,7 @@ func TestPlainText(t *testing.T) {
tests := []struct {
name string
code string
want []string
want []template.HTML
}{
{
name: "empty.py",
@@ -165,9 +177,7 @@ c=2`),
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
out := PlainText([]byte(tt.code))
expected := strings.Join(tt.want, "\n")
actual := strings.Join(out, "\n")
assert.EqualValues(t, expected, actual)
assert.EqualValues(t, tt.want, out)
})
}
}

View File

@@ -6,6 +6,7 @@ package code
import (
"bytes"
"context"
"html/template"
"strings"
"code.gitea.io/gitea/modules/highlight"
@@ -22,7 +23,7 @@ type Result struct {
Language string
Color string
LineNumbers []int
FormattedLines string
FormattedLines template.HTML
}
type SearchResultLanguages = internal.SearchResultLanguages

View File

@@ -211,10 +211,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(options.Keyword, &meilisearch.SearchRequest{
Filter: query.Statement(),
Limit: int64(limit),
Offset: int64(skip),
Sort: sortBy,
Filter: query.Statement(),
Limit: int64(limit),
Offset: int64(skip),
Sort: sortBy,
MatchingStrategy: "all",
})
if err != nil {
return nil, err

View File

@@ -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

View File

@@ -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
}
@@ -852,7 +827,9 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
}
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil || ctx.Metas["mode"] == "document" {
// FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered?
// The "mode" approach should be refactored to some other more clear&reliable way.
if ctx.Metas == nil || (ctx.Metas["mode"] == "document" && !ctx.IsWiki) {
return
}
var (

View File

@@ -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())

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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) {
"[!["+title+"]("+url+")]("+href+")",
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
url = "/../../.images/src/02/train.jpg"
test(
"!["+title+"]("+url+")",
`<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 image](https://example.com/image.jpg)
[[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)
}
}

View File

@@ -4,7 +4,6 @@
package markup
import (
"bytes"
"fmt"
"html"
"io"
@@ -87,7 +86,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
}
lexer = chroma.Coalesce(lexer)
if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil {
if _, err := w.WriteString(string(highlight.CodeFromLexer(lexer, source))); err != nil {
return ""
}
}
@@ -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
}

View File

@@ -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))

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)

View File

@@ -160,24 +160,25 @@ const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | o
// getDirectorySize returns the disk consumption for a given path
func getDirectorySize(path string) (int64, error) {
var size int64
err := filepath.WalkDir(path, func(_ string, info os.DirEntry, err error) error {
if err != nil {
if os.IsNotExist(err) { // ignore the error because the file maybe deleted during traversing.
return nil
}
err := filepath.WalkDir(path, func(_ string, entry os.DirEntry, err error) error {
if os.IsNotExist(err) { // ignore the error because some files (like temp/lock file) may be deleted during traversing.
return nil
} else if err != nil {
return err
}
if info.IsDir() {
if entry.IsDir() {
return nil
}
f, err := info.Info()
if err != nil {
info, err := entry.Info()
if os.IsNotExist(err) { // ignore the error as above
return nil
} else if err != nil {
return err
}
if (f.Mode() & notRegularFileMode) == 0 {
size += f.Size()
if (info.Mode() & notRegularFileMode) == 0 {
size += info.Size()
}
return err
return nil
})
return size, err
}

View File

@@ -26,7 +26,7 @@ func loadAttachmentFrom(rootCfg ConfigProvider) (err error) {
}
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip")
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(2048)
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)

View File

@@ -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

View File

@@ -9,7 +9,6 @@ import (
"net"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"time"
@@ -117,7 +116,7 @@ func DBConnStr() (string, error) {
if !EnableSQLite3 {
return "", errors.New("this Gitea binary was not built with SQLite3 support")
}
if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil {
if err := os.MkdirAll(filepath.Dir(Database.Path), os.ModePerm); err != nil {
return "", fmt.Errorf("Failed to create directories: %w", err)
}
journalMode := ""
@@ -169,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
}

View File

@@ -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",

View File

@@ -35,6 +35,7 @@ var (
PasswordHashAlgo string
PasswordCheckPwn bool
SuccessfulTokensCacheSize int
DisableQueryAuthToken bool
CSRFCookieName = "_csrf"
CSRFCookieHTTPOnly = true
)
@@ -159,4 +160,11 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
PasswordComplexity = append(PasswordComplexity, name)
}
}
// TODO: default value should be true in future releases
DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false)
if !DisableQueryAuthToken {
log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.")
}
}

Some files were not shown because too many files have changed in this diff Show More