Compare commits

...

54 commits

Author SHA1 Message Date
Cat /dev/Nulo 327bcf710d nulo: woodpecker CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-23 23:18:22 -03:00
Cat /dev/Nulo a094b48f42 Dockerfile: rename user to _gitea instead of git 2023-12-23 23:18:14 -03:00
Earl Warren 248b8bb0fa
Revert "improve possible performance bottleneck (#28547) (#28578)"
This reverts commit 7ddb1291ea.

Does not work at all.
2023-12-22 16:52:28 +01:00
Giteabot 90a650d2b0
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>
(cherry picked from commit acc8100d47bab3b13311bcf4c330b19e2153d4ca)
2023-12-22 12:10:04 +01:00
Giteabot 7ddb1291ea
improve possible performance bottleneck (#28547) (#28578)
Backport #28547 by @lunny

Replace #28500

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
(cherry picked from commit 1a3803effd42df628090b436b44a902482413e45)
2023-12-22 12:10:04 +01:00
Giteabot 1f8d01c74d
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>
(cherry picked from commit 1183002b3216d265a9180c9a86a1a37dc8226e89)
2023-12-22 12:10:03 +01:00
wxiaoguang 12a8d65a5a
Update mermaid for 1.21 (#28571)
Try to fix #28170

(cherry picked from commit 1fc6bc1be20469a9ff481d55a202b1d04b3b02e1)
2023-12-22 12:10:03 +01:00
Lunny Xiao 004ec9026b
Add changelog for 1.21.3 (#28569)
(cherry picked from commit 2360c7ec6cdddc337a9b4964a64d2e1640c69dad)
2023-12-22 12:10:03 +01:00
Giteabot bea2c52572
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>
(cherry picked from commit 8ca32dc8733c0dad147cc7f734097dbe1113e9a9)
2023-12-22 12:10:03 +01:00
Giteabot 1c7c48d96e
Update actions document about comparsion as Github Actions (#28560) (#28564)
Backport #28560 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
(cherry picked from commit 47f9b3f48474ba6577642cd15fe07928dd90aaa7)
2023-12-22 12:10:03 +01:00
Giteabot d350add668
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>
(cherry picked from commit 16263af9715afcfa249cf43157b2ae9b4d4765f4)
2023-12-22 12:10:03 +01:00
Giteabot 40fa6a526a
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>
(cherry picked from commit f096635622f117f7552e98d9ab3625369015e6ed)
2023-12-22 12:10:03 +01:00
Giteabot 9693f08812
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>
(cherry picked from commit 932e282e153ca30f6afaac738469137b761cf904)
2023-12-22 12:10:03 +01:00
Giteabot d15f9ee0b0
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>
(cherry picked from commit d9aeb1f09d3e8e734ccc51c50072a07f822e3033)
2023-12-22 12:10:03 +01:00
Giteabot 5d1d66ac3a
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>
(cherry picked from commit 411310d698e86bd639b31f2f5a8b856365b4590f)
2023-12-22 12:10:03 +01:00
Giteabot 2878d07926
Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
Backport #28509 by wxiaoguang

Close #28505

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit 4462628a262ed9bc667fa9e2a22af24359d02ceb)
2023-12-22 12:07:12 +01:00
wxiaoguang cd5a0ec1c8
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

(cherry picked from commit a3f403f438)

Conflicts:
	modules/setting/ui.go
	trivial context conflict
2023-12-22 12:07:01 +01:00
Giteabot c5ac659d1b
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>
(cherry picked from commit 8ee1ed877b)
2023-12-22 12:05:11 +01:00
Giteabot 6cc170011b
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>
(cherry picked from commit 2c2e00899d)
2023-12-22 12:05:11 +01:00
Giteabot 1475c1fcc4
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>
(cherry picked from commit 6cbb6f303a)
2023-12-22 12:05:11 +01:00
Giteabot 2b991b32eb
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>
(cherry picked from commit 6af698fb81)
2023-12-22 12:05:11 +01:00
Giteabot 3c6edfa5e2
Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
Backport #28472 by @CaiCandong

Co-authored-by: CaiCandong <50507092+CaiCandong@users.noreply.github.com>
(cherry picked from commit 94a05a492d)
2023-12-22 12:05:11 +01:00
Giteabot 066c3f3baa
Fix documents for "custom/public/assets/" (#28465) (#28467)
Backport #28465 by wxiaoguang

Fix #28463

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit 6de862abdf)
2023-12-22 12:05:11 +01:00
Giteabot 2a82e2d216
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>
(cherry picked from commit b47482d58e)
2023-12-22 12:05:11 +01:00
Giteabot a77398cd34
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>
(cherry picked from commit 74ab798033)
2023-12-22 12:05:11 +01:00
Giteabot 160ef74363
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>
(cherry picked from commit 97a0bf151a)
2023-12-22 12:05:11 +01:00
Earl Warren fdf950398b
[SEMVER] 6.0.3+0-gitea-1.21.3 2023-12-22 11:56:45 +01:00
Gusted b0874a0912 [GITEA] Downgrade @github/combobox-nav
- The v2.3.0 update caused to always scroll to the suggestion menu, where
it previously wouldn't work at all or only scroll when it wasn't in the
viewport.
- Ref: https://github.com/github/text-expander-element/issues/50
- Ref: https://github.com/github/combobox-nav/pull/75
- Resolves #1990

(cherry picked from commit 27145be211ff782afe0910adbe200f126961f150)
2023-12-21 13:16:29 +00:00
Earl Warren 580f29d9a2
[GITEA] the ref of a scheduled action is always the default branch
Since a scheduled action is only run from the default branch, it
cannot be anything else.

Refs: https://codeberg.org/forgejo/forgejo/issues/1926
(cherry picked from commit eff0822856fd727915f6e6493a80844cffd7b02a)
2023-12-21 12:13:49 +01:00
Gusted cbe94214e9 [GITEA] Remove redundant syncBranchToDB
- The transaction in combination with Git push was causing deadlocks if
you had the `push_update` queue set to `immediate`. This was the root
cause of slow integration tests in CI.
- Remove the sync branch code as this is already being done in the Git
post-receive hook.
- Add tests to proof the branch models are in sync even with this code
removed.

Backport of https://codeberg.org/forgejo/forgejo/pulls/1962

(cherry picked from commit a064065cb9a6e39597e38c37a405d066cfabf7f7)
2023-12-21 11:07:41 +00:00
Earl Warren 401c2a3c3d
[CI] upgrade moved to https://code.forgejo.org/forgejo/end-to-end/ 2023-12-19 23:44:34 +01:00
Gusted cd4413bdca Merge pull request 'Revert "[TESTS] oauth2: make it possible to use an alternate http.Client"' (#1986) from earl-warren/forgejo:wip-v1.21-oauth-npe into v1.21/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1986
Reviewed-by: Loïc Dachary <dachary@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2023-12-19 20:48:50 +00:00
Earl Warren cfaa6dc2ed
Revert "[TESTS] oauth2: make it possible to use an alternate http.Client"
This reverts commit 172fdd0d32.

This code was introduced for a test case that is no longer in use. It
should guard against the provider being null but that's not worth the
effort for deadcode. Just remove it.

Refs: https://codeberg.org/forgejo/forgejo/issues/1984
2023-12-19 16:38:35 +01:00
Gusted 9515a0ea38
[GITEA] Update crypto dependency
- https://groups.google.com/g/golang-announce/c/qA3XtxvMUyg & https://terrapin-attack.com/

(cherry picked from commit b38e83c9ef18c83e26a54b19a196fa4611eb1859)
2023-12-19 15:24:20 +01:00
Earl Warren a86fa739dc Merge pull request '[GITEA] Revert "Make user-content-* consistent with github (#26388)"' (#1945) from earl-warren/forgejo:wip-v1.21-markdown-anchors into v1.21/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1945
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2023-12-13 17:47:29 +00:00
Earl Warren 76d58fa269
[GITEA] test markdown CleanValue to prevent regression
It will determine how anchors are created and will break existing
links otherwise.

Adapted from Revert "Make `user-content-* ` consistent with github (#26388)
2023-12-13 18:12:08 +01:00
Earl Warren dbd896ce42
[GITEA] Revert "Make user-content-* consistent with github (#26388)"
Refs: https://codeberg.org/forgejo/forgejo/issues/1943

This reverts commit d41aee1d1e.

(cherry picked from commit d29ec91e91362b0657edee631f12cf1d55ba7a2c)
2023-12-13 18:09:56 +01:00
Earl Warren ce3d6b60bd
[SEMVER] 6.0.2+0-gitea-1.21.2 2023-12-12 17:04:38 +01:00
Earl Warren c477780163
Revert "Deprecate query string auth tokens (#28390) (#28430)"
It shows warnings although the setting is not set, this will surely be
fixed later but there is no sense in spaming the users right now. This
revert can be discarded when another fix lands in v1.21.

su -c "forgejo admin user generate-access-token -u root --raw --scopes 'all,sudo'" git
2023/12/12 15:54:45 .../setting/security.go:166:loadSecurityFrom() [W] 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.

This reverts commit 0e3a5abb69.

Conflicts:
	routers/api/v1/api.go
2023-12-12 17:04:38 +01:00
Giteabot bd264e6aed
Don't show unnecessary citation JS error on UI (#28433) (#28437)
Backport #28433 by wxiaoguang

Fix #28226

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit 5e2bae7716)
2023-12-12 15:21:33 +01:00
techknowlogick 6b48228500
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>
(cherry picked from commit 96d3fcf179)
2023-12-12 15:21:33 +01:00
Lunny Xiao 3b672c9791
Do some missing checks (#28423) (#28432)
backport #28423

(cherry picked from commit 265f485295)
2023-12-12 15:21:33 +01:00
Giteabot 0e3a5abb69
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>
(cherry picked from commit f144521aea)
2023-12-12 15:21:33 +01:00
Giteabot a0300f0bce
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>
(cherry picked from commit 40d51188c0)
2023-12-12 15:21:33 +01:00
Lunny Xiao 14750f3d11
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.

(cherry picked from commit 87db4a47c8)
2023-12-12 15:21:33 +01:00
Giteabot e4dc14f070
Fix missing check (#28406) (#28411)
Backport #28406 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
(cherry picked from commit cd2dd5a67d)
2023-12-12 15:21:32 +01:00
Earl Warren 69b4fd5fe2
Revert "fix POST /{username}/{reponame}/{type:issues|pulls}/move_pin"
This reverts commit 6483bceee2.
2023-12-12 15:21:32 +01:00
Earl Warren ee1655d5b0
Revert "fix POST /{username}/{reponame}/{type:issues|pulls}/{index}/content-history/soft-delete"
This reverts commit 2a8cb675ca.
2023-12-12 15:21:32 +01:00
Earl Warren d6ae79f78f Merge pull request '[GITEA] GetScheduledMergeByPullID may involve a system user' (#1925) from earl-warren/forgejo:wip-v1.21-actions-register into v1.21/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1925
2023-12-11 18:39:37 +00:00
Earl Warren 4148fb27db
[GITEA] GetScheduledMergeByPullID may involve a system user
Refs: https://codeberg.org/forgejo/forgejo/issues/1897
(cherry picked from commit ddc3c2255840d347afd13c272d2695c68196d6ef)
2023-12-11 18:43:03 +01:00
Gusted bbdb47dfa1 Merge pull request '[GITEA] Actually recover from a panic in cron task' (#1912) from forgejo-bp-1911 into v1.21/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1912
2023-12-10 20:39:24 +00:00
Gusted 026a4bb02d
[GITEA] Actually recover from a panic in cron task
- Backport #1911
- 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).
- Resolves #1910

(cherry picked from commit 761e1c83414407b65e331c2eeb4348c47acf0fbb)
2023-12-10 17:20:49 +01:00
Gusted 3981e6fdf3 Merge pull request 'Backport Correct default licenses to work as desired' (#1902) from fnetx/correct-license-defaults into v1.21/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1902
2023-12-08 18:31:20 +00:00
Otto Richter 5258e8f63d Correct default license selection
The default license choice was not working as expected,
because both the files in options/license were named differently, and
the setting string is not parsed properly.

The documentation will also be corrected.

See conversation on Matrix:
https://matrix.to/#/%23forgejo-chat%3Amatrix.org/%24ue13GJPr2d7D8fEaLx8yh1mFn3a4TVy_khkajrAYtx0?via=matrix.tu-berlin.de&via=turbo.ooo&via=matrix.org&via=catgirl.cloud
2023-12-08 14:38:06 +01:00
98 changed files with 938 additions and 547 deletions

View file

@ -1,45 +0,0 @@
name: upgrade
on:
pull_request_review:
push:
branches:
- 'forgejo*'
- 'v*/forgejo*'
jobs:
upgrade:
runs-on: docker
container:
image: codeberg.org/forgejo/test_env:main
steps:
- run: apt-get install -y -qq zstd
- name: cache S3 binaries
id: S3
uses: https://code.forgejo.org/actions/cache@v3
with:
path: |
/usr/local/bin/minio
/usr/local/bin/mc
/usr/local/bin/garage
key: S3
- name: skip if S3 cache hit
if: steps.S3.outputs.cache-hit != 'true'
run: echo no hit
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version: "1.21"
- run: |
git config --add safe.directory '*'
chown -R gitea:gitea . /go
- run: |
su gitea -c 'make deps-backend'
- run: |
script=$(pwd)/.forgejo/upgrades/test-upgrade.sh
$script run dependencies
$script clobber
su gitea -c "$script test_upgrades"

15
.woodpecker.yml Normal file
View file

@ -0,0 +1,15 @@
pipeline:
nulo-container:
image: docker.io/woodpeckerci/plugin-docker-buildx
settings:
repo: gitea.nulo.in/nulo/forgejo
tag: v1.21.3-0
registry: https://gitea.nulo.in
username: Nulo
password:
from_secret: registry_secret
secrets: [REGISTRY_SECRET]
when:
branch: "nulo/release/v1.21"
event: "push"

View file

@ -4,6 +4,73 @@ 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.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

@ -79,10 +79,10 @@ RUN addgroup \
-s /bin/bash \
-u 1000 \
-G git \
git && \
echo "git:*" | chpasswd -e
_gitea && \
echo "_gitea:*" | chpasswd -e
ENV USER git
ENV USER _gitea
ENV GITEA_CUSTOM /data/gitea
VOLUME ["/data"]

View file

@ -89,7 +89,7 @@ endif
VERSION = ${GITEA_VERSION}
# SemVer
FORGEJO_VERSION := 6.0.1+0-gitea-1.21.2
FORGEJO_VERSION := 6.0.3+0-gitea-1.21.3
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"

View file

@ -1214,6 +1214,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
;;

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

@ -220,6 +220,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
@ -573,6 +574,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`)

View file

@ -1040,10 +1040,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

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

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

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

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

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

6
go.mod
View file

@ -103,12 +103,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

15
go.sum
View file

@ -1143,8 +1143,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=
@ -1336,8 +1336,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=
@ -1347,8 +1347,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=
@ -1363,8 +1363,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=

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

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

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

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

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

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

@ -37,7 +37,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)
}

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

@ -74,7 +74,7 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe
return false, nil, err
}
doer, err := user_model.GetUserByID(ctx, scheduledPRM.DoerID)
doer, err := user_model.GetPossibleUserByID(ctx, scheduledPRM.DoerID)
if err != nil {
return false, nil, 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"}
@ -594,25 +602,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 +660,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

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

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

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

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

@ -29,12 +29,17 @@ func CleanValue(value []byte) []byte {
value = bytes.TrimSpace(value)
rs := bytes.Runes(value)
result := make([]rune, 0, len(rs))
needsDash := false
for _, r := range rs {
if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' || r == '-' {
switch {
case unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_':
if needsDash && len(result) > 0 {
result = append(result, '-')
}
needsDash = false
result = append(result, unicode.ToLower(r))
}
if unicode.IsSpace(r) {
result = append(result, '-')
default:
needsDash = true
}
}
return []byte(string(result))

View file

@ -1,4 +1,5 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
@ -15,44 +16,45 @@ func TestCleanValue(t *testing.T) {
}{
// Github behavior test cases
{"", ""},
{"test(0)", "test0"},
{"test!1", "test1"},
{"test:2", "test2"},
{"test*3", "test3"},
{"test4", "test4"},
{"test5", "test5"},
{"test*6", "test6"},
{"test6 a", "test6-a"},
{"test6 !b", "test6-b"},
{"testad # df", "testad--df"},
{"testad #23 df 2*/*", "testad-23-df-2"},
{"testad 23 df 2*/*", "testad-23-df-2"},
{"testad # 23 df 2*/*", "testad--23-df-2"},
{"test.0.1", "test-0-1"},
{"test(0)", "test-0"},
{"test!1", "test-1"},
{"test:2", "test-2"},
{"test*3", "test-3"},
{"test4", "test-4"},
{"test5", "test-5"},
{"test*6", "test-6"},
{"test6 a", "test-6-a"},
{"test6 !b", "test-6-b"},
{"testad # df", "test-ad-df"},
{"testad #23 df 2*/*", "test-ad-23-df-2"},
{"testad 23 df 2*/*", "test-ad-23-df-2"},
{"testad # 23 df 2*/*", "test-ad-23-df-2"},
{"Anchors in Markdown", "anchors-in-markdown"},
{"a_b_c", "a_b_c"},
{"a-b-c", "a-b-c"},
{"a-b-c----", "a-b-c----"},
{"test6a", "test6a"},
{"testa6", "testa6"},
{"tes a a a a", "tes-a-a---a--a"},
{" tes a a a a ", "tes-a-a---a--a"},
{"a-b-c----", "a-b-c"},
{"test6a", "test-6a"},
{"testa6", "test-a6"},
{"tes a a a a", "tes-a-a-a-a"},
{" tes a a a a ", "tes-a-a-a-a"},
{"Header with \"double quotes\"", "header-with-double-quotes"},
{"Placeholder to force scrolling on link's click", "placeholder-to-force-scrolling-on-links-click"},
{"Placeholder to force scrolling on link's click", "placeholder-to-force-scrolling-on-link-s-click"},
{"tes", "tes"},
{"tes0", "tes0"},
{"tes{0}", "tes0"},
{"tes[0]", "tes0"},
{"test【0】", "test0"},
{"tes…@a", "tesa"},
{"tes0", "tes-0"},
{"tes{0}", "tes-0"},
{"tes[0]", "tes-0"},
{"test【0】", "test-0"},
{"tes…@a", "tes-a"},
{"tes¥& a", "tes-a"},
{"tes= a", "tes-a"},
{"tes|a", "tesa"},
{"tes\\a", "tesa"},
{"tes/a", "tesa"},
{"tes|a", "tes-a"},
{"tes\\a", "tes-a"},
{"tes/a", "tes-a"},
{"a啊啊b", "a啊啊b"},
{"c🤔🤔d", "cd"},
{"a⚡a", "aa"},
{"e.~f", "ef"},
{"c🤔🤔d", "c-d"},
{"a⚡a", "a-a"},
{"e.~f", "e-f"},
}
for _, test := range tests {
assert.Equal(t, []byte(test.expect), CleanValue([]byte(test.param)), test.param)

View file

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

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

View file

@ -150,7 +150,7 @@ var (
DefaultPrivate: RepoCreatingLastUserVisibility,
DefaultPushCreatePrivate: true,
MaxCreationLimit: -1,
PreferredLicenses: []string{"Apache License 2.0", "MIT License"},
PreferredLicenses: []string{"Apache-2.0", "MIT"},
DisableHTTPGit: false,
AccessControlAllowOrigin: "",
UseCompatSSHURI: false,

View file

@ -34,6 +34,8 @@ var UI = struct {
SearchRepoDescription bool
OnlyShowRelevantRepos bool
AmbiguousUnicodeDetection bool
Notification struct {
MinTimeout time.Duration
TimeoutStep time.Duration
@ -81,6 +83,9 @@ var UI = struct {
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`, `forgejo`},
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:", "forgejo": ":forgejo:"},
AmbiguousUnicodeDetection: true,
Notification: struct {
MinTimeout time.Duration
TimeoutStep time.Duration

View file

@ -15,9 +15,8 @@ type CreateUserOption struct {
FullName string `json:"full_name" binding:"MaxSize(100)"`
// required: true
// swagger:strfmt email
Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
// required: true
Password string `json:"password" binding:"Required;MaxSize(255)"`
Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
Password string `json:"password" binding:"MaxSize(255)"`
MustChangePassword *bool `json:"must_change_password"`
SendNotify bool `json:"send_notify"`
Restricted *bool `json:"restricted"`

View file

@ -3,7 +3,7 @@
package util
import "github.com/yuin/goldmark/util"
import "unsafe"
func isSnakeCaseUpper(c byte) bool {
return 'A' <= c && c <= 'Z'
@ -83,5 +83,15 @@ func ToSnakeCase(input string) string {
}
}
}
return util.BytesToReadOnlyString(res)
return UnsafeBytesToString(res)
}
// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string.
// TODO: replace all "goldmark/util.BytesToReadOnlyString" with this official approach
func UnsafeBytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
func UnsafeStringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}

View file

@ -3551,7 +3551,7 @@ runs.commit = Commit
runs.scheduled = Scheduled
runs.pushed_by = pushed by
runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s
runs.no_matching_runner_helper = No matching runner: %s
runs.no_matching_online_runner_helper = No matching online runner with label: %s
runs.actor = Actor
runs.status = Status
runs.actors_no_select = All actors

18
package-lock.json generated
View file

@ -30,7 +30,7 @@
"katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0",
"mermaid": "10.5.0",
"mermaid": "10.6.1",
"mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.3",
"monaco-editor": "0.44.0",
@ -999,11 +999,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@github/combobox-nav": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.0.tgz",
"integrity": "sha512-5CX03DbsLZ41dX5hKHyQKtg133U6lruX4TD9G0Zs4W8BpWy7lN8DJ6TYaeZN/V7x8K34coaqNYk/Y5ic7stfkg=="
},
"node_modules/@github/markdown-toolbar-element": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.2.1.tgz",
@ -1022,6 +1017,11 @@
"@github/combobox-nav": "^2.0.2"
}
},
"node_modules/@github/text-expander-element/node_modules/@github/combobox-nav": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.2.0.tgz",
"integrity": "sha512-28kJUfzzPDYNyYsFCP/be8aXvEpjcEuciVENZlopcaUynS/4Pt9ll88Kl9l1D1Vy6a9+k+Km/YGJQ1e+gzG7SQ=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
@ -7530,9 +7530,9 @@
}
},
"node_modules/mermaid": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.5.0.tgz",
"integrity": "sha512-9l0o1uUod78D3/FVYPGSsgV+Z0tSnzLBDiC9rVzvelPxuO80HbN1oDr9ofpPETQy9XpypPQa26fr09VzEPfvWA==",
"version": "10.6.1",
"resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-10.6.1.tgz",
"integrity": "sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==",
"dependencies": {
"@braintree/sanitize-url": "^6.0.1",
"@types/d3-scale": "^4.0.3",

View file

@ -29,7 +29,7 @@
"katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0",
"mermaid": "10.5.0",
"mermaid": "10.6.1",
"mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.3",
"monaco-editor": "0.44.0",
@ -82,6 +82,9 @@
"vite-string-plugin": "1.1.2",
"vitest": "0.34.6"
},
"overrides": {
"@github/combobox-nav": "2.2.0"
},
"browserslist": [
"defaults",
"not ie > 0",

View file

@ -25,10 +25,11 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
contentRange := ctx.Req.Header.Get("Content-Range")
start, end, length := int64(0), int64(0), int64(0)
if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil {
log.Warn("parse content range error: %v, content-range: %s", err, contentRange)
return -1, fmt.Errorf("parse content range error: %v", err)
}
// build chunk store path
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d.chunk", runID, artifact.ID, start, end)
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
// use io.TeeReader to avoid reading all body to md5 sum.
// it writes data to hasher after reading end
// if hash is not matched, delete the read-end result
@ -57,6 +58,7 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
}
type chunkFileItem struct {
RunID int64
ArtifactID int64
Start int64
End int64
@ -66,9 +68,12 @@ type chunkFileItem struct {
func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) {
storageDir := fmt.Sprintf("tmp%d", runID)
var chunks []*chunkFileItem
if err := st.IterateObjects(storageDir, func(path string, obj storage.Object) error {
item := chunkFileItem{Path: path}
if _, err := fmt.Sscanf(path, filepath.Join(storageDir, "%d-%d-%d.chunk"), &item.ArtifactID, &item.Start, &item.End); err != nil {
if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error {
baseName := filepath.Base(fpath)
// when read chunks from storage, it only contains storage dir and basename,
// no matter the subdirectory setting in storage config
item := chunkFileItem{Path: storageDir + "/" + baseName}
if _, err := fmt.Sscanf(baseName, "%d-%d-%d-%d.chunk", &item.RunID, &item.ArtifactID, &item.Start, &item.End); err != nil {
return fmt.Errorf("parse content range error: %v", err)
}
chunks = append(chunks, &item)

View file

@ -603,7 +603,10 @@ func ContainerRoutes() *web.Route {
})
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Get("/token", container.Authenticate)
r.Group("/token", func() {
r.Get("", container.Authenticate)
r.Post("", container.AuthenticateNotImplemented)
})
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.Group("/{image}", func() {

View file

@ -156,6 +156,17 @@ func Authenticate(ctx *context.Context) {
})
}
// https://distribution.github.io/distribution/spec/auth/oauth/
func AuthenticateNotImplemented(ctx *context.Context) {
// This optional endpoint can be used to authenticate a client.
// It must implement the specification described in:
// https://datatracker.ietf.org/doc/html/rfc6749
// https://distribution.github.io/distribution/spec/auth/oauth/
// Purpose of this stub is to respond with 404 Not Found instead of 405 Method Not Allowed.
ctx.Status(http.StatusNotFound)
}
// https://docs.docker.com/registry/spec/api/#listing-repositories
func GetRepositoryList(ctx *context.Context) {
n := ctx.FormInt("n")

View file

@ -93,18 +93,28 @@ func CreateUser(ctx *context.APIContext) {
if ctx.Written() {
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return
}
pwned, err := password.IsPwned(ctx, form.Password)
if pwned {
if err != nil {
log.Error(err.Error())
if u.LoginType == auth.Plain {
if len(form.Password) < setting.MinPasswordLength {
err := errors.New("PasswordIsRequired")
ctx.Error(http.StatusBadRequest, "PasswordIsRequired", err)
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return
}
pwned, err := password.IsPwned(ctx, form.Password)
if pwned {
if err != nil {
log.Error(err.Error())
}
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return
}
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{

View file

@ -787,6 +787,24 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
}
}
func individualPermsChecker(ctx *context.APIContext) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
switch {
case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
ctx.NotFound("Visit Project", nil)
return
}
case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
if ctx.Doer == nil {
ctx.NotFound("Visit Project", nil)
return
}
}
}
}
// Routes registers all v1 APIs routes to web application.
func Routes() *web.Route {
m := web.NewRoute()
@ -887,7 +905,7 @@ func Routes() *web.Route {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds)
}, context_service.UserAssignmentAPI())
}, context_service.UserAssignmentAPI(), individualPermsChecker)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope)

View file

@ -262,12 +262,11 @@ func CreateBranch(ctx *context.APIContext) {
}
}
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
if err != nil {
if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
}
if models.IsErrTagAlreadyExists(err) {
} else if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "", "The branch already exists.")

View file

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/convert"
@ -77,6 +78,7 @@ func List(ctx *context.Context) {
// Get all runner labels
opts := actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID,
IsOnline: util.OptionalBoolTrue,
WithAvailable: true,
}
runners, err := actions_model.FindRunners(ctx, opts)
@ -114,7 +116,7 @@ func List(ctx *context.Context) {
continue
}
if !allRunnerLabels.Contains(ro) {
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_runner_helper", ro)
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_online_runner_helper", ro)
break
}
}

View file

@ -125,7 +125,7 @@ func RefBlame(ctx *context.Context) {
}
type blameResult struct {
Parts []git.BlamePart
Parts []*git.BlamePart
UsesIgnoreRevs bool
FaultyIgnoreRevsFile bool
}
@ -170,7 +170,9 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil
func fillBlameResult(br *git.BlameReader, r *blameResult) error {
r.UsesIgnoreRevs = br.UsesIgnoreRevs()
r.Parts = make([]git.BlamePart, 0, 5)
previousHelper := make(map[string]*git.BlamePart)
r.Parts = make([]*git.BlamePart, 0, 5)
for {
blamePart, err := br.NextPart()
if err != nil {
@ -179,13 +181,23 @@ func fillBlameResult(br *git.BlameReader, r *blameResult) error {
if blamePart == nil {
break
}
r.Parts = append(r.Parts, *blamePart)
if prev, ok := previousHelper[blamePart.Sha]; ok {
if blamePart.PreviousSha == "" {
blamePart.PreviousSha = prev.PreviousSha
blamePart.PreviousPath = prev.PreviousPath
}
} else {
previousHelper[blamePart.Sha] = blamePart
}
r.Parts = append(r.Parts, blamePart)
}
return nil
}
func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[string]*user_model.UserCommit {
func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[string]*user_model.UserCommit {
// store commit data by SHA to look up avatar info etc
commitNames := make(map[string]*user_model.UserCommit)
// and as blameParts can reference the same commits multiple
@ -227,7 +239,7 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[str
return commitNames
}
func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit) {
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink
language := ""
@ -310,8 +322,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
lexerName = lexerNameForLine
}
br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale)
br.Code = gotemplate.HTML(line)
br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale)
rows = append(rows, br)
escapeStatus = escapeStatus.Or(br.EscapeStatus)
}

View file

@ -191,9 +191,9 @@ func CreateBranch(ctx *context.Context) {
}
err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
} else if ctx.Repo.IsViewBranch {
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.BranchName, form.NewBranchName)
} else {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName)
}
if err != nil {
if models.IsErrProtectedTagName(err) {

View file

@ -193,16 +193,7 @@ func SoftDeleteContentHistory(ctx *context.Context) {
var comment *issues_model.Comment
var history *issues_model.ContentHistory
var err error
if commentID != 0 {
if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil {
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
return
}
if comment.IssueID != issue.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
}
if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
log.Error("can not get issue content history %v. err=%v", historyID, err)
return
@ -211,6 +202,21 @@ func SoftDeleteContentHistory(ctx *context.Context) {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if commentID != 0 {
if history.CommentID != commentID {
ctx.NotFound("CompareCommentID", issues_model.ErrCommentNotExist{})
return
}
if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil {
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
return
}
if comment.IssueID != issue.ID {
ctx.NotFound("CompareIssueID", issues_model.ErrCommentNotExist{})
return
}
}
canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history)
if !canSoftDelete {

View file

@ -89,8 +89,10 @@ func IssuePinMove(ctx *context.Context) {
log.Error(err.Error())
return
}
if issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
ctx.Status(http.StatusNotFound)
log.Error("Issue does not belong to this repository")
return
}

View file

@ -9,6 +9,7 @@ import (
gocontext "context"
"encoding/base64"
"fmt"
"html/template"
"image"
"io"
"net/http"
@ -317,19 +318,18 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
}, rd)
if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], _ = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
ctx.Data["FileContent"] = buf.String()
}
} else {
ctx.Data["IsPlainText"] = true
buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], err = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
if err != nil {
log.Error("Read failed: %v", err)
delete(ctx.Data, "IsMarkup")
}
}
ctx.Data["FileContent"] = buf.String()
if ctx.Data["IsMarkup"] != true {
ctx.Data["IsPlainText"] = true
content, err := io.ReadAll(rd)
if err != nil {
log.Error("Read readme content failed: %v", err)
}
contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
}
}
@ -611,7 +611,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
}
}
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output string, err error) {
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
markupRd, markupWr := io.Pipe()
defer markupWr.Close()
done := make(chan struct{})
@ -619,7 +619,7 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i
sb := &strings.Builder{}
// We allow NBSP here this is rendered
escaped, _ = charset.EscapeControlReader(markupRd, sb, ctx.Locale, charset.RuneNBSP)
output = sb.String()
output = template.HTML(sb.String())
close(done)
}()
err = markup.Render(renderCtx, input, markupWr)

View file

@ -798,6 +798,24 @@ func registerRoutes(m *web.Route) {
}
}
individualPermsChecker := func(ctx *context.Context) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
switch {
case ctx.ContextUser.Visibility == structs.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
ctx.NotFound("Visit Project", nil)
return
}
case ctx.ContextUser.Visibility == structs.VisibleTypeLimited:
if ctx.Doer == nil {
ctx.NotFound("Visit Project", nil)
return
}
}
}
}
// ***** START: Organization *****
m.Group("/org", func() {
m.Group("/{org}", func() {
@ -984,11 +1002,11 @@ func registerRoutes(m *web.Route) {
return
}
})
})
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker)
m.Group("", func() {
m.Get("/code", user.CodeSearch)
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker)
}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
m.Group("/{username}/{reponame}", func() {

View file

@ -422,7 +422,7 @@ func handleSchedules(
OwnerID: input.Repo.OwnerID,
WorkflowID: dwf.EntryName,
TriggerUserID: input.Doer.ID,
Ref: input.Ref,
Ref: input.Repo.DefaultBranch,
CommitSHA: commit.ID.String(),
Event: input.Event,
EventPayload: string(p),

View file

@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: Copyright the Forgejo contributors
// SPDX-License-Identifier: MIT
package oauth2
import (
"net/http"
)
var HTTPClient *http.Client

View file

@ -63,9 +63,7 @@ func init() {
if setting.OAuth2Client.EnableAutoRegistration {
scopes = append(scopes, "user:email")
}
provider := github.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, custom.EmailURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
return github.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, custom.EmailURL, scopes...), nil
}))
RegisterGothProvider(NewCustomProvider(
@ -75,9 +73,7 @@ func init() {
ProfileURL: availableAttribute(gitlab.ProfileURL),
}, func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
scopes = append(scopes, "read_user")
provider := gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
return gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil
}))
RegisterGothProvider(NewCustomProvider(
@ -87,9 +83,7 @@ func init() {
ProfileURL: requiredAttribute(gitea.ProfileURL),
},
func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
provider := gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
return gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil
}))
RegisterGothProvider(NewCustomProvider(
@ -99,9 +93,7 @@ func init() {
ProfileURL: requiredAttribute(nextcloud.ProfileURL),
},
func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
provider := nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
return nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil
}))
RegisterGothProvider(NewCustomProvider(
@ -109,9 +101,7 @@ func init() {
AuthURL: requiredAttribute(mastodon.InstanceURL),
},
func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
provider := mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
return mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, scopes...), nil
}))
RegisterGothProvider(NewCustomProvider(
@ -124,12 +114,10 @@ func init() {
azureScopes[i] = azureadv2.ScopeType(scope)
}
provider := azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{
return azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{
Tenant: azureadv2.TenantType(custom.Tenant),
Scopes: azureScopes,
})
provider.HTTPClient = HTTPClient
return provider, nil
}), nil
},
))
}

View file

@ -43,7 +43,6 @@ func (o *OpenIDProvider) CreateGothProvider(providerName, callbackURL string, so
if err != nil {
log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err)
}
provider.HTTPClient = HTTPClient
return provider, err
}

View file

@ -84,13 +84,15 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.lock.Unlock()
defer func() {
taskStatusTable.Stop(t.Name)
if err := recover(); err != nil {
// Recover a panic within the
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr)
}
}()
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
defer func() {
if err := recover(); err != nil {
// Recover a panic within the execution of the task.
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr)
}
}()
// Store the time of this run, before the function is executed, so it
// matches the behavior of what the cron library does.
t.lock.Lock()

View file

@ -285,15 +285,15 @@ type DiffInline struct {
// DiffInlineWithUnicodeEscape makes a DiffInline with hidden unicode characters escaped
func DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) DiffInline {
status, content := charset.EscapeControlHTML(string(s), locale)
return DiffInline{EscapeStatus: status, Content: template.HTML(content)}
status, content := charset.EscapeControlHTML(s, locale)
return DiffInline{EscapeStatus: status, Content: content}
}
// DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escaped
func DiffInlineWithHighlightCode(fileName, language, code string, locale translation.Locale) DiffInline {
highlighted, _ := highlight.Code(fileName, language, code)
status, content := charset.EscapeControlHTML(highlighted, locale)
return DiffInline{EscapeStatus: status, Content: template.HTML(content)}
return DiffInline{EscapeStatus: status, Content: content}
}
// GetComputedInlineDiffFor computes inline diff for the given line.

View file

@ -93,10 +93,10 @@ func (hcd *highlightCodeDiff) diffWithHighlight(filename, language, codeA, codeB
highlightCodeA, _ := highlight.Code(filename, language, codeA)
highlightCodeB, _ := highlight.Code(filename, language, codeB)
highlightCodeA = hcd.convertToPlaceholders(highlightCodeA)
highlightCodeB = hcd.convertToPlaceholders(highlightCodeB)
convertedCodeA := hcd.convertToPlaceholders(string(highlightCodeA))
convertedCodeB := hcd.convertToPlaceholders(string(highlightCodeB))
diffs := diffMatchPatch.DiffMain(highlightCodeA, highlightCodeB, true)
diffs := diffMatchPatch.DiffMain(convertedCodeA, convertedCodeB, true)
diffs = diffMatchPatch.DiffCleanupEfficiency(diffs)
for i := range diffs {

View file

@ -82,10 +82,7 @@ func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}
@ -157,12 +154,11 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexFilename, fmt.Sprintf("%s|%s|%s", branch, repository, architecture))
if err != nil && !errors.Is(err, util.ErrNotExist) {
return err
} else if pf == nil {
return nil
}
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
return packages_model.DeleteFileByID(ctx, pf.ID)
return packages_service.DeletePackageFile(ctx, pf)
}
// Cache data needed for all repository files

View file

@ -11,6 +11,7 @@ import (
container_model "code.gitea.io/gitea/models/packages/container"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
digest "github.com/opencontainers/go-digest"
)
@ -47,10 +48,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}

View file

@ -110,10 +110,7 @@ func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}
@ -182,12 +179,11 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, key)
if err != nil && !errors.Is(err, util.ErrNotExist) {
return err
} else if pf == nil {
continue
}
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}
@ -285,12 +281,11 @@ func buildReleaseFiles(ctx context.Context, ownerID int64, repoVersion *packages
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, distribution)
if err != nil && !errors.Is(err, util.ErrNotExist) {
return err
} else if pf == nil {
continue
}
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}

View file

@ -148,10 +148,7 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
return err
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}

View file

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files"
@ -28,30 +29,13 @@ import (
)
// CreateNewBranch creates a new repository branch
func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName string) (err error) {
// Check if branch name can be used
if err := checkBranchName(ctx, repo, branchName); err != nil {
func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, oldBranchName, branchName string) (err error) {
branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName)
if err != nil {
return err
}
if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) {
return git_model.ErrBranchNotExist{
BranchName: oldBranchName,
}
}
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
Remote: repo.RepoPath(),
Branch: fmt.Sprintf("%s%s:%s%s", git.BranchPrefix, oldBranchName, git.BranchPrefix, branchName),
Env: repo_module.PushingEnvironment(doer, repo),
}); err != nil {
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
return fmt.Errorf("push: %w", err)
}
return nil
return CreateNewBranchFromCommit(ctx, doer, repo, gitRepo, branch.CommitID, branchName)
}
// Branch contains the branch information
@ -244,8 +228,54 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
return err
}
// syncBranchToDB sync the branch information in the database. It 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.
func syncBranchToDB(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error {
cnt, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit)
if err != nil {
return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err)
}
if cnt > 0 { // This means branch does exist, so it's a normal update. It also means the branch has been synced.
return nil
}
// if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21,
// we cannot simply insert the branch but need to check we have branches or not
hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{
RepoID: repoID,
IsDeletedBranch: util.OptionalBoolFalse,
}.ToConds())
if err != nil {
return err
}
if !hasBranch {
if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil {
return fmt.Errorf("repo_module.SyncRepoBranches %d:%s failed: %v", repoID, branchName, err)
}
return nil
}
// if database have branches but not this branch, it means this is a new branch
return db.Insert(ctx, &git_model.Branch{
RepoID: repoID,
Name: branchName,
CommitID: commit.ID.String(),
CommitMessage: commit.Summary(),
PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
})
}
// CreateNewBranchFromCommit creates a new repository branch
func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commit, branchName string) (err error) {
func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, commitID, branchName string) (err error) {
err = repo.MustNotBeArchived()
if err != nil {
return err
}
// Check if branch name can be used
if err := checkBranchName(ctx, repo, branchName); err != nil {
return err
@ -253,7 +283,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
Remote: repo.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName),
Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
Env: repo_module.PushingEnvironment(doer, repo),
}); err != nil {
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {

View file

@ -257,7 +257,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
}
if err = git_model.UpdateBranch(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil {
if err = syncBranchToDB(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil {
return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err)
}

View file

@ -31,7 +31,7 @@
<a href="{{$repo.Link}}/src/commit/{{$result.CommitID | PathEscape}}/{{$result.Filename | PathEscapeSegments}}#L{{.}}"><span>{{.}}</span></a>
{{end}}
</td>
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines | Safe}}</code></td>
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines}}</code></td>
</tr>
</tbody>
</table>

View file

@ -73,8 +73,8 @@
</label>
</div>
<div class="required field">
<label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" required>
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required>
</div>
<div class="text right actions">

View file

@ -53,7 +53,7 @@
<a href="{{$.SourcePath}}/src/commit/{{PathEscape $result.CommitID}}/{{PathEscapeSegments $result.Filename}}#L{{.}}"><span>{{.}}</span></a>
{{end}}
</td>
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines | Safe}}</code></td>
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines}}</code></td>
</tr>
</tbody>
</table>

View file

@ -921,8 +921,8 @@
</label>
</div>
<div class="required field">
<label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" required>
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required>
</div>
<div class="text right actions">

View file

@ -69,9 +69,9 @@
{{end}}
<div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextSource}} code-view{{end}}">
{{if .IsMarkup}}
{{if .FileContent}}{{.FileContent | Safe}}{{end}}
{{if .FileContent}}{{.FileContent}}{{end}}
{{else if .IsPlainText}}
<pre>{{if .FileContent}}{{.FileContent | Safe}}{{end}}</pre>
<pre>{{if .FileContent}}{{.FileContent}}{{end}}</pre>
{{else if not .IsTextSource}}
<div class="view-raw">
{{if .IsImageFile}}
@ -109,7 +109,7 @@
{{if $.EscapeStatus.Escaped}}
<td class="lines-escape">{{if (index $.LineEscapeStatus $idx).Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{if (index $.LineEscapeStatus $idx).HasInvisible}}{{ctx.Locale.Tr "repo.invisible_runes_line"}} {{end}}{{if (index $.LineEscapeStatus $idx).HasAmbiguous}}{{ctx.Locale.Tr "repo.ambiguous_runes_line"}}{{end}}"></button>{{end}}</td>
{{end}}
<td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code | Safe}}</code></td>
<td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code}}</code></td>
</tr>
{{end}}
</tbody>

View file

@ -18596,8 +18596,7 @@
"type": "object",
"required": [
"username",
"email",
"password"
"email"
],
"properties": {
"created_at": {

View file

@ -78,7 +78,7 @@
<input readonly="" value="{{$.TokenToSign}}">
<div class="help">
<p>{{ctx.Locale.Tr "settings.ssh_token_help"}}</p>
<p><code>{{printf "echo -n '%s' | ssh-keygen -Y sign -n gitea -f /path_to_your_privkey" $.TokenToSign}}</code></p>
<p><code>{{printf "echo -n '%s' | ssh-keygen -Y sign -n gitea -f /path_to_PrivateKey_or_RelatedPublicKey" $.TokenToSign}}</code></p>
</div>
<br>
</div>

View file

@ -70,15 +70,16 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
session = loginUser(t, user4.Name)
token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
// Make a new branch in repo1
newBranch := "test_branch"
err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
// Get the commit ID of the default branch
gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
assert.NoError(t, err)
defer gitRepo.Close()
// Make a new branch in repo1
newBranch := "test_branch"
err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
// Make a new tag in repo1
newTag := "test_tag"

View file

@ -72,15 +72,16 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
session = loginUser(t, user4.Name)
token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
// Make a new branch in repo1
newBranch := "test_branch"
err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
// Get the commit ID of the default branch
gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
assert.NoError(t, err)
defer gitRepo.Close()
// Make a new branch in repo1
newBranch := "test_branch"
err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
assert.NoError(t, err)
// Make a new tag in repo1

View file

@ -4,72 +4,55 @@
package integration
import (
"fmt"
"net/http"
"net/url"
"testing"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/tests"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
gitea_context "code.gitea.io/gitea/modules/context"
"github.com/stretchr/testify/assert"
)
func TestViewBranches(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/user2/repo1/branches")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
_, exists := htmlDoc.doc.Find(".delete-branch-button").Attr("data-url")
assert.False(t, exists, "The template has changed")
}
func TestDeleteBranch(t *testing.T) {
defer tests.PrepareTestEnv(t)()
deleteBranch(t)
}
func TestUndoDeleteBranch(t *testing.T) {
func TestBranchActions(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
deleteBranch(t)
htmlDoc, name := branchAction(t, ".restore-branch-button")
assert.Contains(t,
htmlDoc.doc.Find(".ui.positive.message").Text(),
translation.NewLocale("en-US").Tr("repo.branch.restore_success", name),
)
session := loginUser(t, "user2")
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
branch3 := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 3, RepoID: repo1.ID})
branchesLink := repo1.FullName() + "/branches"
t.Run("View", func(t *testing.T) {
req := NewRequest(t, "GET", branchesLink)
MakeRequest(t, req, http.StatusOK)
})
t.Run("Delete branch", func(t *testing.T) {
link := fmt.Sprintf("/%s/branches/delete?name=%s", repo1.FullName(), branch3.Name)
req := NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": GetCSRF(t, session, branchesLink),
})
session.MakeRequest(t, req, http.StatusOK)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.Contains(t, flashCookie.Value, "success%3DBranch%2B%2522branch2%2522%2Bhas%2Bbeen%2Bdeleted.")
assert.True(t, unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 3, RepoID: repo1.ID}).IsDeleted)
})
t.Run("Restore branch", func(t *testing.T) {
link := fmt.Sprintf("/%s/branches/restore?branch_id=%d&name=%s", repo1.FullName(), branch3.ID, branch3.Name)
req := NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": GetCSRF(t, session, branchesLink),
})
session.MakeRequest(t, req, http.StatusOK)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.Contains(t, flashCookie.Value, "success%3DBranch%2B%2522branch2%2522%2Bhas%2Bbeen%2Brestored")
assert.False(t, unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 3, RepoID: repo1.ID}).IsDeleted)
})
})
}
func deleteBranch(t *testing.T) {
htmlDoc, name := branchAction(t, ".delete-branch-button")
assert.Contains(t,
htmlDoc.doc.Find(".ui.positive.message").Text(),
translation.NewLocale("en-US").Tr("repo.branch.deletion_success", name),
)
}
func branchAction(t *testing.T, button string) (*HTMLDoc, string) {
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/branches")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find(button).Attr("data-url")
if !assert.True(t, exists, "The template has changed") {
t.Skip()
}
req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
})
session.MakeRequest(t, req, http.StatusOK)
url, err := url.Parse(link)
assert.NoError(t, err)
req = NewRequest(t, "GET", "/user2/repo1/branches")
resp = session.MakeRequest(t, req, http.StatusOK)
return NewHTMLParser(t, resp.Body), url.Query().Get("name")
}

View file

@ -0,0 +1,139 @@
// Copyright Earl Warren <contact@earl-warren.org>
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"net/url"
"os"
"path"
"testing"
"time"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestActionsUserGit(t *testing.T) {
onGiteaRun(t, testActionsUserGit)
}
func NewActionsUserTestContext(t *testing.T, username, reponame string) APITestContext {
t.Helper()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47})
task.RepoID = repo.ID
task.OwnerID = repoOwner.ID
task.GenerateToken()
actions_model.UpdateTask(db.DefaultContext, task)
return APITestContext{
Session: emptyTestSession(t),
Token: task.Token,
Username: username,
Reponame: reponame,
}
}
func testActionsUserGit(t *testing.T, u *url.URL) {
username := "user2"
reponame := "repo1"
httpContext := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
for _, testCase := range []struct {
name string
head string
ctx APITestContext
}{
{
name: "UserTypeIndividual",
head: "individualhead",
ctx: httpContext,
},
{
name: "ActionsUser",
head: "actionsuserhead",
ctx: NewActionsUserTestContext(t, username, reponame),
},
} {
t.Run("CreatePR "+testCase.name, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
dstPath := t.TempDir()
u.Path = httpContext.GitPath()
u.User = url.UserPassword(httpContext.Username, userPassword)
t.Run("Clone", doGitClone(dstPath, u))
t.Run("PopulateBranch", doActionsUserPopulateBranch(dstPath, &httpContext, "master", testCase.head))
t.Run("CreatePR", doActionsUserPR(httpContext, testCase.ctx, "master", testCase.head))
})
}
}
func doActionsUserPopulateBranch(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) {
return func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
t.Run("AddCommit", func(t *testing.T) {
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
if !assert.NoError(t, err) {
return
}
err = git.AddChanges(dstPath, true)
assert.NoError(t, err)
err = git.CommitChanges(dstPath, git.CommitChangesOptions{
Committer: &git.Signature{
Email: "user2@example.com",
Name: "user2",
When: time.Now(),
},
Author: &git.Signature{
Email: "user2@example.com",
Name: "user2",
When: time.Now(),
},
Message: "Testing commit 1",
})
assert.NoError(t, err)
})
t.Run("Push", func(t *testing.T) {
err := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/heads/" + headBranch).Run(&git.RunOpts{Dir: dstPath})
assert.NoError(t, err)
})
}
}
func doActionsUserPR(ctx, doerCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) {
return func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var pr api.PullRequest
var err error
// Create a test pullrequest
t.Run("CreatePullRequest", func(t *testing.T) {
pr, err = doAPICreatePullRequest(doerCtx, ctx.Username, ctx.Reponame, baseBranch, headBranch)(t)
assert.NoError(t, err)
})
doerCtx.ExpectedCode = http.StatusCreated
t.Run("AutoMergePR", doAPIAutoMergePullRequest(doerCtx, ctx.Username, ctx.Reponame, pr.Index))
// Ensure the PR page works
t.Run("EnsureCanSeePull", doEnsureCanSeePull(ctx, pr))
}
}

View file

@ -0,0 +1,23 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"testing"
"code.gitea.io/gitea/tests"
)
func TestPrivateRepoProject(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// not logged in user
req := NewRequest(t, "GET", "/user31/-/projects")
MakeRequest(t, req, http.StatusNotFound)
sess := loginUser(t, "user1")
req = NewRequest(t, "GET", "/user31/-/projects")
sess.MakeRequest(t, req, http.StatusOK)
}

View file

@ -10,6 +10,8 @@ import (
"strings"
"testing"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
@ -47,12 +49,14 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
CreateRelease string
FlashMessage string
ExpectedStatus int
CheckBranch bool
}{
{
OldRefSubURL: "branch/master",
NewBranch: "feature/test1",
ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test1"),
CheckBranch: true,
},
{
OldRefSubURL: "branch/master",
@ -65,6 +69,7 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
NewBranch: "feature=test1",
ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature=test1"),
CheckBranch: true,
},
{
OldRefSubURL: "branch/master",
@ -94,6 +99,7 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
NewBranch: "feature/test3",
ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test3"),
CheckBranch: true,
},
{
OldRefSubURL: "branch/master",
@ -108,10 +114,15 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
CreateRelease: "v1.0.1",
ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test4"),
CheckBranch: true,
},
}
session := loginUser(t, "user2")
for _, test := range tests {
session := loginUser(t, "user2")
if test.CheckBranch {
unittest.AssertNotExistsBean(t, &git_model.Branch{RepoID: 1, Name: test.NewBranch})
}
if test.CreateRelease != "" {
createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false)
}
@ -125,6 +136,9 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
test.FlashMessage,
)
}
if test.CheckBranch {
unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: 1, Name: test.NewBranch})
}
}
}

View file

@ -2,7 +2,7 @@ import $ from 'jquery';
const {pageData} = window.config;
const initInputCitationValue = async ($citationCopyApa, $citationCopyBibtex) => {
async function initInputCitationValue($citationCopyApa, $citationCopyBibtex) {
const [{Cite, plugins}] = await Promise.all([
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
@ -19,9 +19,9 @@ const initInputCitationValue = async ($citationCopyApa, $citationCopyBibtex) =>
const bibtexOutput = citationFormatter.format('bibtex', {lang});
$citationCopyBibtex.attr('data-text', bibtexOutput);
$citationCopyApa.attr('data-text', apaOutput);
};
}
export function initCitationFileCopyContent() {
export async function initCitationFileCopyContent() {
const defaultCitationFormat = 'apa'; // apa or bibtex
if (!pageData.citationFileContent) return;
@ -39,7 +39,14 @@ export function initCitationFileCopyContent() {
$citationCopyBibtex.toggleClass('primary', isBibtex);
$citationCopyApa.toggleClass('primary', !isBibtex);
};
initInputCitationValue($citationCopyApa, $citationCopyBibtex).then(updateUi);
try {
await initInputCitationValue($citationCopyApa, $citationCopyBibtex);
} catch (e) {
console.error(`initCitationFileCopyContent error: ${e}`, e);
return;
}
updateUi();
$citationCopyApa.on('click', () => {
localStorage.setItem('citation-copy-format', 'apa');

View file

@ -6,7 +6,7 @@ import {initCompColorPicker} from './comp/ColorPicker.js';
import {showGlobalErrorMessage} from '../bootstrap.js';
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
import {svg} from '../svg.js';
import {hideElem, showElem, toggleElem} from '../utils/dom.js';
import {hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter} from '../utils/dom.js';
import {htmlEscape} from 'escape-goat';
import {showTemporaryTooltip} from '../modules/tippy.js';
import {confirmModal} from './comp/ConfirmModal.js';
@ -122,7 +122,8 @@ async function formFetchAction(e) {
const formMethod = formEl.getAttribute('method') || 'get';
const formActionUrl = formEl.getAttribute('action');
const formData = new FormData(formEl);
const [submitterName, submitterValue] = [e.submitter?.getAttribute('name'), e.submitter?.getAttribute('value')];
const formSubmitter = submitEventSubmitter(e);
const [submitterName, submitterValue] = [formSubmitter?.getAttribute('name'), formSubmitter?.getAttribute('value')];
if (submitterName) {
formData.append(submitterName, submitterValue || '');
}
@ -193,6 +194,7 @@ export function initGlobalCommon() {
$('.tabular.menu .item').tab();
initSubmitEventPolyfill();
document.addEventListener('submit', formFetchAction);
document.addEventListener('click', linkAction);
}

View file

@ -1,5 +1,5 @@
import $ from 'jquery';
import {isElemHidden, onInputDebounce, toggleElem} from '../utils/dom.js';
import {isElemHidden, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.js';
import {GET} from '../modules/fetch.js';
const {appSubUrl} = window.config;
@ -40,7 +40,7 @@ export function initCommonIssueListQuickGoto() {
$form.on('submit', (e) => {
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
let doQuickGoto = !isElemHidden($goto);
const submitter = e.originalEvent.submitter;
const submitter = submitEventSubmitter(e.originalEvent);
if (submitter !== $form[0] && submitter !== $input[0] && submitter !== $goto[0]) doQuickGoto = false;
if (!doQuickGoto) return;

View file

@ -7,6 +7,7 @@ import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
import {initImageDiff} from './imagediff.js';
import {showErrorToast} from '../modules/toast.js';
import {submitEventSubmitter} from '../utils/dom.js';
const {csrfToken, pageData, i18n} = window.config;
@ -57,7 +58,7 @@ function initRepoDiffConversationForm() {
const formData = new FormData($form[0]);
// if the form is submitted by a button, append the button's name and value to the form data
const submitter = e.originalEvent?.submitter;
const submitter = submitEventSubmitter(e.originalEvent);
const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit');
if (isSubmittedByButton && submitter.name) {
formData.append(submitter.name, submitter.value);

View file

@ -106,7 +106,7 @@ function switchTitleToTooltip(target) {
/**
* Creating tooltip tippy instance is expensive, so we only create it when the user hovers over the element
* According to https://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order , mouseover event is fired before mouseenter event
* Some old browsers like Pale Moon doesn't support "mouseenter(capture)"
* Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)"
* The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy
* @param e {Event}
*/

View file

@ -194,3 +194,24 @@ export function loadElem(el, src) {
el.src = src;
});
}
// some browsers like PaleMoon don't have "SubmitEvent" support, so polyfill it by a tricky method: use the last clicked button as submitter
// it can't use other transparent polyfill patches because PaleMoon also doesn't support "addEventListener(capture)"
const needSubmitEventPolyfill = typeof SubmitEvent === 'undefined';
export function submitEventSubmitter(e) {
return needSubmitEventPolyfill ? (e.target._submitter || null) : e.submitter;
}
function submitEventPolyfillListener(e) {
const form = e.target.closest('form');
if (!form) return;
form._submitter = e.target.closest('button:not([type]), button[type="submit"], input[type="submit"]');
}
export function initSubmitEventPolyfill() {
if (!needSubmitEventPolyfill) return;
console.warn(`This browser doesn't have "SubmitEvent" support, use a tricky method to polyfill`);
document.body.addEventListener('click', submitEventPolyfillListener);
document.body.addEventListener('focus', submitEventPolyfillListener);
}

View file

@ -1,4 +1,4 @@
import '@webcomponents/custom-elements'; // polyfill for some browsers like Pale Moon
import '@webcomponents/custom-elements'; // polyfill for some browsers like PaleMoon
import './polyfill.js';
import '@github/relative-time-element';