Compare commits

...

100 Commits

Author SHA1 Message Date
Giteabot
62a94ef7a4
Improve job commit description (#30579) (#30709)
Backport #30579 by @yp05327

Fix https://github.com/go-gitea/gitea/issues/30567

When job is a schedule:

![image](https://github.com/go-gitea/gitea/assets/18380374/b07e9d43-e8b7-4ee2-87b3-a7050c3a8ca5)
When it is a normal one:

![image](https://github.com/go-gitea/gitea/assets/18380374/0d58dab9-74bb-421b-8952-0578cdf21a52)

also add a 'space' behind  `:`

![image](https://github.com/go-gitea/gitea/assets/18380374/4cebece0-bfe6-4ad9-b806-e5c49bb9be43)


![image](https://github.com/go-gitea/gitea/assets/18380374/02da7681-474b-4c0f-9dad-b6558f6cb484)

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-26 21:46:32 +02:00
Giteabot
bb6edb2eed
Fix a panic bug when head repository deleting (#30674) (#30676)
Backport #30674 & #30679 by @lunny

When visiting a pull request files which head repository has been
deleted, it will panic because headrepo is nil.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-25 14:22:02 +08:00
silverwind
ad34d22113
Tweak repo buttons on mobile and labeled button border-radius (#30503) (#30525)
Backport of https://github.com/go-gitea/gitea/pull/30503 to v1.22, the
only change excluded is the border-radius changes which aren't necessary
on this branch because the button css was not migrated.

Fixes: https://github.com/go-gitea/gitea/issues/30514
Fixes:
https://github.com/go-gitea/gitea/pull/30288#issuecomment-2057466623

- Fix border-radius regression from
https://github.com/go-gitea/gitea/pull/30475
- Fix and simplify hover state
- Move the modal HTML so it does not interfere with the CSS
- Make the star and unwatch text show on mobile. There is still plenty
of space, below is iPhone 12 viewport size

<img width="696" alt="Screenshot 2024-04-15 at 20 34 03"
src="https://github.com/go-gitea/gitea/assets/115237/af90bb00-4671-4973-a255-8eb44ee6ba8d">
<img width="230" alt="Screenshot 2024-04-15 at 20 31 42"
src="https://github.com/go-gitea/gitea/assets/115237/986ef533-7a01-4bb0-8dcd-fd19e4259e84">
<img width="233" alt="Screenshot 2024-04-15 at 20 31 47"
src="https://github.com/go-gitea/gitea/assets/115237/5b825dd8-0ccc-4d56-9d8f-774abb935b68">
2024-04-24 03:34:25 +00:00
Giteabot
2be724c85d
Fix project name wrapping, remove horizontal margin on header (#30631) (#30654)
Backport #30631 by @silverwind

Enable wrapping of unbroken lines:

<img width="1308" alt="Screenshot 2024-04-22 at 00 31 33"
src="https://github.com/go-gitea/gitea/assets/115237/1a28ade1-d708-4260-96a3-cf508b6dcb79">

Remove extra margin added by nested `.ui.container` on certain
viewports:

Before:
<img width="1305" alt="Screenshot 2024-04-22 at 00 40 23"
src="https://github.com/go-gitea/gitea/assets/115237/d3d8c0d1-380c-4867-b95c-4d53d70d4a93">

After:
<img width="1310" alt="Screenshot 2024-04-22 at 00 40 33"
src="https://github.com/go-gitea/gitea/assets/115237/2ba7b9f2-db2f-4bcc-8cce-5c415625ddea">

---------

Co-authored-by: silverwind <me@silverwind.io>
2024-04-23 10:48:36 +00:00
Lunny Xiao
5c12e79445
Fix wrong table name (#30557) (#30651)
Backport #30557 

The table name should be `oauth2_application` but `o_auth2_application`

Caused by

https://github.com/go-gitea/gitea/pull/21316/files#diff-9610efbc608a41f1f2eaff5790423f0a187906f6ff0beb23a5e8d18366cc2ccfR38
2024-04-23 17:15:16 +08:00
Lunny Xiao
0027ca8ce7
Use maintained gziphandler (#30592) (#30637)
Replace #27894
Backport #30592

Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-23 02:56:49 +00:00
Giteabot
682a076768
Hide diff stats on empty PRs (#30629) (#30645)
Backport #30629 by @silverwind

When a PR is empty, e.g. has neither additions nor deletions, we don't
need to show this:

<img width="125" alt="Screenshot 2024-04-21 at 23 25 38"
src="https://github.com/go-gitea/gitea/assets/115237/0b987eb5-66f5-4b9b-b5aa-7e9e267e9b52">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-22 14:43:44 +00:00
Giteabot
706fc596a0
Use correct hash for "git update-index" (#30626) (#30634)
Backport #30626 by @wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-22 13:55:01 +00:00
Giteabot
4aba4f8dc3
Fix dropdown text ellipsis (#30628) (#30633)
Backport #30628 by @wxiaoguang

Follow
https://github.com/go-gitea/gitea/pull/30547#discussion_r1573866519

Fix #30624

The Fomantic UI Dropdown wasn't designed to work that way, its "text"
element might contain images. So the "overflow" shouldn't be added to
any general dropdown text.


![image](https://github.com/go-gitea/gitea/assets/2114189/f6ceaabd-bc89-4bf2-baa2-a6f0324c1962)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-22 06:52:08 +00:00
Giteabot
5dbe7496c7
Add --skip-db option to dump command (#30613) (#30630)
Backport #30613 by @kemzeb

Attempts to resolve #28720.

Co-authored-by: Kemal Zebari <60799661+kemzeb@users.noreply.github.com>
2024-04-22 14:25:07 +08:00
Giteabot
fc376c863a
Refactor and fix archive link bug (#30535) (#30570)
Backport #30535 by wxiaoguang

Regression of #29920
Fixes: https://github.com/go-gitea/gitea/issues/30569

Also this is a rewriting to eliminate the remaining jQuery usages from
code.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2024-04-21 23:12:47 +02:00
Giteabot
ea2ea8ef28
Fix package list performance (#30520) (#30616)
Backport #30520 by @KN4CK3R

Fixes #28255

The new query uses the id field to sort by "newer". This most not be
correct (usually it is) but it's faster (see #28255).
If someone has a better idea, please propose changes.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-04-21 08:50:50 +08:00
Giteabot
b4e1874a87
Fix links in PyPI Simple Repository API page (#30594) (#30612)
Backport #30594 by wxiaoguang

Thanks to @Zottelchen for looking into problem and proposing the fix.

Ref: https://github.com/astral-sh/uv/issues/3017 ,
https://peps.python.org/pep-0503/

This PR's change is from Zottelchen's work.

And I by the way rename the `$p` to `$pd` because `p` is used as
"package" in code, while `pd` is used as "package description".

----

Co-authored-by: Zottelchen

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-20 03:46:26 +00:00
Giteabot
60f049318d
Use action user as the trigger user of schedules (#30581) (#30610)
Backport #30581 by @yp05327

Follow https://github.com/go-gitea/gitea/pull/30357

When user push to default branch, the schedule trigger user will be the
user.
When disable then enable action units in settings, the schedule trigger
user will be action user.
When repo is a mirror, the schedule trigger user will be action user. (
before it will return error, fixed by #30357)

As scheduled job is a cron, the trigger user should be action user from
Gitea, not a real user.

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-20 10:45:53 +08:00
Giteabot
7eaf7907d7
Fix HEAD method for robots.txt (#30603) (#30605)
Backport #30603 by @wxiaoguang

Fix #30601


```
~$ curl --head localhost:3000/robots.txt
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 5
Content-Type: text/plain; charset=utf-8
Last-Modified: Wed, 19 Jul 2023 04:56:12 GMT
X-Gitea-Debug: RUN_MODE=dev
Date: Fri, 19 Apr 2024 12:59:44 GMT
```

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-19 22:32:49 +02:00
Giteabot
199397a852
Avoid importing modules/web/middleware in modules/session (#30584) (#30589)
Backport #30584 by @wolfogre

Related to #30375.

It doesn't make sense to import `modules/web/middleware` and
`modules/setting` in `modules/web/session` since the last one is more
low-level.

And it looks like a workaround to call `DeleteLegacySiteCookie` in
`RegenerateSession`, so maybe we could reverse the importing by
registering hook functions.

Co-authored-by: Jason Song <i@wolfogre.com>
2024-04-19 07:44:24 +00:00
Giteabot
a1a65b49a8
Enable npm cache on setup-node action (#30577) (#30586)
Backport #30577 by @silverwind

Enable npm dependency cache in
[setup-node](https://github.com/actions/setup-node). This should work
reliably and across branches as well.

Co-authored-by: silverwind <me@silverwind.io>
2024-04-19 09:16:42 +02:00
Giteabot
42019677e6
Improve "Reference in new issue" modal (#30547) (#30574)
Backport #30547 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/29994

Also some misc enhancements done to the form in the modal.

<img width="840" alt="Screenshot 2024-04-17 at 23 02 55"
src="https://github.com/go-gitea/gitea/assets/115237/e71fba55-55cd-4e48-a497-6b1025c36a43">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-18 19:58:37 +00:00
Giteabot
0184bd1599
Fixup app.example.ini for task section, which is now queue.task (#30555) (#30563)
Backport #30555 by xor-gate

Config section `[task]` has been deprecated in favor of `[queue.task]`

Co-authored-by: Jerry Jacobs <xor-gate@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-18 15:16:01 +02:00
Giteabot
4fec3a3d44
Add form field id generation, remove duplicated ids (#30546) (#30561)
Backport #30546 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/30384

On repo settings page, there id `repo_name` was used 5 times on the same
page, some in modal and such. I think we are better off just
auto-generating these IDs in the future so that labels link up with
their form element.

Ideally this id generation would be done in backend in a subtemplate,
but seeing that we already have similar JS patches for checkboxes, I
took the easy path for now.

I also checked that these `#repo_name` were not in use in JS and the
only case where this id appears in JS is on the migration page where
it's still there.

Co-authored-by: silverwind <me@silverwind.io>
2024-04-18 10:33:32 +00:00
Giteabot
b4a38318c3
Fix border-radius on view, blame and code search (#30545) (#30560)
Backport #30545 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/30540

1. Fix all these boxes by adding `bottom attached` and removing a
problematic CSS rule:

<img width="1319" alt="Screenshot 2024-04-17 at 22 25 31"
src="https://github.com/go-gitea/gitea/assets/115237/346445a4-4944-4003-a1ef-6f5b0eda624e">
<img width="643" alt="Screenshot 2024-04-17 at 22 21 18"
src="https://github.com/go-gitea/gitea/assets/115237/10f17ed3-9ad6-48de-92fa-bac6621815b9">

2. Change the "last commit" box to `ui segment` which has correct
border-radius. Also included is a tiny tweak to make author name ellipse
instead of wrap.

<img width="1331" alt="Screenshot 2024-04-17 at 22 23 23"
src="https://github.com/go-gitea/gitea/assets/115237/285fbd45-ced0-4d33-abe3-7384ffa03188">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-18 10:04:47 +00:00
Giteabot
c9633f2d74
Disable enter key for accepting code completion in Monaco (#30548) (#30559)
Backport #30548 by @silverwind

Fixes https://github.com/go-gitea/gitea/issues/28114 and behaviour
matches vscode on desktop as well.

Co-authored-by: silverwind <me@silverwind.io>
2024-04-18 11:38:32 +02:00
Giteabot
d88958bb99
Fix branch_protection api shows users/teams who has no readAccess (#30291) (#30544)
Backport #30291 by @edwardzhanged

Add some logic in `convert.ToBranchProtection` to return only the names
associated with readAccess instead of returning all names. This will
ensure consistency in behavior between the frontend and backend.
Fixes: #27694

Co-authored-by: Edward Zhang <45360012+edwardzhanged@users.noreply.github.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-authored-by: wenzhuo.zhang <wenzhuo.zhang@geely.com>
2024-04-17 13:50:49 +00:00
Giteabot
5c55851c2e
Run go generate and go vet on all packages (#30529) (#30541)
Backport #30529 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/30512

I think this does mean those tools would run on a potential `vendor`
directory, but I'm not sure we really support vendoring of dependencies
anymore.

`release` has a `vendor` prerequisite so likely the source tarballs
contain vendor files?

Co-authored-by: silverwind <me@silverwind.io>
2024-04-17 13:34:30 +02:00
Giteabot
caeed3af6e
Fix install page checkboxes and dropdown width (#30526) (#30538)
Backport #30526 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/30523

1. Fix checkbox rendering:

<img width="406" alt="Screenshot 2024-04-16 at 21 37 03"
src="https://github.com/go-gitea/gitea/assets/115237/42df99b0-58c7-47d1-b99d-0c15250560c7">

2. Fix width of selection dropdowns (was too small):

<img width="826" alt="Screenshot 2024-04-16 at 21 37 09"
src="https://github.com/go-gitea/gitea/assets/115237/e006d0d6-ac3d-4804-94de-b2c3c8a5900d">

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <dev.lh@web.de>
2024-04-17 10:51:38 +02:00
Giteabot
d5525b7143
Tweak and fix toggle checkboxes (#30527) (#30531)
Backport #30527 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/30524. Slightly restyled
them so that the "knob" is contained inside the background.

<img width="179" alt="Screenshot 2024-04-16 at 21 58 09"
src="https://github.com/go-gitea/gitea/assets/115237/be94517b-9cb7-46e2-ae96-fcf6767ce4ba">
<img width="187" alt="Screenshot 2024-04-16 at 21 58 50"
src="https://github.com/go-gitea/gitea/assets/115237/c13a1959-5c5a-4e88-9225-e5f6fb72e3e0">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-17 15:15:02 +08:00
Giteabot
f9a025f6a3
Fix empty field login_name in API response JSON when creating user (#30511) (#30516)
Backport #30511 by @yp05327

Fix #30508

ps: if `sourceID` is not set, `LoginName` will be ignored

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-16 19:34:48 +02:00
Giteabot
00179f637d
Fix various overflows on actions view (#30344) (#30505)
Backport #30344 by @silverwind

Fix a number of text overflow issues in actions view and run list. Also
improve mobile view of run list.

Fixes: https://github.com/go-gitea/gitea/issues/30328

<img width="782" alt="Screenshot 2024-04-08 at 23 10 16"
src="https://github.com/go-gitea/gitea/assets/115237/3d9f9f88-3eab-44a0-8144-30c2b58b24cb">
<img width="935" alt="Screenshot 2024-04-08 at 23 17 46"
src="https://github.com/go-gitea/gitea/assets/115237/581d73ea-a31d-416b-be3a-47313b879b12">
<img width="1008" alt="Screenshot 2024-04-08 at 23 49 05"
src="https://github.com/go-gitea/gitea/assets/115237/c5d10565-f285-477f-8659-1caf94797647">
<img width="397" alt="Screenshot 2024-04-08 at 23 55 30"
src="https://github.com/go-gitea/gitea/assets/115237/368aaa75-1903-4058-9d75-d1fe91c564d6">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-16 08:24:06 +08:00
Giteabot
f52b1db305
Convert max file name length to 255 (#30489) (#30504)
Backport #30489 by @yp05327

Quick/Partly fix #29907

In Linux and MacOS, by default the max file name length is 255.
In windows, it depends on the version and settings, and has no file name
length limitation, but has path length limitation.
By default it is 260, considering path length is longer than filename,
so I think it is ok to do this.

For Windows, see
https://learn.microsoft.com/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
For Linux, see
https://github.com/torvalds/linux/blob/master/include/uapi/linux/limits.h#L12-L13
For MacOS, see
https://discussions.apple.com/thread/254788848?sortBy=best

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-15 21:42:15 +02:00
Giteabot
3d31b5963e
Fix code owners will not be mentioned when a pull request comes from a forked repository (#30476) (#30496)
Backport #30476 by @lunny

Fix #30277
Caused by #29783

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-15 19:08:16 +02:00
Lunny Xiao
7ffc0acc42
Fix commit status cache which missed target_url (#30426) (#30444)
Fix #30421
Backport #30426

Co-authored-by: Jason Song <i@wolfogre.com>
2024-04-15 09:31:14 +00:00
Giteabot
2efc81d200
Fix overflow on issue dependency (#30484) (#30494)
Backport #30484 by @silverwind

Small tweak here to prevent this and likely other events from
overflowing in the timeline:

<img width="895" alt="Screenshot 2024-04-14 at 22 53 17"
src="https://github.com/go-gitea/gitea/assets/115237/001b4f6b-f649-44ff-b2f0-c8e0dedeb384">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-15 16:58:09 +08:00
Giteabot
b266c78cff
Improve "must-change-password" logic and document (#30472) (#30478)
Backport #30472 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-04-14 17:49:16 +00:00
Giteabot
e64926c519
fix: Fix to delete cookie when AppSubURL is non-empty (#30375) (#30469)
Backport #30375 by @jtran

Cookies may exist on "/subpath" and "/subpath/" for some legacy reasons
(eg: changed CookiePath behavior in code). The legacy cookie should be
removed correctly.

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Kyle D <kdumontnu@gmail.com>
2024-04-14 12:27:36 +00:00
Giteabot
0352b99221
Rewrite and restyle reaction selector and enable no-sizzle eslint rule (#30453) (#30473)
Backport #30453 by @silverwind

Enable `no-sizzle` lint rule, there was only one use in
`initCompReactionSelector` which I have rewritten as follows:

- Remove all jQuery except the necessary fomantic dropdown init
- Remove the recursion, instead bind event listeners to common parent
container nodes

Did various tests, works with our without attachments, in diff view and
in diff comments inside comment list.

Additionally the style of reactions now matches between code comments
and issue comments:

<img width="275" alt="Screenshot 2024-04-13 at 14 58 10"
src="https://github.com/go-gitea/gitea/assets/115237/9d08f188-8661-4dd9-bff4-cad6d6d09cab">

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-14 11:58:48 +00:00
Giteabot
dd12861011
Fix JS error when opening to expanded code comment (#30463) (#30470)
Backport #30463 by silverwind

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-14 18:46:21 +08:00
Giteabot
3735797b33
Fix network error when open/close organization/individual projects and redirect to project page (#30387) (#30465)
Backport #30387 by @yp05327

Follow #27734


![image](https://github.com/go-gitea/gitea/assets/18380374/02ed6b9a-cbb6-4f49-a54a-ca76a0d052a9)

Updated:
Redirect to project page instead of project list page.

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-13 17:53:14 +00:00
Giteabot
92f4cd9461
Avoid losing token when updating mirror settings (#30429) (#30464)
Backport #30429 by @wolfogre

Fix #30416.

Before (it shows as "Unset" while there's a token):

<img width="980" alt="image"
src="https://github.com/go-gitea/gitea/assets/9418365/d7148e3e-62c9-4d2e-942d-3d795b79515a">

After:

<img width="977" alt="image"
src="https://github.com/go-gitea/gitea/assets/9418365/24aaa1db-5baa-4204-9081-470b15ea72b5">

The username shows as "oauth2" because of
f9fdac9809/services/migrations/dump.go (L99)

I have checked that all usage of `MirrorRemoteAddress` has been updated.

<img width="1806" alt="image"
src="https://github.com/go-gitea/gitea/assets/9418365/2f042501-2824-4511-9203-c84a6731a02d">

However, it needs to be checked again when backporting.

Co-authored-by: Jason Song <i@wolfogre.com>
2024-04-14 00:57:53 +08:00
Giteabot
846888fb15
Fix label rendering (#30456) (#30460)
Backport #30456 by wxiaoguang

1. Check whether the label is for an issue or a pull request.
2. Don't use space to layout
3. Make sure the test strings have trailing spaces explicitly, to avoid
some IDE removing the trailing spaces automatically.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-13 10:33:32 +00:00
Giteabot
764878f050
Fix mirror error when mirror repo is empty (#30432) (#30455)
Backport #30432 by @yp05327

Fix #30424

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-13 09:20:10 +00:00
Giteabot
fd2184e234
Fix admin notice view-detail (#30450) (#30458)
Backport #30450 by @silverwind

Fix https://github.com/go-gitea/gitea/issues/30434, regression from
https://github.com/go-gitea/gitea/pull/30115.

I also removed the date insertion into the modal which was also broken
since that date was switched to `absolute-date` because I see no real
purpose to putting that date into the modal.

Result:

<img width="1038" alt="image"
src="https://github.com/go-gitea/gitea/assets/115237/aa2eb8b4-73dc-4d98-9b80-3f276f89d9e5">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-13 10:49:44 +02:00
Giteabot
b941d7485b
Change the default maxPerPage for gitbucket (#30392) (#30425)
Backport #30392 by @jam7

This patch improves the migration from gitbucket to gitea.

The gitbucket uses it's own internal perPage value (= 25) for paging and
ignore per_page arguments in the requested URL. This cause gitea to
migrate only 25 issues and 25 PRs from gitbucket repository. This may
not happens on old gitbucket. But recent gitbucket 4.40 or 4.38.4 has
this problem.

This patch change to use this internally hardcoded perPage of gitbucket
as gitea's maxPerPage numer when migrating from gitbucket. There are
several perPage values in gitbucket like 25 for Isseus/PRs and 10 for
Releases. Some of those API doesn't support paging yet. It sounds
difficult to implement, but using the minimum number among them worked
out very well. So, I use 10 in this patch.

Brief descriptions of problems and this patch are also available in
https://github.com/go-gitea/gitea/issues/30316.

In addition, I'm not sure what kind of test cases are possible to write
here. It's a test for migration, so it requires testing gitbucket server
and gitea server, I guess. Please let me know if it is possible to write
such test cases here. Thanks!

Co-authored-by: Kazushi (Jam) Marukawa <jam@pobox.com>
2024-04-12 11:23:34 +00:00
Giteabot
cabe4e0dc6
Fix rename branch 500 when the target branch is deleted but exist in database (#30430) (#30438)
Backport #30430 by @lunny

Fix #30428

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-12 18:56:14 +08:00
Giteabot
358b28cec0
Fix the spacing issue in the Project view (#30415) (#30423)
Backport #30415 by @HEREYUA

**fix**:  [#30388](https://github.com/go-gitea/gitea/issues/30388)

**before**


![image](https://github.com/go-gitea/gitea/assets/37935145/52ca7311-dca4-4430-9a37-3c45b08fe3dd)


**after**


![image](https://github.com/go-gitea/gitea/assets/37935145/6b75ce69-4423-4ea4-99a1-d7234287c5c0)

Co-authored-by: HEREYUA <37935145+HEREYUA@users.noreply.github.com>
2024-04-12 09:23:00 +00:00
Giteabot
6c228fe5d6
Limit the max line length when parsing git grep output (#30418) (#30427)
Backport #30418 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-12 12:13:14 +08:00
Giteabot
7aa68d6f86
Split issue edit code from repo-legacy.js into its own file (#30419) (#30422)
Backport #30419 by wxiaoguang

Follow Split `index.js` to separate files (#17315)

It's time to move some code away from the messy "legacy" file.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-12 11:11:57 +08:00
Giteabot
81b5938b2c
Check the token's owner and repository when registering a runner (#30406) (#30411)
Backport #30406 by @Zettat123

Fix #30378

Co-authored-by: Zettat123 <zettat123@gmail.com>
2024-04-11 10:57:34 +02:00
Giteabot
69cc79173d
Avoid user does not exist error when detecting schedule actions when the commit author is an external user (#30357) (#30409)
Backport #30357 by @yp05327


![image](https://github.com/go-gitea/gitea/assets/18380374/ddf6ee84-2242-49b9-b066-bd8429ba4d76)

When repo is a mirror, and commit author is an external user, then
`GetUserByEmail` will return error.

reproduce/test:
- mirror Gitea to your instance
- disable action and enable it again, this will trigger
`DetectAndHandleSchedules`

ps: also follow #24706, it only fixed normal runs, not scheduled runs.

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-11 07:51:02 +00:00
Giteabot
40f1f770e6
Update actions variables documents (#30394) (#30405)
Backport #30394 by @lunny

Fix #30393

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Zettat123 <zettat123@gmail.com>
2024-04-11 12:03:13 +08:00
Giteabot
7287267b35
Minor color tweaks (#30397) (#30398)
Backport #30397 by @silverwind

New approach to color shades: Stem all colors off the body color
`#1b1f23` using [this](https://pinetools.com/darken-color) and
[this](https://pinetools.com/lighten-color) tool. The differences are
very subtle, but it will give a more consistent color scheme until
https://github.com/go-gitea/gitea/issues/30160.

<img width="1342" alt="Screenshot 2024-04-10 at 20 44 16"
src="https://github.com/go-gitea/gitea/assets/115237/75b65797-2521-46ea-91d8-d76f77b591b1">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-11 10:14:28 +08:00
Giteabot
6c5b088aa4
Various improvements for long file and commit names (#30374) (#30386)
Backport #30374 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/29438

This contains numerous enhancements for how large commit messages and
large filenames render. Another notable change is that the file path is
no longer cut off by backend at 30 chars, but rendered in full with
wrapping.

<img width="1329" alt="Screenshot 2024-04-09 at 21 53 57"
src="https://github.com/go-gitea/gitea/assets/115237/5ccbb3d6-643a-4f60-ba79-3572b36d5182">
<hr>
<img width="711" alt="Screenshot 2024-04-09 at 21 44 24"
src="https://github.com/go-gitea/gitea/assets/115237/6ffe8fbb-407c-4aa7-b591-3d80daea7d57">
<hr>
<img width="439" alt="Screenshot 2024-04-09 at 21 19 03"
src="https://github.com/go-gitea/gitea/assets/115237/1ec7f6e9-2fd8-4841-87eb-6ca02ab9cd61">
<hr>
<img width="444" alt="Screenshot 2024-04-09 at 21 18 52"
src="https://github.com/go-gitea/gitea/assets/115237/70931b9e-5841-477e-b3bc-98f8d2662964">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-10 08:56:21 +02:00
Giteabot
88b3d192a0
Fix line height on inline code preview (#30372) (#30385)
Backport #30372 by @silverwind

Fixes https://github.com/go-gitea/gitea/issues/30353.

I don't know what causes `code-inner` to not inherit `line-height` from
its direct parent `.lines-code` but instead from grandparent `.markup`
even thought MDN tells me it's
[inherited](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#formal_definition).
This causes no negative impact on other code views, so I think it's the
best solution.

Co-authored-by: silverwind <me@silverwind.io>
2024-04-10 05:49:40 +00:00
Giteabot
021ee8454a
Fix label-list rendering in timeline, decrease gap (#30342) (#30380)
Backport #30342 by @silverwind

Not sure exactly when this regressed, but has been a while I think.

Before:

<img width="895" alt="Screenshot 2024-04-08 at 22 46 50"
src="https://github.com/go-gitea/gitea/assets/115237/9b1788f8-017e-4fe1-8ab9-938e0d76fb41">

After:

<img width="689" alt="Screenshot 2024-04-08 at 23 00 58"
src="https://github.com/go-gitea/gitea/assets/115237/90193df9-5c24-4a1a-96fe-3d4e8392063c">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-10 05:36:04 +00:00
Giteabot
189cfc1be1
Fix ambiguous id when fetch Actions tasks (#30382) (#30383)
Backport #30382 by @wolfogre

Fix regression of #30331.

```txt
time="2024-04-10T02:23:49Z" level=error msg="failed to fetch task" func="[fetchTask]" file="[poller.go:91]" error="unknown: rpc error: code = Internal desc = pick task: CreateTaskForRunner: Error 1052 (23000): Column 'id' in field list is ambiguous"
```

I have tested it in my local env, and it should work now.

Co-authored-by: Jason Song <i@wolfogre.com>
2024-04-10 03:24:31 +00:00
Giteabot
9a18dcbfbe
Fix actions design about default actions download url (#30360) (#30371)
Backport #30360 by @lunny

Fix #30359

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-10 10:07:34 +08:00
silverwind
b8e9d3c553
Fix and rewrite contrast color calculation, fix project-related bugs (#30326)
Backport https://github.com/go-gitea/gitea/pull/30237 to 1.22. Also
includes https://github.com/go-gitea/gitea/pull/30183. Both were clean
cherry-picks.

Co-authored-by: Giteabot <teabot@gitea.io>
2024-04-09 09:45:29 +00:00
Giteabot
82992cf32d
Performance optimization for git push (#30104) (#30348)
Backport #30104 by @lunny

Agit returned result should be from `ProcReceive` hook but not
`PostReceive` hook. Then for all non-agit pull requests, it will not
check the pull requests for every pushing `refs/pull/%d/head`.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-09 06:34:30 +00:00
Giteabot
4c8c10b3df
Reduce checkbox size to 15px (#30346) (#30347)
Backport #30346 by @silverwind

16 seems to big, 14 too small. Let's do 15. Alignment:

<img width="181" alt="image"
src="https://github.com/go-gitea/gitea/assets/115237/f2988611-dee2-492e-a18f-dc5ab3a1cd6c">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-09 08:06:39 +02:00
Giteabot
e0b9638191
Fix missed doer (#30231) (#30343)
Backport #30231 by @lunny

Fix #29879

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-09 02:42:45 +03:00
Giteabot
52bdeb4d45
Avoid running action when action unit is disabled after workflows detected (#30331) (#30338)
Backport #30331 by @yp05327

Fix #30243

We only checking unit disabled when detecting workflows, but not in
runner `FetchTask`.
So if a workflow was detected when action unit is enabled, but disabled
later, `FetchTask` will still return these detected actions.

Global setting: repo.ENABLED and repository.`DISABLED_REPO_UNITS` will
not effect this.

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-08 14:34:44 +00:00
Lunny Xiao
d8505a949d
Fix create commit status (#30225)
Partially backport #30223 

This PR uses the service layer `CreateCommitstatus` method instead of
the git model method.
2024-04-08 13:15:23 +00:00
Giteabot
22a18e6cbf
Avoid showing Failed to change the default wiki branch if repo has no wiki when saving repo settings (#30329) (#30337)
Backport #30329 by @yp05327

If repo does not have wiki, we should return after save the default wiki
branch into DB.
Or you will always see `Failed to change the default wiki branch` error.

Co-authored-by: yp05327 <576951401@qq.com>
2024-04-08 20:48:43 +08:00
Giteabot
c541616f1c
Fix oauth2 builtin application logic (#30304) (#30327)
Backport #30304 by wxiaoguang

Fix #29074 (allow to disable all builtin apps) and don't make the doctor
command remove the builtin apps.

By the way, rename refobject and joincond to camel case.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-08 15:46:21 +08:00
Giteabot
db370c47a6
Add --page-spacing variable, fix admin dashboard notice (#30302) (#30323)
Backport #30302 by @silverwind

Fixes https://github.com/go-gitea/gitea/issues/30293 and introduce the
`--page-spacing` variable which holds the spacing between the elements
on the page. This is working vertically for all pages, including ones
that have fomantic grid, and horizontally for all that use
`flex-container`.

The `.page-content > :first-child:not(.secondary-nav)` selector uses
margin which in some cases enables to adjacent margins to overlap, which
is nice.

<img width="1320" alt="Screenshot 2024-04-06 at 01 35 19"
src="https://github.com/go-gitea/gitea/assets/115237/3e81e707-e9ff-4b7f-a211-3d98f4f85353">
---
<img width="1327" alt="Screenshot 2024-04-06 at 01 35 45"
src="https://github.com/go-gitea/gitea/assets/115237/aad196c0-9e21-4c06-ae59-7e33a76c61e1">
---
<img width="1321" alt="Screenshot 2024-04-06 at 01 35 31"
src="https://github.com/go-gitea/gitea/assets/115237/785f6c5d-08b6-4e66-aa16-aeca7cfed3ad">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-08 02:04:24 +00:00
Giteabot
cc95def2d2
Some NuGet package enhancements (#30280) (#30324)
Backport #30280 by @KN4CK3R

Fixes #30265

1. Read second type of dependencies
2. Render `Description` and `ReleaseNotes`

old:

![grafik](https://github.com/go-gitea/gitea/assets/1666336/abac057c-11cd-4d25-b196-01ff899d948e)

new:

![grafik](https://github.com/go-gitea/gitea/assets/1666336/35302273-740c-481a-a031-1f80d2d7d336)

The NuGet spec does not specify what kind of text can be stored in the
description but we can best guess markdown. The official NuGet registry
just [converts the newlines to html
lines](https://www.nuget.org/packages/rb.Firefox#readme-body-tab).

3. Extract and render the readme. This is the new and better place to
store larger text than in the description. The content is markdown.

![grafik](https://github.com/go-gitea/gitea/assets/1666336/f442264e-3735-4b55-92c4-3b89a8ebafb0)

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Benjamin Heemann <benjamin.heemann@raith.de>
2024-04-07 17:13:36 +00:00
Giteabot
10d83ae643
Action view mobile improvements and fixes (#30309) (#30320)
Backport #30309 by @silverwind

Fix the action issue in https://github.com/go-gitea/gitea/issues/30303,
specifically:

- Use opaque step header hover background to avoid transparency issue
- Un-sticky the `action-view-left` on mobile, it would otherwise overlap
into right view
- Improve commit summary, let it wrap
- Fix and comment z-indexes
- Tweak width for run-list-item-right so it wastes less space on desktop
- Synced latest changes to console colors from dark to light theme

<img width="467" alt="Screenshot 2024-04-06 at 18 58 15"
src="https://github.com/go-gitea/gitea/assets/115237/8ad26b72-6cd9-4522-8ad1-6fd86b2d0d53">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-07 15:22:47 +00:00
Giteabot
d26ec5f2eb
Fix checkboxes on mobile view, remove some dead css (#30308) (#30319)
Backport #30308 by @silverwind

Fix the checkbox issues in
https://github.com/go-gitea/gitea/issues/30303 which were existing
problems with these selectors, but made visible with
https://github.com/go-gitea/gitea/pull/30162.

There is a lot of dead/useless CSS in `form.css`, I only fixed the two
problems and remove CSS that was definitely not in use or needed.

<img width="369" alt="Screenshot 2024-04-06 at 18 00 08"
src="https://github.com/go-gitea/gitea/assets/115237/720f178b-1b22-48d4-8704-becb8ce66129">
<img width="405" alt="Screenshot 2024-04-06 at 18 00 28"
src="https://github.com/go-gitea/gitea/assets/115237/61c0f8ec-34af-46c5-a3fa-7c5c4d30c7d2">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-07 21:10:40 +08:00
Giteabot
5a6f7353d3
Clean up log messages (#30313) (#30318)
Backport #30313 by wxiaoguang

`log.Xxx("%v")` is not ideal, this PR adds necessary context messages.
Remove some unnecessary logs.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-07 11:44:09 +00:00
Giteabot
a29e505c15
Refactor startup deprecation messages (#30305) (#30312)
Backport #30305 by wxiaoguang

It doesn't change logic, it only does:

1. Rename the variable and function names
2. Use more consistent format when mentioning config section&key
3. Improve some messages

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-07 09:43:54 +08:00
Giteabot
be5518eadd
Markup color and font size fixes (#30282) (#30310)
Backport #30282 by @silverwind

1. Distinguish inline an block code with new CSS variable
`--color-markup-code-inline`
2. Various color tweaks, better contrast from background

<img width="447" alt="Screenshot 2024-04-05 at 00 51 00"
src="https://github.com/go-gitea/gitea/assets/115237/93e069f4-6807-4f2c-9331-2d69730919d4">
<img width="456" alt="Screenshot 2024-04-05 at 00 50 44"
src="https://github.com/go-gitea/gitea/assets/115237/0dc9c745-c531-40fa-94ec-b0ba10bd7ccf">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-07 05:48:41 +08:00
Giteabot
95caad7750
Always use octicon-eye on watch button (#30288) (#30307)
Backport #30288 by @silverwind

This might appear odd but I think it's the right thing to do: On Github,
the "Watch" button always has the open eye icon:

<img width="177" alt="Screenshot 2024-04-05 at 08 26 48"
src="https://github.com/go-gitea/gitea/assets/115237/0c1188d1-145b-4c6d-909f-2e1460499941">
<img width="179" alt="Screenshot 2024-04-05 at 08 26 40"
src="https://github.com/go-gitea/gitea/assets/115237/e29d91fa-f122-4e10-9589-f79c1d612cf9">

On Gitea, while watching, the icon is this and this sometimes confuses
me slightly, being used to above:

<img width="158" alt="Screenshot 2024-04-05 at 08 29 08"
src="https://github.com/go-gitea/gitea/assets/115237/3301021b-744e-409f-a9d8-887ec2772fdc">

After this PR, both states will use the same icon:

<img width="145" alt="Screenshot 2024-04-05 at 08 26 27"
src="https://github.com/go-gitea/gitea/assets/115237/8addfa5b-c009-4bdb-bfa1-4f3dfaffa4cd">
<img width="161" alt="Screenshot 2024-04-05 at 08 26 33"
src="https://github.com/go-gitea/gitea/assets/115237/cef383e6-2cc0-460f-a4d3-83ebb321debe">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-06 23:42:11 +08:00
Giteabot
45e8a884b8
Fix code block style for code preview (#30298) (#30306)
Backport #30298 by wxiaoguang

Fix #30292

To avoid unnecessary style overriding, use "div" instead of "code"

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-06 12:34:38 +00:00
wxiaoguang
02bf0a8cd7
Fix view commit link (#30297) (#30299)
Backport #30297
2024-04-06 05:38:26 +08:00
Giteabot
d09ddb5250
Add gap to commit status details (#30284) (#30290)
Backport #30284 by @silverwind

Co-authored-by: silverwind <me@silverwind.io>
2024-04-05 10:44:03 -04:00
Giteabot
1d77df82cf
Upgrade golang.org/x/net to v0.24.0 (#30283) (#30286)
Backport #30283 by @silverwind

Result of `go get -u golang.org/x/net; make tidy`.

This is related to the following vulncheck warning:
```
There are 2 vulnerabilities in modules that you require that are
neither imported nor called. You may not need to take any action.
See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details.

Vulnerability #1: GO-2024-2687
    HTTP/2 CONTINUATION flood in net/http
  More info: https://pkg.go.dev/vuln/GO-2024-2687
  Module: golang.org/x/net
    Found in: golang.org/x/net@v0.22.0
    Fixed in: golang.org/x/net@v0.23.0

Vulnerability #2: GO-2022-0470
    No access control in github.com/blevesearch/bleve and bleve/v2
  More info: https://pkg.go.dev/vuln/GO-2022-0470
  Module: github.com/blevesearch/bleve/v2
    Found in: github.com/blevesearch/bleve/v2@v2.3.10
    Fixed in: N/A
```

Co-authored-by: silverwind <me@silverwind.io>
2024-04-05 05:31:04 +02:00
Giteabot
c8570b73af
Commit-Dropdown: Show Author of commit if available (#30272) (#30285)
Backport #30272 by @sebastian-sauer

As in commits page we show the author of the commit in the commits
dropdown and not the committer.

Commits Page:
![Screenshot from 2024-04-03
22-34-41](https://github.com/go-gitea/gitea/assets/1135157/1c7c5c19-6d0a-4176-8a87-7bca6a0c6dc8)

and the same contents in our dropdown:

![image](https://github.com/go-gitea/gitea/assets/1135157/aa094af2-c369-47ac-9c27-ca208d1d03f0)


fixes #29588

Co-authored-by: sebastian-sauer <sauer.sebastian@gmail.com>
2024-04-05 10:00:20 +08:00
Giteabot
db214817f8
update mailer example config, remove deprecated HOST (#30267) (#30274) 2024-04-03 19:18:43 -04:00
Giteabot
f45df3e3f9
Close file in the Upload func (#30262) (#30270)
Co-authored-by: guangwu <guoguangwu@magic-shield.com>
2024-04-03 15:29:49 +00:00
Giteabot
ac65aeecbd
Fixes #27605: inline math blocks can't be preceeded/followed by alphanumerical characters (#30175) (#30251)
Backport #30175 by @jmlt2002

- Inline math blocks couldn't be preceeded or succeeded by
alphanumerical characters due to changes introduced in PR #21171.
Removed the condition that caused this (precedingCharacter condition)
and added a new exit condition of the for-loop that checks if a specific
'$' was escaped using '\' so that the math expression can be rendered as
intended.
- Additionally this PR fixes another bug where math blocks of the type
'$xyz$abc$' where the dollar sign was not escaped by the user, generated
an error (shown in the screenshots below)
- Altered the tests to accomodate for the changes

Former behaviour (from try.gitea.io):

![image](https://github.com/go-gitea/gitea/assets/114936010/8f0cbb21-321d-451c-b871-c67a8e1e9235)

Fixed behaviour (from my local build):

![image](https://github.com/go-gitea/gitea/assets/114936010/5c22687c-6f11-4407-b5e7-c14b838bc20d)

(Edit) Source code for the README.md file:
```
$x$ -$x$ $x$-

a$xa$ $xa$a 1$xb$ $xb$1

$a a$b b$

a$b $a a$b b$

$a a\$b b$
```

Signed-off-by: João Tiago <joao.leal.tintas@tecnico.ulisboa.pt>
Co-authored-by: João Tiago <114936010+jmlt2002@users.noreply.github.com>
2024-04-03 06:17:02 +00:00
Giteabot
f8cee25ab5
Add -u git to docs when using docker exec with root installation (#29314) (#30259)
Backport #29314 by @scribblemaniac

This fixes a minor issue in the documentation for SSH Container
Passthrough for non-rootless installs. The non-rootless Dockerfile and
docker-compose do not set `USER`/`user` instructions so `docker exec`
will run as root by default. While running as root, gitea commands will
refuse to execute, breaking these approaches. For containers built with
the rootless instructions, `docker exec` will run as git by default so
this is not necessary in that case.

This issue was already discussed in #19065, but it does not appear this
part of the issue was ever added to the documentation.

Co-authored-by: scribblemaniac <scribblemaniac@users.noreply.github.com>
2024-04-03 01:44:26 -04:00
Giteabot
5c9cbeafed
Show 12 lines in markup code preview (#30255) (#30257)
Backport #30255 by @silverwind

Show up to 12 lines instead of previous 5.

<img width="929" alt="image"
src="https://github.com/go-gitea/gitea/assets/115237/de68f200-b9e2-4a25-bd6e-c46849849620">

Co-authored-by: silverwind <me@silverwind.io>
2024-04-03 03:51:34 +00:00
Giteabot
e8d5d5ed49
Refactor "dump" sub-command (#30240) (#30260)
Backport #30240 by wxiaoguang

Major changes:

* Move some functions like "addReader" / "isSubDir" /
"addRecursiveExclude" to a separate package, and add tests
* Clarify the filename&dump type logic and add tests
* Clarify the logger behavior and remove FIXME comments

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-03 02:47:06 +00:00
Giteabot
6bf6fa8de0
Render embedded code preview by permlink in markdown (#30234) (#30249)
Backport #30234 by wxiaoguang

The permlink in markdown will be rendered as a code preview block, like GitHub

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2024-04-03 02:20:16 +08:00
Giteabot
deaabf66d9
Fix missing 0 prefix of GPG key id (#30245) (#30248)
Backport #30245 by @KN4CK3R

Fixes #30235

If the key id "front" byte has a single digit, `%X` is missing the 0
prefix.
` 38D1A3EADDBEA9C` instead of
`038D1A3EADDBEA9C`
When using the `IssuerFingerprint` slice `%X` is enough but I changed it
to `%016X` too to be consistent.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-04-02 16:23:52 +00:00
Giteabot
39322d9fc4
Fix spacing in issue navbar (#30238) (#30242)
Backport #30238 by @silverwind

Create a new `issue-navbar` class specifically for this bar, previous
class used in many places and I thought I had them all removed, but not
this one.

Fixes: https://github.com/go-gitea/gitea/issues/30226

Co-authored-by: silverwind <me@silverwind.io>
2024-04-02 12:15:50 +00:00
Giteabot
f95456b362
Refactor dropzone (#30232) (#30233)
Backport #30232 by wxiaoguang

Simplify code and use `.files` elements

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-02 03:05:05 +08:00
Giteabot
895d2795ab
Remove scheduled action tasks if the repo is archived (#30224) (#30230)
Backport #30224 by @Zettat123

Fix #30220

Co-authored-by: Zettat123 <zettat123@gmail.com>
2024-04-01 19:43:09 +02:00
Giteabot
f3f0081759
Refactor file view & render (#30227) (#30229)
Backport #30227 by wxiaoguang

The old code is inconsistent and fragile, and the UI isn't right.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-01 13:39:36 +00:00
Giteabot
3ff4a6936b
Prevent flash of dropdown menu on labels list (#30215) (#30216)
Backport #30215 by @silverwind

On the labels list, This `left` class caused the dropdown content to
flash on page load until JS had hidden it. Remove it as I see no purpose
to it.

<img width="215" alt="image"
src="https://github.com/go-gitea/gitea/assets/115237/9e1de97f-dd89-41e0-9229-5c4a786ba762">

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-01 06:16:55 +00:00
Giteabot
98a81bef17
Refactor DeleteInactiveUsers, fix bug and add tests (#30206) (#30222)
Backport #30206 by wxiaoguang

1. check `IsActive` before calling `IsLastAdminUser`.
2. Fix some comments and error messages.
3. Don't `return err` if "removing file" fails in `DeleteUser`.
4. Remove incorrect `DeleteInactiveEmailAddresses`. Active users could
also have inactive emails, and inactive emails do not support
"olderThan"
5. Add tests

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-04-01 12:58:46 +08:00
wxiaoguang
e579ddc31f
Fix home topic edit form layout (#30213)
The UI has been refactored by #30191 , so here are 2 choices:

1. Backport #30191
2. Apply this quick fix


Before:

<details>


![image](https://github.com/go-gitea/gitea/assets/2114189/0db583cf-8ce8-4fdb-9e4d-8c93fe6766c7)

</details>

After:

<details>


![image](https://github.com/go-gitea/gitea/assets/2114189/0e732c4c-d28d-4c04-b328-72b9efd7daa9)

</details>

Co-authored-by: Giteabot <teabot@gitea.io>
2024-03-31 13:19:12 +00:00
Giteabot
bf22be90b7
Fix markdown color code detection (#30208) (#30211)
Backport #30208 by wxiaoguang

When reviewing PRs, some color names might be mentioned, the
`transformCodeSpan` (which calls `css.ColorHandler`) considered it as a
valid color, but actually it shouldn't be rendered as a color codespan.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-03-31 12:25:15 +00:00
Giteabot
f86ec4c302
Remove modifies/frontend from labeler (#30198) (#30212)
Backport #30198 by @silverwind

Remove this label, I find it barely useful and we already have more
useful labels like `modifies/js`. Backport so that we can eventually
delete that label.

Co-authored-by: silverwind <me@silverwind.io>
2024-03-31 13:37:01 +02:00
Giteabot
e581efe238
Fix GPG subkey verify (#30193) (#30203)
Backport #30193 by @KN4CK3R

Fixes #30189

Can't verify subkeys if they are not loaded.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-03-31 06:48:33 +00:00
Giteabot
cd117863f3
Do not allow different storage configurations to point to the same directory (#30169) (#30204)
Backport #30169 by wxiaoguang

Replace #29171

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-03-31 03:29:51 +00:00
Giteabot
9d38c4d60e
Fix unclickable checkboxes (#30195) (#30199)
Backport #30195 by @silverwind

Fix https://github.com/go-gitea/gitea/issues/30185, regression from
https://github.com/go-gitea/gitea/pull/30162.

The checkboxes were unclickable because the label was positioned over
the checkbox with `padding`. Now it uses `margin` so the checkbox itself
will be clickable in all cases.

Secondly, I changed the for/id linking to also add missing `for`
attributes when `id` is present. The other way around (only `for`
present) is currently not handled and I think there are likey no
occurences in the code and introducing new non-generated `id`s might
cause problems elsewhere if we do, so I skipped on that.

Co-authored-by: silverwind <me@silverwind.io>
2024-03-31 00:05:52 +00:00
Giteabot
19443a28b7
Include encoding in signature payload (#30174) (#30182)
Backport #30174 by @KN4CK3R

Fixes #30119

Include the encoding in the signature payload.

before

![grafik](https://github.com/go-gitea/gitea/assets/1666336/01ab94a3-8af5-4d6f-be73-a10b65a15421)

after

![grafik](https://github.com/go-gitea/gitea/assets/1666336/3a37d438-c70d-4d69-b178-d170e74aa683)

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-03-30 00:03:11 +01:00
Giteabot
591759fdfa
Remove fomantic checkbox module (#30162) (#30168)
Backport #30162 by @silverwind

CSS is pretty slim already and the `.ui.toggle.checkbox` sliders on
admin page also still work. The only necessary JS is the one that links
`input` and `label` so that it can be toggled via label. All checkboxes
except the markdown ones render at `--checkbox-size: 16px` now.

<img width="174" alt="Screenshot 2024-03-28 at 22 15 10"
src="https://github.com/go-gitea/gitea/assets/115237/3455c1bb-166b-47e4-9847-2d20dd1f04db">

<img width="499" alt="Screenshot 2024-03-28 at 21 00 07"
src="https://github.com/go-gitea/gitea/assets/115237/412be2b3-d5a0-478a-b17b-43e6bc12e8ce">

<img width="83" alt="Screenshot 2024-03-28 at 22 14 34"
src="https://github.com/go-gitea/gitea/assets/115237/d8c89838-a420-4723-8c49-89405bb39474">

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <dev.lh@web.de>
2024-03-29 17:44:22 +00:00
Giteabot
05766d065b
Fix:the rounded corners of the folded file are not displayed correctly (#29953) (#30177)
Backport #29953 by @HEREYUA

Fix:    [#29933](https://github.com/go-gitea/gitea/issues/29933)

**Before**

![image](https://github.com/go-gitea/gitea/assets/37935145/71ec80f6-5896-4e4a-b686-4d792c11ebe2)

**After**

![image](https://github.com/go-gitea/gitea/assets/37935145/81348a61-946a-4562-881d-8d873e50228f)

Co-authored-by: HEREYUA <37935145+HEREYUA@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2024-03-29 17:16:40 +00:00
Giteabot
af81b73d83
Refactor topic Find functions and add more tests for pagination (#30127) (#30167)
Backport #30127 by @lunny

This also fixed #22238

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-03-29 18:09:40 +01:00
271 changed files with 3647 additions and 3824 deletions

View File

@ -317,7 +317,7 @@ rules:
jquery/no-serialize: [2] jquery/no-serialize: [2]
jquery/no-show: [2] jquery/no-show: [2]
jquery/no-size: [2] jquery/no-size: [2]
jquery/no-sizzle: [0] jquery/no-sizzle: [2]
jquery/no-slide: [0] jquery/no-slide: [0]
jquery/no-submit: [0] jquery/no-submit: [0]
jquery/no-text: [0] jquery/no-text: [0]
@ -469,7 +469,7 @@ rules:
no-jquery/no-selector-prop: [2] no-jquery/no-selector-prop: [2]
no-jquery/no-serialize: [2] no-jquery/no-serialize: [2]
no-jquery/no-size: [2] no-jquery/no-size: [2]
no-jquery/no-sizzle: [0] no-jquery/no-sizzle: [2]
no-jquery/no-slide: [2] no-jquery/no-slide: [2]
no-jquery/no-sub: [2] no-jquery/no-sub: [2]
no-jquery/no-support: [2] no-jquery/no-support: [2]

7
.github/labeler.yml vendored
View File

@ -4,13 +4,6 @@ modifies/docs:
- "**/*.md" - "**/*.md"
- "docs/**" - "docs/**"
modifies/frontend:
- changed-files:
- any-glob-to-any-file:
- "web_src/**"
- "tailwind.config.js"
- "webpack.config.js"
modifies/templates: modifies/templates:
- changed-files: - changed-files:
- all-globs-to-any-file: - all-globs-to-any-file:

View File

@ -38,6 +38,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: pip install poetry - run: pip install poetry
- run: make deps-py - run: make deps-py
- run: make deps-frontend - run: make deps-frontend
@ -65,6 +67,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-swagger - run: make lint-swagger
@ -134,6 +138,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-frontend - run: make lint-frontend
- run: make checks-frontend - run: make checks-frontend
@ -181,6 +187,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-md - run: make lint-md
- run: make docs - run: make docs

View File

@ -24,6 +24,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend frontend deps-backend - run: make deps-frontend frontend deps-backend
- run: npx playwright install --with-deps - run: npx playwright install --with-deps
- run: make test-e2e-sqlite - run: make test-e2e-sqlite

View File

@ -25,6 +25,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release

View File

@ -24,6 +24,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release

View File

@ -26,6 +26,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release

View File

@ -110,7 +110,6 @@ LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(G
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/)) GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
@ -423,7 +422,7 @@ lint-go-windows:
lint-go-vet: lint-go-vet:
@echo "Running go vet..." @echo "Running go vet..."
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES) @$(GO) vet -vettool=gitea-vet ./...
.PHONY: lint-editorconfig .PHONY: lint-editorconfig
lint-editorconfig: lint-editorconfig:
@ -779,7 +778,7 @@ generate-backend: $(TAGS_PREREQ) generate-go
.PHONY: generate-go .PHONY: generate-go
generate-go: $(TAGS_PREREQ) generate-go: $(TAGS_PREREQ)
@echo "Running go generate..." @echo "Running go generate..."
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES) @CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' ./...
.PHONY: security-check .PHONY: security-check
security-check: security-check:

File diff suppressed because one or more lines are too long

View File

@ -36,6 +36,7 @@ var microcmdUserChangePassword = &cli.Command{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "must-change-password", Name: "must-change-password",
Usage: "User must change password", Usage: "User must change password",
Value: true,
}, },
}, },
} }
@ -57,23 +58,18 @@ func runChangePassword(c *cli.Context) error {
return err return err
} }
var mustChangePassword optional.Option[bool]
if c.IsSet("must-change-password") {
mustChangePassword = optional.Some(c.Bool("must-change-password"))
}
opts := &user_service.UpdateAuthOptions{ opts := &user_service.UpdateAuthOptions{
Password: optional.Some(c.String("password")), Password: optional.Some(c.String("password")),
MustChangePassword: mustChangePassword, MustChangePassword: optional.Some(c.Bool("must-change-password")),
} }
if err := user_service.UpdateAuth(ctx, user, opts); err != nil { if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
switch { switch {
case errors.Is(err, password.ErrMinLength): case errors.Is(err, password.ErrMinLength):
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength) return fmt.Errorf("password is not long enough, needs to be at least %d characters", setting.MinPasswordLength)
case errors.Is(err, password.ErrComplexity): case errors.Is(err, password.ErrComplexity):
return errors.New("Password does not meet complexity requirements") return errors.New("password does not meet complexity requirements")
case errors.Is(err, password.ErrIsPwned): case errors.Is(err, password.ErrIsPwned):
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords") return errors.New("the password is in a list of stolen passwords previously exposed in public data breaches, please try again with a different password, to see more details: https://haveibeenpwned.com/Passwords")
default: default:
return err return err
} }

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password" pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
@ -46,8 +47,9 @@ var microcmdUserCreate = &cli.Command{
Usage: "Generate a random password for the user", Usage: "Generate a random password for the user",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "must-change-password", Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)", Usage: "Set to false to prevent forcing the user to change their password after initial login",
DisableDefaultText: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "random-password-length", Name: "random-password-length",
@ -71,10 +73,10 @@ func runCreateUser(c *cli.Context) error {
} }
if c.IsSet("name") && c.IsSet("username") { if c.IsSet("name") && c.IsSet("username") {
return errors.New("Cannot set both --name and --username flags") return errors.New("cannot set both --name and --username flags")
} }
if !c.IsSet("name") && !c.IsSet("username") { if !c.IsSet("name") && !c.IsSet("username") {
return errors.New("One of --name or --username flags must be set") return errors.New("one of --name or --username flags must be set")
} }
if c.IsSet("password") && c.IsSet("random-password") { if c.IsSet("password") && c.IsSet("random-password") {
@ -110,17 +112,21 @@ func runCreateUser(c *cli.Context) error {
return errors.New("must set either password or random-password flag") return errors.New("must set either password or random-password flag")
} }
// always default to true isAdmin := c.Bool("admin")
changePassword := true mustChangePassword := true // always default to true
// If this is the first user being created.
// Take it as the admin and don't force a password update.
if n := user_model.CountUsers(ctx, nil); n == 0 {
changePassword = false
}
if c.IsSet("must-change-password") { if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password") // if the flag is set, use the value provided by the user
mustChangePassword = c.Bool("must-change-password")
} else {
// check whether there are users in the database
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
if err != nil {
return fmt.Errorf("IsTableNotEmpty: %w", err)
}
if !hasUserRecord && isAdmin {
// if this is the first admin being created, don't force to change password (keep the old behavior)
mustChangePassword = false
}
} }
restricted := optional.None[bool]() restricted := optional.None[bool]()
@ -136,8 +142,8 @@ func runCreateUser(c *cli.Context) error {
Name: username, Name: username,
Email: c.String("email"), Email: c.String("email"),
Passwd: password, Passwd: password,
IsAdmin: c.Bool("admin"), IsAdmin: isAdmin,
MustChangePassword: changePassword, MustChangePassword: mustChangePassword,
Visibility: visibility, Visibility: visibility,
} }

View File

@ -6,14 +6,13 @@ package cmd
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/dump"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -25,89 +24,17 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
if verbose {
log.Info("Adding file %s", customName)
}
return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: customName,
},
ReadCloser: r,
})
}
func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
file, err := os.Open(absPath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
return addReader(w, file, fileInfo, filePath, verbose)
}
func isSubdir(upper, lower string) (bool, error) {
if relPath, err := filepath.Rel(upper, lower); err != nil {
return false, err
} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
return true, nil
}
return false, nil
}
type outputType struct {
Enum []string
Default string
selected string
}
func (o outputType) Join() string {
return strings.Join(o.Enum, ", ")
}
func (o *outputType) Set(value string) error {
for _, enum := range o.Enum {
if enum == value {
o.selected = value
return nil
}
}
return fmt.Errorf("allowed values are %s", o.Join())
}
func (o outputType) String() string {
if o.selected == "" {
return o.Default
}
return o.selected
}
var outputTypeEnum = &outputType{
Enum: []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"},
Default: "zip",
}
// CmdDump represents the available dump sub-command. // CmdDump represents the available dump sub-command.
var CmdDump = &cli.Command{ var CmdDump = &cli.Command{
Name: "dump", Name: "dump",
Usage: "Dump Gitea files and database", Usage: "Dump Gitea files and database",
Description: `Dump compresses all related files and database into zip file. Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`,
It can be used for backup and capture Gitea server image to send to maintainer`, Action: runDump,
Action: runDump,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "file", Name: "file",
Aliases: []string{"f"}, Aliases: []string{"f"},
Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()), Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`,
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "verbose", Name: "verbose",
@ -160,64 +87,56 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
Name: "skip-index", Name: "skip-index",
Usage: "Skip bleve index data", Usage: "Skip bleve index data",
}, },
&cli.GenericFlag{ &cli.BoolFlag{
Name: "skip-db",
Usage: "Skip database",
},
&cli.StringFlag{
Name: "type", Name: "type",
Value: outputTypeEnum, Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")),
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
}, },
}, },
} }
func fatal(format string, args ...any) { func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
log.Fatal(format, args...) log.Fatal(format, args...)
} }
func runDump(ctx *cli.Context) error { func runDump(ctx *cli.Context) error {
var file *os.File
fileName := ctx.String("file")
outType := ctx.String("type")
if fileName == "-" {
file = os.Stdout
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
} else {
for _, suffix := range outputTypeEnum.Enum {
if strings.HasSuffix(fileName, "."+suffix) {
fileName = strings.TrimSuffix(fileName, "."+suffix)
break
}
}
fileName += "." + outType
}
setting.MustInstalled() setting.MustInstalled()
// make sure we are logging to the console no matter what the configuration tells us do to quite := ctx.Bool("quiet")
// FIXME: don't use CfgProvider directly
if _, err := setting.CfgProvider.Section("log").NewKey("MODE", "console"); err != nil {
fatal("Setting logging mode to console failed: %v", err)
}
if _, err := setting.CfgProvider.Section("log.console").NewKey("STDERR", "true"); err != nil {
fatal("Setting console logger to stderr failed: %v", err)
}
// Set loglevel to Warn if quiet-mode is requested
if ctx.Bool("quiet") {
if _, err := setting.CfgProvider.Section("log.console").NewKey("LEVEL", "Warn"); err != nil {
fatal("Setting console log-level failed: %v", err)
}
}
if !setting.InstallLock {
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
return fmt.Errorf("gitea is not initialized")
}
setting.LoadSettings() // cannot access session settings otherwise
verbose := ctx.Bool("verbose") verbose := ctx.Bool("verbose")
if verbose && ctx.Bool("quiet") { if verbose && quite {
return fmt.Errorf("--quiet and --verbose cannot both be set") fatal("Option --quiet and --verbose cannot both be set")
} }
// outFileName is either "-" or a file name (will be made absolute)
outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type"))
if outType == "" {
fatal("Invalid output type")
}
outFile := os.Stdout
if outFileName != "-" {
var err error
if outFileName, err = filepath.Abs(outFileName); err != nil {
fatal("Unable to get absolute path of dump file: %v", err)
}
if exist, _ := util.IsExist(outFileName); exist {
fatal("Dump file %q exists", outFileName)
}
if outFile, err = os.Create(outFileName); err != nil {
fatal("Unable to create dump file %q: %v", outFileName, err)
}
defer outFile.Close()
}
setupConsoleLogger(util.Iif(quite, log.WARN, log.INFO), log.CanColorStderr, os.Stderr)
setting.DisableLoggerInit()
setting.LoadSettings() // cannot access session settings otherwise
stdCtx, cancel := installSignals() stdCtx, cancel := installSignals()
defer cancel() defer cancel()
@ -226,44 +145,32 @@ func runDump(ctx *cli.Context) error {
return err return err
} }
if err := storage.Init(); err != nil { if err = storage.Init(); err != nil {
return err return err
} }
if file == nil { archiverGeneric, err := archiver.ByExtension("." + outType)
file, err = os.Create(fileName)
if err != nil {
fatal("Unable to open %s: %v", fileName, err)
}
}
defer file.Close()
absFileName, err := filepath.Abs(fileName)
if err != nil {
return err
}
var iface any
if fileName == "-" {
iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
} else {
iface, err = archiver.ByExtension(fileName)
}
if err != nil { if err != nil {
fatal("Unable to get archiver for extension: %v", err) fatal("Unable to get archiver for extension: %v", err)
} }
w, _ := iface.(archiver.Writer) archiverWriter := archiverGeneric.(archiver.Writer)
if err := w.Create(file); err != nil { if err := archiverWriter.Create(outFile); err != nil {
fatal("Creating archiver.Writer failed: %v", err) fatal("Creating archiver.Writer failed: %v", err)
} }
defer w.Close() defer archiverWriter.Close()
dumper := &dump.Dumper{
Writer: archiverWriter,
Verbose: verbose,
}
dumper.GlobalExcludeAbsPath(outFileName)
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") { if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
log.Info("Skip dumping local repositories") log.Info("Skip dumping local repositories")
} else { } else {
log.Info("Dumping local repositories... %s", setting.RepoRootPath) log.Info("Dumping local repositories... %s", setting.RepoRootPath)
if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil { if err := dumper.AddRecursiveExclude("repos", setting.RepoRootPath, nil); err != nil {
fatal("Failed to include repositories: %v", err) fatal("Failed to include repositories: %v", err)
} }
@ -276,49 +183,52 @@ func runDump(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "lfs", objPath))
return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose)
}); err != nil { }); err != nil {
fatal("Failed to dump LFS objects: %v", err) fatal("Failed to dump LFS objects: %v", err)
} }
} }
tmpDir := ctx.String("tempdir") if ctx.Bool("skip-db") {
if _, err := os.Stat(tmpDir); os.IsNotExist(err) { // Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
fatal("Path does not exist: %s", tmpDir) dumper.GlobalExcludeAbsPath(setting.Database.Path)
} log.Info("Skipping database")
dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql")
if err != nil {
fatal("Failed to create tmp file: %v", err)
}
defer func() {
_ = dbDump.Close()
if err := util.Remove(dbDump.Name()); err != nil {
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
}
}()
targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else { } else {
log.Info("Dumping database...") tmpDir := ctx.String("tempdir")
} if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
fatal("Path does not exist: %s", tmpDir)
if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
fatal("Failed to dump database: %v", err)
}
if err := addFile(w, "gitea-db.sql", dbDump.Name(), verbose); err != nil {
fatal("Failed to include gitea-db.sql: %v", err)
}
if len(setting.CustomConf) > 0 {
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
fatal("Failed to include specified app.ini: %v", err)
} }
dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql")
if err != nil {
fatal("Failed to create tmp file: %v", err)
}
defer func() {
_ = dbDump.Close()
if err := util.Remove(dbDump.Name()); err != nil {
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
}
}()
targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else {
log.Info("Dumping database...")
}
if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
fatal("Failed to dump database: %v", err)
}
if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil {
fatal("Failed to include gitea-db.sql: %v", err)
}
}
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err = dumper.AddFile("app.ini", setting.CustomConf); err != nil {
fatal("Failed to include specified app.ini: %v", err)
} }
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") { if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
@ -326,8 +236,8 @@ func runDump(ctx *cli.Context) error {
} else { } else {
customDir, err := os.Stat(setting.CustomPath) customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() { if err == nil && customDir.IsDir() {
if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is { if is, _ := dump.IsSubdir(setting.AppDataPath, setting.CustomPath); !is {
if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil { if err := dumper.AddRecursiveExclude("custom", setting.CustomPath, nil); err != nil {
fatal("Failed to include custom: %v", err) fatal("Failed to include custom: %v", err)
} }
} else { } else {
@ -364,8 +274,7 @@ func runDump(ctx *cli.Context) error {
excludes = append(excludes, setting.Attachment.Storage.Path) excludes = append(excludes, setting.Attachment.Storage.Path)
excludes = append(excludes, setting.Packages.Storage.Path) excludes = append(excludes, setting.Packages.Storage.Path)
excludes = append(excludes, setting.Log.RootPath) excludes = append(excludes, setting.Log.RootPath)
excludes = append(excludes, absFileName) if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
fatal("Failed to include data directory: %v", err) fatal("Failed to include data directory: %v", err)
} }
} }
@ -377,8 +286,7 @@ func runDump(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "attachments", objPath))
return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose)
}); err != nil { }); err != nil {
fatal("Failed to dump attachments: %v", err) fatal("Failed to dump attachments: %v", err)
} }
@ -392,8 +300,7 @@ func runDump(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "packages", objPath))
return addReader(w, object, info, path.Join("data", "packages", objPath), verbose)
}); err != nil { }); err != nil {
fatal("Failed to dump packages: %v", err) fatal("Failed to dump packages: %v", err)
} }
@ -409,80 +316,23 @@ func runDump(ctx *cli.Context) error {
log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err) log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err)
} }
if isExist { if isExist {
if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil { if err := dumper.AddRecursiveExclude("log", setting.Log.RootPath, nil); err != nil {
fatal("Failed to include log: %v", err) fatal("Failed to include log: %v", err)
} }
} }
} }
if fileName != "-" { if outFileName == "-" {
if err = w.Close(); err != nil { log.Info("Finish dumping to stdout")
_ = util.Remove(fileName) } else {
fatal("Failed to save %s: %v", fileName, err) if err = archiverWriter.Close(); err != nil {
_ = os.Remove(outFileName)
fatal("Failed to save %q: %v", outFileName, err)
} }
if err = os.Chmod(outFileName, 0o600); err != nil {
if err := os.Chmod(fileName, 0o600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err) log.Info("Can't change file access permissions mask to 0600: %v", err)
} }
} log.Info("Finish dumping in file %s", outFileName)
if fileName != "-" {
log.Info("Finish dumping in file %s", fileName)
} else {
log.Info("Finish dumping to stdout")
}
return nil
}
// addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
absPath, err := filepath.Abs(absPath)
if err != nil {
return err
}
dir, err := os.Open(absPath)
if err != nil {
return err
}
defer dir.Close()
files, err := dir.Readdir(0)
if err != nil {
return err
}
for _, file := range files {
currentAbsPath := filepath.Join(absPath, file.Name())
currentInsidePath := path.Join(insidePath, file.Name())
if file.IsDir() {
if !util.SliceContainsString(excludeAbsPath, currentAbsPath) {
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
return err
}
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
return err
}
}
} else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
shouldAdd := file.Mode().IsRegular()
if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
target, err := filepath.EvalSymlinks(currentAbsPath)
if err != nil {
return err
}
targetStat, err := os.Stat(target)
if err != nil {
return err
}
shouldAdd = targetStat.Mode().IsRegular()
}
if shouldAdd {
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
return err
}
}
}
} }
return nil return nil
} }

View File

@ -448,23 +448,26 @@ Gitea or set your environment appropriately.`, "")
func hookPrintResults(results []private.HookPostReceiveBranchResult) { func hookPrintResults(results []private.HookPostReceiveBranchResult) {
for _, res := range results { for _, res := range results {
if !res.Message { hookPrintResult(res.Message, res.Create, res.Branch, res.URL)
continue
}
fmt.Fprintln(os.Stderr, "")
if res.Create {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
}
fmt.Fprintln(os.Stderr, "")
os.Stderr.Sync()
} }
} }
func hookPrintResult(output, isCreate bool, branch, url string) {
if !output {
return
}
fmt.Fprintln(os.Stderr, "")
if isCreate {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
fmt.Fprintf(os.Stderr, " %s\n", url)
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", url)
}
fmt.Fprintln(os.Stderr, "")
os.Stderr.Sync()
}
func pushOptions() map[string]string { func pushOptions() map[string]string {
opts := make(map[string]string) opts := make(map[string]string)
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil { if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
@ -691,6 +694,12 @@ Gitea or set your environment appropriately.`, "")
} }
err = writeFlushPktLine(ctx, os.Stdout) err = writeFlushPktLine(ctx, os.Stdout)
if err == nil {
for _, res := range resp.Results {
hookPrintResult(res.ShouldShowMessage, res.IsCreatePR, res.HeadBranch, res.URL)
}
}
return err return err
} }

View File

@ -114,7 +114,7 @@ func showWebStartupMessage(msg string) {
log.Info("* WorkPath: %s", setting.AppWorkPath) log.Info("* WorkPath: %s", setting.AppWorkPath)
log.Info("* CustomPath: %s", setting.CustomPath) log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf) log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg) log.Info("%s", msg) // show startup message
} }
func serveInstall(ctx *cli.Context) error { func serveInstall(ctx *cli.Context) error {

View File

@ -2369,22 +2369,6 @@ LEVEL = Info
;; Enable issue by repository metrics; default is false ;; Enable issue by repository metrics; default is false
;ENABLED_ISSUE_BY_REPOSITORY = false ;ENABLED_ISSUE_BY_REPOSITORY = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[task]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Task queue type, could be `channel` or `redis`.
;QUEUE_TYPE = channel
;;
;; Task queue length, available only when `QUEUE_TYPE` is `channel`.
;QUEUE_LENGTH = 1000
;;
;; Task queue connection string, available only when `QUEUE_TYPE` is `redis`.
;; If there is a password of redis, use `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for `redis-clsuter`.
;QUEUE_CONN_STR = "redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[migrations] ;[migrations]

View File

@ -83,8 +83,7 @@ Admin operations:
- `--email value`: Email. Required. - `--email value`: Email. Required.
- `--admin`: If provided, this makes the user an admin. Optional. - `--admin`: If provided, this makes the user an admin. Optional.
- `--access-token`: If provided, an access token will be created for the user. Optional. (default: false). - `--access-token`: If provided, an access token will be created for the user. Optional. (default: false).
- `--must-change-password`: If provided, the created user will be required to choose a newer password after the - `--must-change-password`: The created user will be required to set a new password after the initial login, default: true. It could be disabled by `--must-change-password=false`.
initial login. Optional. (default: true).
- `--random-password`: If provided, a randomly generated password will be used as the password of the created - `--random-password`: If provided, a randomly generated password will be used as the password of the created
user. The value of `--password` will be discarded. Optional. user. The value of `--password` will be discarded. Optional.
- `--random-password-length`: If provided, it will be used to configure the length of the randomly generated - `--random-password-length`: If provided, it will be used to configure the length of the randomly generated
@ -95,7 +94,7 @@ Admin operations:
- Options: - Options:
- `--username value`, `-u value`: Username. Required. - `--username value`, `-u value`: Username. Required.
- `--password value`, `-p value`: New password. Required. - `--password value`, `-p value`: New password. Required.
- `--must-change-password`: If provided, the user is required to choose a new password after the login. Optional. - `--must-change-password`: The user is required to set a new password after the login, default: true. It could be disabled by `--must-change-password=false`.
- Examples: - Examples:
- `gitea admin user change-password --username myname --password asecurepassword` - `gitea admin user change-password --username myname --password asecurepassword`
- `must-change-password`: - `must-change-password`:

View File

@ -1193,14 +1193,6 @@ in this mapping or the filetype using heuristics.
- `DEFAULT_UI_LOCATION`: Default location of time on the UI, so that we can display correct user's time on UI. i.e. Asia/Shanghai - `DEFAULT_UI_LOCATION`: Default location of time on the UI, so that we can display correct user's time on UI. i.e. Asia/Shanghai
## Task (`task`)
Task queue configuration has been moved to `queue.task`. However, the below configuration values are kept for backwards compatibility:
- `QUEUE_TYPE`: **channel**: Task queue type, could be `channel` or `redis`.
- `QUEUE_LENGTH`: **1000**: Task queue length, available only when `QUEUE_TYPE` is `channel`.
- `QUEUE_CONN_STR`: **redis://127.0.0.1:6379/0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If redis needs a password, use `redis://123@127.0.0.1:6379/0` or `redis+cluster://123@127.0.0.1:6379/0`.
## Migrations (`migrations`) ## Migrations (`migrations`)
- `MAX_ATTEMPTS`: **3**: Max attempts per http/https request on migrations. - `MAX_ATTEMPTS`: **3**: Max attempts per http/https request on migrations.

View File

@ -1128,15 +1128,6 @@ ALLOW_DATA_URI_IMAGES = true
- `DEFAULT_UI_LOCATION`:在 UI 上的默认时间位置,以便我们可以在 UI 上显示正确的用户时间。例如Asia/Shanghai - `DEFAULT_UI_LOCATION`:在 UI 上的默认时间位置,以便我们可以在 UI 上显示正确的用户时间。例如Asia/Shanghai
## 任务 (`task`)
任务队列配置已移动到 `queue.task`。然而,以下配置值仍保留以确保向后兼容:
- `QUEUE_TYPE`**channel**:任务队列类型,可以是 `channel``redis`
- `QUEUE_LENGTH`**1000**:任务队列长度,仅在 `QUEUE_TYPE``channel` 时可用。
- `QUEUE_CONN_STR`**redis://127.0.0.1:6379/0**:任务队列连接字符串,仅在 `QUEUE_TYPE``redis` 时可用。
如果 redis 需要密码,使用 `redis://123@127.0.0.1:6379/0``redis+cluster://123@127.0.0.1:6379/0`
## 迁移 (`migrations`) ## 迁移 (`migrations`)
- `MAX_ATTEMPTS`**3**:每次 http/https 请求的最大尝试次数(用于迁移)。 - `MAX_ATTEMPTS`**3**:每次 http/https 请求的最大尝试次数(用于迁移)。

View File

@ -304,7 +304,8 @@ services:
- GITEA__mailer__ENABLED=true - GITEA__mailer__ENABLED=true
- GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set} - GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set}
- GITEA__mailer__PROTOCOL=smtps - GITEA__mailer__PROTOCOL=smtps
- GITEA__mailer__HOST=${GITEA__mailer__HOST:?GITEA__mailer__HOST not set} - GITEA__mailer__SMTP_ADDR=${GITEA__mailer__SMTP_ADDR:?GITEA__mailer__SMTP_ADDR not set}
- GITEA__mailer__SMTP_PORT=${GITEA__mailer__SMTP_PORT:?GITEA__mailer__SMTP_PORT not set}
- GITEA__mailer__USER=${GITEA__mailer__USER:-apikey} - GITEA__mailer__USER=${GITEA__mailer__USER:-apikey}
- GITEA__mailer__PASSWD="""${GITEA__mailer__PASSWD:?GITEA__mailer__PASSWD not set}""" - GITEA__mailer__PASSWD="""${GITEA__mailer__PASSWD:?GITEA__mailer__PASSWD not set}"""
``` ```
@ -545,7 +546,7 @@ In this option, the idea is that the host SSH uses an `AuthorizedKeysCommand` in
```bash ```bash
cat <<"EOF" | sudo tee /home/git/docker-shell cat <<"EOF" | sudo tee /home/git/docker-shell
#!/bin/sh #!/bin/sh
/usr/bin/docker exec -i --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea sh "$@" /usr/bin/docker exec -i -u git --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea sh "$@"
EOF EOF
sudo chmod +x /home/git/docker-shell sudo chmod +x /home/git/docker-shell
sudo usermod -s /home/git/docker-shell git sudo usermod -s /home/git/docker-shell git
@ -560,7 +561,7 @@ Add the following block to `/etc/ssh/sshd_config`, on the host:
```bash ```bash
Match User git Match User git
AuthorizedKeysCommandUser git AuthorizedKeysCommandUser git
AuthorizedKeysCommand /usr/bin/docker exec -i gitea /usr/local/bin/gitea keys -c /data/gitea/conf/app.ini -e git -u %u -t %t -k %k AuthorizedKeysCommand /usr/bin/docker exec -i -u git gitea /usr/local/bin/gitea keys -c /data/gitea/conf/app.ini -e git -u %u -t %t -k %k
``` ```
(From 1.16.0 you will not need to set the `-c /data/gitea/conf/app.ini` option.) (From 1.16.0 you will not need to set the `-c /data/gitea/conf/app.ini` option.)

View File

@ -303,34 +303,3 @@ sudo systemctl enable act_runner --now
``` ```
If using Docker, the `act_runner` user should also be added to the `docker` group before starting the service. Keep in mind that this effectively gives `act_runner` root access to the system [[1]](https://docs.docker.com/engine/security/#docker-daemon-attack-surface). If using Docker, the `act_runner` user should also be added to the `docker` group before starting the service. Keep in mind that this effectively gives `act_runner` root access to the system [[1]](https://docs.docker.com/engine/security/#docker-daemon-attack-surface).
## Configuration variable
You can create configuration variables on the user, organization and repository level.
The level of the variable depends on where you created it.
### Naming conventions
The following rules apply to variable names:
- Variable names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.
- Variable names must not start with the `GITHUB_` and `GITEA_` prefix.
- Variable names must not start with a number.
- Variable names are case-insensitive.
- Variable names must be unique at the level they are created at.
- Variable names must not be `CI`.
### Using variable
After creating configuration variables, they will be automatically filled in the `vars` context.
They can be accessed through expressions like `{{ vars.VARIABLE_NAME }}` in the workflow.
### Precedence
If a variable with the same name exists at multiple levels, the variable at the lowest level takes precedence:
A repository variable will always be chosen over an organization/user variable.

View File

@ -258,32 +258,3 @@ Runner的标签用于确定Runner可以运行哪些Job以及如何运行它们
Runner将从Gitea实例获取Job并自动运行它们。 Runner将从Gitea实例获取Job并自动运行它们。
由于Act Runner仍处于开发中建议定期检查最新版本并进行升级。 由于Act Runner仍处于开发中建议定期检查最新版本并进行升级。
## 变量
您可以创建用户、组织和仓库级别的变量。变量的级别取决于创建它的位置。
### 命名规则
以下规则适用于变量名:
- 变量名称只能包含字母数字字符 (`[a-z]`, `[A-Z]`, `[0-9]`) 或下划线 (`_`)。不允许使用空格。
- 变量名称不能以 `GITHUB_``GITEA_` 前缀开头。
- 变量名称不能以数字开头。
- 变量名称不区分大小写。
- 变量名称在创建它们的级别上必须是唯一的。
- 变量名称不能为 “CI”。
### 使用
创建配置变量后,它们将自动填充到 `vars` 上下文中。您可以在工作流中使用类似 `{{ vars.VARIABLE_NAME }}` 这样的表达式来使用它们。
### 优先级
如果同名变量存在于多个级别,则级别最低的变量优先。
仓库级别的变量总是比组织或者用户级别的变量优先被选中。

View File

@ -104,7 +104,7 @@ However, if a job container tries to fetch code from localhost, it will fail bec
### Connection 3, act runner to internet ### Connection 3, act runner to internet
When you use some actions like `actions/checkout@v4`, the act runner downloads the scripts, not the job containers. When you use some actions like `actions/checkout@v4`, the act runner downloads the scripts, not the job containers.
By default, it downloads from [gitea.com](http://gitea.com/), so it requires access to the internet. By default, it downloads from [github.com](http://github.com/), so it requires access to the internet. If you configure the `DEFAULT_ACTIONS_URL` to `self`, then it will download from your Gitea instance by default. Then it will not connect to internet when downloading the action itself.
It also downloads some docker images from Docker Hub by default, which also requires internet access. It also downloads some docker images from Docker Hub by default, which also requires internet access.
However, internet access is not strictly necessary. However, internet access is not strictly necessary.

View File

@ -105,7 +105,8 @@ act runner 必须能够连接到Gitea以接收任务并发送执行结果回来
### 连接 3act runner到互联网 ### 连接 3act runner到互联网
当您使用诸如 `actions/checkout@v4` 的一些Actions时act runner下载的是脚本而不是Job容器。 当您使用诸如 `actions/checkout@v4` 的一些Actions时act runner下载的是脚本而不是Job容器。
默认情况下,它从[gitea.com](http://gitea.com/)下载,因此需要访问互联网。 默认情况下,它从[github.com](http://github.com/)下载,因此需要访问互联网。如果您设置的是 self
那么默认将从您的当前Gitea实例下载那么此步骤不需要连接到互联网。
它还默认从Docker Hub下载一些Docker镜像这也需要互联网访问。 它还默认从Docker Hub下载一些Docker镜像这也需要互联网访问。
然而,互联网访问并不是绝对必需的。 然而,互联网访问并不是绝对必需的。

View File

@ -0,0 +1,41 @@
---
date: "2024-04-10T22:21:00+08:00"
title: "Variables"
slug: "actions-variables"
sidebar_position: 25
draft: false
toc: false
menu:
sidebar:
parent: "actions"
name: "Variables"
sidebar_position: 25
identifier: "actions-variables"
---
## Variables
You can create configuration variables on the user, organization and repository level.
The level of the variable depends on where you created it. When creating a variable, the
key will be converted to uppercase. You need use uppercase on the yaml file.
### Naming conventions
The following rules apply to variable names:
- Variable names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.
- Variable names must not start with the `GITHUB_` and `GITEA_` prefix.
- Variable names must not start with a number.
- Variable names are case-insensitive.
- Variable names must be unique at the level they are created at.
- Variable names must not be `CI`.
### Using variable
After creating configuration variables, they will be automatically filled in the `vars` context.
They can be accessed through expressions like `${{ vars.VARIABLE_NAME }}` in the workflow.
### Precedence
If a variable with the same name exists at multiple levels, the variable at the lowest level takes precedence:
A repository variable will always be chosen over an organization/user variable.

View File

@ -0,0 +1,39 @@
---
date: "2024-04-10T22:21:00+08:00"
title: "变量"
slug: "actions-variables"
sidebar_position: 25
draft: false
toc: false
menu:
sidebar:
parent: "actions"
name: "变量"
sidebar_position: 25
identifier: "actions-variables"
---
## 变量
您可以创建用户、组织和仓库级别的变量。变量的级别取决于创建它的位置。当创建变量时,变量的名称会被
转换为大写在yaml文件中引用时需要使用大写。
### 命名规则
以下规则适用于变量名:
- 变量名称只能包含字母数字字符 (`[a-z]`, `[A-Z]`, `[0-9]`) 或下划线 (`_`)。不允许使用空格。
- 变量名称不能以 `GITHUB_``GITEA_` 前缀开头。
- 变量名称不能以数字开头。
- 变量名称不区分大小写。
- 变量名称在创建它们的级别上必须是唯一的。
- 变量名称不能为 `CI`
### 使用
创建配置变量后,它们将自动填充到 `vars` 上下文中。您可以在工作流中使用类似 `${{ vars.VARIABLE_NAME }}` 这样的表达式来使用它们。
### 优先级
如果同名变量存在于多个级别,则级别最低的变量优先。
仓库级别的变量总是比组织或者用户级别的变量优先被选中。

9
go.mod
View File

@ -16,7 +16,6 @@ require (
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/NYTimes/gziphandler v1.1.1
github.com/PuerkitoBio/goquery v1.9.1 github.com/PuerkitoBio/goquery v1.9.1
github.com/alecthomas/chroma/v2 v2.13.0 github.com/alecthomas/chroma/v2 v2.13.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
@ -68,7 +67,7 @@ require (
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
github.com/klauspost/compress v1.17.7 github.com/klauspost/compress v1.17.8
github.com/klauspost/cpuid/v2 v2.2.7 github.com/klauspost/cpuid/v2 v2.2.7
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/markbates/goth v1.79.0 github.com/markbates/goth v1.79.0
@ -105,11 +104,11 @@ require (
github.com/yuin/goldmark v1.7.0 github.com/yuin/goldmark v1.7.0
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.22.0
golang.org/x/image v0.15.0 golang.org/x/image v0.15.0
golang.org/x/net v0.22.0 golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.18.0 golang.org/x/oauth2 v0.18.0
golang.org/x/sys v0.18.0 golang.org/x/sys v0.19.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
golang.org/x/tools v0.19.0 golang.org/x/tools v0.19.0
google.golang.org/grpc v1.62.1 google.golang.org/grpc v1.62.1

22
go.sum
View File

@ -61,8 +61,6 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI= github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
@ -493,8 +491,8 @@ github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
@ -846,8 +844,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw= golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
@ -881,8 +879,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -932,8 +930,8 @@ 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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -943,8 +941,8 @@ 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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@ -74,6 +74,13 @@ func (run *ActionRun) Link() string {
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index)
} }
func (run *ActionRun) WorkflowLink() string {
if run.Repo == nil {
return ""
}
return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), run.WorkflowID)
}
// RefLink return the url of run's ref // RefLink return the url of run's ref
func (run *ActionRun) RefLink() string { func (run *ActionRun) RefLink() string {
refName := git.RefName(run.Ref) refName := git.RefName(run.Ref)
@ -146,6 +153,10 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
return nil, fmt.Errorf("event %s is not a pull request event", run.Event) return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
} }
func (run *ActionRun) IsSchedule() bool {
return run.ScheduleID > 0
}
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID). _, err := db.GetEngine(ctx).ID(repo.ID).
SetExpr("num_action_runs", SetExpr("num_action_runs",

View File

@ -44,6 +44,9 @@ func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
schedule.TriggerUser = user_model.NewActionsUser() schedule.TriggerUser = user_model.NewActionsUser()
} else { } else {
schedule.TriggerUser = users[schedule.TriggerUserID] schedule.TriggerUser = users[schedule.TriggerUserID]
if schedule.TriggerUser == nil {
schedule.TriggerUser = user_model.NewGhostUser()
}
} }
} }
return nil return nil

View File

@ -11,6 +11,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -227,7 +228,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if runner.RepoID != 0 { if runner.RepoID != 0 {
jobCond = builder.Eq{"repo_id": runner.RepoID} jobCond = builder.Eq{"repo_id": runner.RepoID}
} else if runner.OwnerID != 0 { } else if runner.OwnerID != 0 {
jobCond = builder.In("repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": runner.OwnerID})) jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
} }
if jobCond.IsValid() { if jobCond.IsValid() {
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond)) jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))

View File

@ -139,13 +139,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific
} }
} }
keyID := "" keyID := tryGetKeyIDFromSignature(sig)
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
}
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
}
defaultReason := NoKeyFound defaultReason := NoKeyFound
// First check if the sig has a keyID and if so just look at that // First check if the sig has a keyID and if so just look at that

View File

@ -134,3 +134,13 @@ func extractSignature(s string) (*packet.Signature, error) {
} }
return sig, nil return sig, nil
} }
func tryGetKeyIDFromSignature(sig *packet.Signature) string {
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
return fmt.Sprintf("%016X", *sig.IssuerKeyId)
}
if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
return fmt.Sprintf("%016X", sig.IssuerFingerprint[12:20])
}
return ""
}

View File

@ -11,7 +11,9 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/keybase/go-crypto/openpgp/packet"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -391,3 +393,13 @@ epiDVQ==
assert.Equal(t, time.Unix(1586105389, 0), expire) assert.Equal(t, time.Unix(1586105389, 0), expire)
} }
} }
func TestTryGetKeyIDFromSignature(t *testing.T) {
assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{}))
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)),
}))
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
}))
}

View File

@ -46,6 +46,10 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
return "", ErrGPGKeyNotExist{} return "", ErrGPGKeyNotExist{}
} }
if err := key.LoadSubKeys(ctx); err != nil {
return "", err
}
sig, err := extractSignature(signature) sig, err := extractSignature(signature)
if err != nil { if err != nil {
return "", ErrGPGInvalidTokenSignature{ return "", ErrGPGInvalidTokenSignature{

View File

@ -76,23 +76,14 @@ func calcFingerprintNative(publicKeyContent string) (string, error) {
// CalcFingerprint calculate public key's fingerprint // CalcFingerprint calculate public key's fingerprint
func CalcFingerprint(publicKeyContent string) (string, error) { func CalcFingerprint(publicKeyContent string) (string, error) {
// Call the method based on configuration // Call the method based on configuration
var ( useNative := setting.SSH.KeygenPath == ""
fnName, fp string calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen)
err error fp, err := calcFn(publicKeyContent)
)
if len(setting.SSH.KeygenPath) == 0 {
fnName = "calcFingerprintNative"
fp, err = calcFingerprintNative(publicKeyContent)
} else {
fnName = "calcFingerprintSSHKeygen"
fp, err = calcFingerprintSSHKeygen(publicKeyContent)
}
if err != nil { if err != nil {
if IsErrKeyUnableVerify(err) { if IsErrKeyUnableVerify(err) {
log.Info("%s", publicKeyContent)
return "", err return "", err
} }
return "", fmt.Errorf("%s: %w", fnName, err) return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err)
} }
return fp, nil return fp, nil
} }

View File

@ -13,8 +13,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
//////////////////// Application
func TestOAuth2Application_GenerateClientSecret(t *testing.T) { func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})

View File

@ -10,21 +10,21 @@ import (
) )
// CountOrphanedObjects count subjects with have no existing refobject anymore // CountOrphanedObjects count subjects with have no existing refobject anymore
func CountOrphanedObjects(ctx context.Context, subject, refobject, joinCond string) (int64, error) { func CountOrphanedObjects(ctx context.Context, subject, refObject, joinCond string) (int64, error) {
return GetEngine(ctx). return GetEngine(ctx).
Table("`"+subject+"`"). Table("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond). Join("LEFT", "`"+refObject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"}). Where(builder.IsNull{"`" + refObject + "`.id"}).
Select("COUNT(`" + subject + "`.`id`)"). Select("COUNT(`" + subject + "`.`id`)").
Count() Count()
} }
// DeleteOrphanedObjects delete subjects with have no existing refobject anymore // DeleteOrphanedObjects delete subjects with have no existing refobject anymore
func DeleteOrphanedObjects(ctx context.Context, subject, refobject, joinCond string) error { func DeleteOrphanedObjects(ctx context.Context, subject, refObject, joinCond string) error {
subQuery := builder.Select("`"+subject+"`.id"). subQuery := builder.Select("`"+subject+"`.id").
From("`"+subject+"`"). From("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond). Join("LEFT", "`"+refObject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"}) Where(builder.IsNull{"`" + refObject + "`.id"})
b := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`") b := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`")
_, err := GetEngine(ctx).Exec(b) _, err := GetEngine(ctx).Exec(b)
return err return err

View File

@ -284,8 +284,8 @@ func MaxBatchInsertSize(bean any) int {
} }
// IsTableNotEmpty returns true if table has at least one record // IsTableNotEmpty returns true if table has at least one record
func IsTableNotEmpty(tableName string) (bool, error) { func IsTableNotEmpty(beanOrTableName any) (bool, error) {
return x.Table(tableName).Exist() return x.Table(beanOrTableName).Exist()
} }
// DeleteAllRecords will delete all the records of this table // DeleteAllRecords will delete all the records of this table

View File

@ -297,6 +297,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
sess := db.GetEngine(ctx) sess := db.GetEngine(ctx)
// check whether from branch exist
var branch Branch var branch Branch
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch) exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
if err != nil { if err != nil {
@ -308,6 +309,24 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
} }
} }
// check whether to branch exist or is_deleted
var dstBranch Branch
exist, err = db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, to).Get(&dstBranch)
if err != nil {
return err
}
if exist {
if !dstBranch.IsDeleted {
return ErrBranchAlreadyExists{
BranchName: to,
}
}
if _, err := db.GetEngine(ctx).ID(dstBranch.ID).NoAutoCondition().Delete(&dstBranch); err != nil {
return err
}
}
// 1. update branch in database // 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
Name: to, Name: to,
@ -362,12 +381,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
return err return err
} }
// 5. do git action // 5. insert renamed branch record
if err = gitAction(ctx, isDefault); err != nil {
return err
}
// 6. insert renamed branch record
renamedBranch := &RenamedBranch{ renamedBranch := &RenamedBranch{
RepoID: repo.ID, RepoID: repo.ID,
From: from, From: from,
@ -378,6 +392,11 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
return err return err
} }
// 6. do git action
if err = gitAction(ctx, isDefault); err != nil {
return err
}
return committer.Commit() return committer.Commit()
} }

View File

@ -9,9 +9,9 @@ import (
// AddConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true // AddConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true
func AddConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error { func AddConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
type OAuth2Application struct { type oauth2Application struct {
ID int64
ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"` ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"`
} }
return x.Sync(new(oauth2Application))
return x.Sync(new(OAuth2Application))
} }

View File

@ -13,12 +13,12 @@ import (
func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) { func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) {
// premigration // premigration
type OAuth2Application struct { type oauth2Application struct {
ID int64 ID int64
} }
// Prepare and load the testing database // Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(OAuth2Application)) x, deferable := base.PrepareTestEnv(t, 0, new(oauth2Application))
defer deferable() defer deferable()
if x == nil || t.Failed() { if x == nil || t.Failed() {
return return
@ -36,7 +36,7 @@ func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) {
} }
got := []ExpectedOAuth2Application{} got := []ExpectedOAuth2Application{}
if err := x.Table("o_auth2_application").Select("id, confidential_client").Find(&got); !assert.NoError(t, err) { if err := x.Table("oauth2_application").Select("id, confidential_client").Find(&got); !assert.NoError(t, err) {
return return
} }

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"strings" "strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -402,6 +403,8 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
&TeamInvite{OrgID: org.ID}, &TeamInvite{OrgID: org.ID},
&secret_model.Secret{OwnerID: org.ID}, &secret_model.Secret{OwnerID: org.ID},
&user_model.Blocking{BlockerID: org.ID}, &user_model.Blocking{BlockerID: org.ID},
&actions_model.ActionRunner{OwnerID: org.ID},
&actions_model.ActionRunnerToken{OwnerID: org.ID},
); err != nil { ); err != nil {
return fmt.Errorf("DeleteBeans: %w", err) return fmt.Errorf("DeleteBeans: %w", err)
} }

View File

@ -287,9 +287,10 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
// SearchVersions gets all versions of packages matching the search options // SearchVersions gets all versions of packages matching the search options
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Where(opts.ToConds()). Select("package_version.*").
Table("package_version"). Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id") Join("INNER", "package", "package.id = package_version.package_id").
Where(opts.ToConds())
opts.configureOrderBy(sess) opts.configureOrderBy(sess)
@ -304,19 +305,18 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package
// SearchLatestVersions gets the latest version of every package matching the search options // SearchLatestVersions gets the latest version of every package matching the search options
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
cond := opts.ToConds(). in := builder.
And(builder.Expr("pv2.id IS NULL")) Select("MAX(package_version.id)").
From("package_version").
joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))") InnerJoin("package", "package.id = package_version.package_id").
if opts.IsInternal.Has() { Where(opts.ToConds()).
joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.Value()}) GroupBy("package_version.package_id")
}
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Select("package_version.*").
Table("package_version"). Table("package_version").
Join("LEFT", "package_version pv2", joinCond).
Join("INNER", "package", "package.id = package_version.package_id"). Join("INNER", "package", "package.id = package_version.package_id").
Where(cond) Where(builder.In("package_version.id", in))
opts.configureOrderBy(sess) opts.configureOrderBy(sess)

View File

@ -53,7 +53,7 @@ func (repo *Repository) IsDependenciesEnabled(ctx context.Context) bool {
var u *RepoUnit var u *RepoUnit
var err error var err error
if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil { if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil {
log.Trace("%s", err) log.Trace("IsDependenciesEnabled: %v", err)
return setting.Service.DefaultEnableDependencies return setting.Service.DefaultEnableDependencies
} }
return u.IssuesConfig().EnableDependencies return u.IssuesConfig().EnableDependencies

View File

@ -178,7 +178,7 @@ type FindTopicOptions struct {
Keyword string Keyword string
} }
func (opts *FindTopicOptions) toConds() builder.Cond { func (opts *FindTopicOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_topic.repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_topic.repo_id": opts.RepoID})
@ -191,29 +191,24 @@ func (opts *FindTopicOptions) toConds() builder.Cond {
return cond return cond
} }
// FindTopics retrieves the topics via FindTopicOptions func (opts *FindTopicOptions) ToOrders() string {
func FindTopics(ctx context.Context, opts *FindTopicOptions) ([]*Topic, int64, error) {
sess := db.GetEngine(ctx).Select("topic.*").Where(opts.toConds())
orderBy := "topic.repo_count DESC" orderBy := "topic.repo_count DESC"
if opts.RepoID > 0 { if opts.RepoID > 0 {
sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id")
orderBy = "topic.name" // when render topics for a repo, it's better to sort them by name, to get consistent result orderBy = "topic.name" // when render topics for a repo, it's better to sort them by name, to get consistent result
} }
if opts.PageSize != 0 && opts.Page != 0 { return orderBy
sess = db.SetSessionPagination(sess, opts)
}
topics := make([]*Topic, 0, 10)
total, err := sess.OrderBy(orderBy).FindAndCount(&topics)
return topics, total, err
} }
// CountTopics counts the number of topics matching the FindTopicOptions func (opts *FindTopicOptions) ToJoins() []db.JoinFunc {
func CountTopics(ctx context.Context, opts *FindTopicOptions) (int64, error) { if opts.RepoID <= 0 {
sess := db.GetEngine(ctx).Where(opts.toConds()) return nil
if opts.RepoID > 0 { }
sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") return []db.JoinFunc{
func(e db.Engine) error {
e.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id")
return nil
},
} }
return sess.Count(new(Topic))
} }
// GetRepoTopicByName retrieves topic from name for a repo if it exist // GetRepoTopicByName retrieves topic from name for a repo if it exist
@ -283,7 +278,7 @@ func DeleteTopic(ctx context.Context, repoID int64, topicName string) (*Topic, e
// SaveTopics save topics to a repository // SaveTopics save topics to a repository
func SaveTopics(ctx context.Context, repoID int64, topicNames ...string) error { func SaveTopics(ctx context.Context, repoID int64, topicNames ...string) error {
topics, _, err := FindTopics(ctx, &FindTopicOptions{ topics, err := db.Find[Topic](ctx, &FindTopicOptions{
RepoID: repoID, RepoID: repoID,
}) })
if err != nil { if err != nil {

View File

@ -19,18 +19,18 @@ func TestAddTopic(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
topics, _, err := repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{}) topics, err := db.Find[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, topics, totalNrOfTopics) assert.Len(t, topics, totalNrOfTopics)
topics, total, err := repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ topics, total, err := db.FindAndCount[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{
ListOptions: db.ListOptions{Page: 1, PageSize: 2}, ListOptions: db.ListOptions{Page: 1, PageSize: 2},
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, topics, 2) assert.Len(t, topics, 2)
assert.EqualValues(t, 6, total) assert.EqualValues(t, 6, total)
topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ topics, err = db.Find[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{
RepoID: 1, RepoID: 1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -38,11 +38,11 @@ func TestAddTopic(t *testing.T) {
assert.NoError(t, repo_model.SaveTopics(db.DefaultContext, 2, "golang")) assert.NoError(t, repo_model.SaveTopics(db.DefaultContext, 2, "golang"))
repo2NrOfTopics := 1 repo2NrOfTopics := 1
topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{}) topics, err = db.Find[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, topics, totalNrOfTopics) assert.Len(t, topics, totalNrOfTopics)
topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ topics, err = db.Find[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{
RepoID: 2, RepoID: 2,
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -55,11 +55,11 @@ func TestAddTopic(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, topic.RepoCount) assert.EqualValues(t, 1, topic.RepoCount)
topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{}) topics, err = db.Find[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, topics, totalNrOfTopics) assert.Len(t, topics, totalNrOfTopics)
topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ topics, err = db.Find[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{
RepoID: 2, RepoID: 2,
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -256,14 +256,6 @@ func IsEmailUsed(ctx context.Context, email string) (bool, error) {
return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{}) return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
} }
// DeleteInactiveEmailAddresses deletes inactive email addresses
func DeleteInactiveEmailAddresses(ctx context.Context) error {
_, err := db.GetEngine(ctx).
Where("is_activated = ?", false).
Delete(new(EmailAddress))
return err
}
// ActivateEmail activates the email address to given user. // ActivateEmail activates the email address to given user.
func ActivateEmail(ctx context.Context, email *EmailAddress) error { func ActivateEmail(ctx context.Context, email *EmailAddress) error {
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)

View File

@ -4,6 +4,7 @@
package charset package charset
import ( import (
"regexp"
"strings" "strings"
"testing" "testing"
@ -156,13 +157,16 @@ func TestEscapeControlReader(t *testing.T) {
tests = append(tests, test) tests = append(tests, test)
} }
re := regexp.MustCompile(`repo.ambiguous_character:\d+,\d+`) // simplify the output for the tests, remove the translation variants
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
output := &strings.Builder{} output := &strings.Builder{}
status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{}) status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.status, *status) assert.Equal(t, tt.status, *status)
assert.Equal(t, tt.result, output.String()) outStr := output.String()
outStr = re.ReplaceAllString(outStr, "repo.ambiguous_character")
assert.Equal(t, tt.result, outStr)
}) })
} }
} }

View File

@ -561,14 +561,14 @@ func TestFormatError(t *testing.T) {
err: &csv.ParseError{ err: &csv.ParseError{
Err: csv.ErrFieldCount, Err: csv.ErrFieldCount,
}, },
expectedMessage: "repo.error.csv.invalid_field_count", expectedMessage: "repo.error.csv.invalid_field_count:0",
expectsError: false, expectsError: false,
}, },
{ {
err: &csv.ParseError{ err: &csv.ParseError{
Err: csv.ErrBareQuote, Err: csv.ErrBareQuote,
}, },
expectedMessage: "repo.error.csv.unexpected", expectedMessage: "repo.error.csv.unexpected:0,0",
expectsError: false, expectsError: false,
}, },
{ {

174
modules/dump/dumper.go Normal file
View File

@ -0,0 +1,174 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package dump
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"slices"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"github.com/mholt/archiver/v3"
)
var SupportedOutputTypes = []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"}
// PrepareFileNameAndType prepares the output file name and type, if the type is not supported, it returns an empty "outType"
func PrepareFileNameAndType(argFile, argType string) (outFileName, outType string) {
if argFile == "" && argType == "" {
outType = SupportedOutputTypes[0]
outFileName = fmt.Sprintf("gitea-dump-%d.%s", timeutil.TimeStampNow(), outType)
} else if argFile == "" {
outType = argType
outFileName = fmt.Sprintf("gitea-dump-%d.%s", timeutil.TimeStampNow(), outType)
} else if argType == "" {
if filepath.Ext(outFileName) == "" {
outType = SupportedOutputTypes[0]
outFileName = argFile
} else {
for _, t := range SupportedOutputTypes {
if strings.HasSuffix(argFile, "."+t) {
outFileName = argFile
outType = t
}
}
}
} else {
outFileName, outType = argFile, argType
}
if !slices.Contains(SupportedOutputTypes, outType) {
return "", ""
}
return outFileName, outType
}
func IsSubdir(upper, lower string) (bool, error) {
if relPath, err := filepath.Rel(upper, lower); err != nil {
return false, err
} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
return true, nil
}
return false, nil
}
type Dumper struct {
Writer archiver.Writer
Verbose bool
globalExcludeAbsPaths []string
}
func (dumper *Dumper) AddReader(r io.ReadCloser, info os.FileInfo, customName string) error {
if dumper.Verbose {
log.Info("Adding file %s", customName)
}
return dumper.Writer.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: customName,
},
ReadCloser: r,
})
}
func (dumper *Dumper) AddFile(filePath, absPath string) error {
file, err := os.Open(absPath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
return dumper.AddReader(file, fileInfo, filePath)
}
func (dumper *Dumper) normalizeFilePath(absPath string) string {
absPath = filepath.Clean(absPath)
if setting.IsWindows {
absPath = strings.ToLower(absPath)
}
return absPath
}
func (dumper *Dumper) GlobalExcludeAbsPath(absPaths ...string) {
for _, absPath := range absPaths {
dumper.globalExcludeAbsPaths = append(dumper.globalExcludeAbsPaths, dumper.normalizeFilePath(absPath))
}
}
func (dumper *Dumper) shouldExclude(absPath string, excludes []string) bool {
norm := dumper.normalizeFilePath(absPath)
return slices.Contains(dumper.globalExcludeAbsPaths, norm) || slices.Contains(excludes, norm)
}
func (dumper *Dumper) AddRecursiveExclude(insidePath, absPath string, excludes []string) error {
excludes = slices.Clone(excludes)
for i := range excludes {
excludes[i] = dumper.normalizeFilePath(excludes[i])
}
return dumper.addFileOrDir(insidePath, absPath, excludes)
}
func (dumper *Dumper) addFileOrDir(insidePath, absPath string, excludes []string) error {
absPath, err := filepath.Abs(absPath)
if err != nil {
return err
}
dir, err := os.Open(absPath)
if err != nil {
return err
}
defer dir.Close()
files, err := dir.Readdir(0)
if err != nil {
return err
}
for _, file := range files {
currentAbsPath := filepath.Join(absPath, file.Name())
if dumper.shouldExclude(currentAbsPath, excludes) {
continue
}
currentInsidePath := path.Join(insidePath, file.Name())
if file.IsDir() {
if err := dumper.AddFile(currentInsidePath, currentAbsPath); err != nil {
return err
}
if err = dumper.addFileOrDir(currentInsidePath, currentAbsPath, excludes); err != nil {
return err
}
} else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
shouldAdd := file.Mode().IsRegular()
if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
target, err := filepath.EvalSymlinks(currentAbsPath)
if err != nil {
return err
}
targetStat, err := os.Stat(target)
if err != nil {
return err
}
shouldAdd = targetStat.Mode().IsRegular()
}
if shouldAdd {
if err = dumper.AddFile(currentInsidePath, currentAbsPath); err != nil {
return err
}
}
}
}
return nil
}

113
modules/dump/dumper_test.go Normal file
View File

@ -0,0 +1,113 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package dump
import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"testing"
"time"
"code.gitea.io/gitea/modules/timeutil"
"github.com/mholt/archiver/v3"
"github.com/stretchr/testify/assert"
)
func TestPrepareFileNameAndType(t *testing.T) {
defer timeutil.MockSet(time.Unix(1234, 0))()
test := func(argFile, argType, expFile, expType string) {
outFile, outType := PrepareFileNameAndType(argFile, argType)
assert.Equal(t,
fmt.Sprintf("outFile=%s, outType=%s", expFile, expType),
fmt.Sprintf("outFile=%s, outType=%s", outFile, outType),
fmt.Sprintf("argFile=%s, argType=%s", argFile, argType),
)
}
test("", "", "gitea-dump-1234.zip", "zip")
test("", "tar.gz", "gitea-dump-1234.tar.gz", "tar.gz")
test("", "no-such", "", "")
test("-", "", "-", "zip")
test("-", "tar.gz", "-", "tar.gz")
test("-", "no-such", "", "")
test("a", "", "a", "zip")
test("a", "tar.gz", "a", "tar.gz")
test("a", "no-such", "", "")
test("a.zip", "", "a.zip", "zip")
test("a.zip", "tar.gz", "a.zip", "tar.gz")
test("a.zip", "no-such", "", "")
test("a.tar.gz", "", "a.tar.gz", "zip")
test("a.tar.gz", "tar.gz", "a.tar.gz", "tar.gz")
test("a.tar.gz", "no-such", "", "")
}
func TestIsSubDir(t *testing.T) {
tmpDir := t.TempDir()
_ = os.MkdirAll(filepath.Join(tmpDir, "include/sub"), 0o755)
isSub, err := IsSubdir(filepath.Join(tmpDir, "include"), filepath.Join(tmpDir, "include"))
assert.NoError(t, err)
assert.True(t, isSub)
isSub, err = IsSubdir(filepath.Join(tmpDir, "include"), filepath.Join(tmpDir, "include/sub"))
assert.NoError(t, err)
assert.True(t, isSub)
isSub, err = IsSubdir(filepath.Join(tmpDir, "include/sub"), filepath.Join(tmpDir, "include"))
assert.NoError(t, err)
assert.False(t, isSub)
}
type testWriter struct {
added []string
}
func (t *testWriter) Create(out io.Writer) error {
return nil
}
func (t *testWriter) Write(f archiver.File) error {
t.added = append(t.added, f.Name())
return nil
}
func (t *testWriter) Close() error {
return nil
}
func TestDumper(t *testing.T) {
sortStrings := func(s []string) []string {
sort.Strings(s)
return s
}
tmpDir := t.TempDir()
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude1"), 0o755)
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude2"), 0o755)
_ = os.MkdirAll(filepath.Join(tmpDir, "include/sub"), 0o755)
_ = os.WriteFile(filepath.Join(tmpDir, "include/a"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/sub/b"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/exclude1/a-1"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/exclude2/a-2"), nil, 0o644)
tw := &testWriter{}
d := &Dumper{Writer: tw}
d.GlobalExcludeAbsPath(filepath.Join(tmpDir, "include/exclude1"))
err := d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), []string{filepath.Join(tmpDir, "include/exclude2")})
assert.NoError(t, err)
assert.EqualValues(t, sortStrings([]string{"include/a", "include/sub", "include/sub/b"}), sortStrings(tw.added))
tw = &testWriter{}
d = &Dumper{Writer: tw}
err = d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), nil)
assert.NoError(t, err)
assert.EqualValues(t, sortStrings([]string{"include/exclude2", "include/exclude2/a-2", "include/a", "include/sub", "include/sub/b", "include/exclude1", "include/exclude1/a-1"}), sortStrings(tw.added))
}

View File

@ -47,6 +47,12 @@ func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
return nil return nil
} }
if c.Encoding != "" && c.Encoding != "UTF-8" {
if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil {
return nil
}
}
if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
return nil return nil
} }

View File

@ -84,6 +84,8 @@ readLoop:
commit.Committer = &Signature{} commit.Committer = &Signature{}
commit.Committer.Decode(data) commit.Committer.Decode(data)
_, _ = payloadSB.Write(line) _, _ = payloadSB.Write(line)
case "encoding":
_, _ = payloadSB.Write(line)
case "gpgsig": case "gpgsig":
fallthrough fallthrough
case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present. case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.

View File

@ -125,6 +125,73 @@ empty commit`, commitFromReader.Signature.Payload)
assert.EqualValues(t, commitFromReader, commitFromReader2) assert.EqualValues(t, commitFromReader, commitFromReader2)
} }
func TestCommitWithEncodingFromReader(t *testing.T) {
commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
encoding ISO-8859-1
gpgsig -----BEGIN PGP SIGNATURE-----
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr
frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j
FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd
G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn
SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
jw4YcO5u
=r3UU
-----END PGP SIGNATURE-----
ISO-8859-1`
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
assert.NoError(t, err)
assert.NotNil(t, gitRepo)
defer gitRepo.Close()
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
assert.NoError(t, err)
if !assert.NotNil(t, commitFromReader) {
return
}
assert.EqualValues(t, sha, commitFromReader.ID)
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
zOfZraLOEWRH4tZcS+u2yFLu3ez2Wqh1xW5LNy7xqEedMXEFD1HwSJ0+pjacNkzr
frp6Asyt7xRI6YmgFJZJoRsS3Ktr6rtKeRL2IErSQQyorOqj6gKrglhrhfG/114j
FKB1v4or0WZ1DE8iP2SJZ3n+/K1IuWAINh7MVdb7PndfBPEa+IL+ucNk5uzEE8Jd
G8smGxXUeFEt2cP1dj2W8EgAxuA9sTnH9dqI5aRqy5ifDjuya7Emm8sdOUvtGdmn
SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
jw4YcO5u
=r3UU
-----END PGP SIGNATURE-----
`, commitFromReader.Signature.Signature)
assert.EqualValues(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
encoding ISO-8859-1
ISO-8859-1`, commitFromReader.Signature.Payload)
assert.EqualValues(t, "KN4CK3R <admin@oldschoolhack.me>", commitFromReader.Author.String())
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err)
commitFromReader.CommitMessage += "\n\n"
commitFromReader.Signature.Payload += "\n\n"
assert.EqualValues(t, commitFromReader, commitFromReader2)
}
func TestHasPreviousCommit(t *testing.T) { func TestHasPreviousCommit(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")

View File

@ -10,6 +10,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"slices"
"strconv" "strconv"
"strings" "strings"
@ -27,6 +28,7 @@ type GrepOptions struct {
MaxResultLimit int MaxResultLimit int
ContextLineNumber int ContextLineNumber int
IsFuzzy bool IsFuzzy bool
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
} }
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) { func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
@ -71,10 +73,20 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
defer stdoutReader.Close() defer stdoutReader.Close()
isInBlock := false isInBlock := false
scanner := bufio.NewScanner(stdoutReader) rd := bufio.NewReaderSize(stdoutReader, util.IfZero(opts.MaxLineLength, 16*1024))
var res *GrepResult var res *GrepResult
for scanner.Scan() { for {
line := scanner.Text() lineBytes, isPrefix, err := rd.ReadLine()
if isPrefix {
lineBytes = slices.Clone(lineBytes)
for isPrefix && err == nil {
_, isPrefix, err = rd.ReadLine()
}
}
if len(lineBytes) == 0 && err != nil {
break
}
line := string(lineBytes) // the memory of lineBytes is mutable
if !isInBlock { if !isInBlock {
if _ /* ref */, filename, ok := strings.Cut(line, ":"); ok { if _ /* ref */, filename, ok := strings.Cut(line, ":"); ok {
isInBlock = true isInBlock = true
@ -100,7 +112,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
res.LineCodes = append(res.LineCodes, lineCode) res.LineCodes = append(res.LineCodes, lineCode)
} }
} }
return scanner.Err() return nil
}, },
}) })
// git grep exits by cancel (killed), usually it is caused by the limit of results // git grep exits by cancel (killed), usually it is caused by the limit of results

View File

@ -41,6 +41,16 @@ func TestGrepSearch(t *testing.T) {
}, },
}, res) }, res)
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1, MaxLineLength: 39})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
Filename: "java-hello/main.java",
LineNumbers: []int{3},
LineCodes: []string{" public static void main(String[] arg"},
},
}, res)
res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{}) res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, res, 0) assert.Len(t, res, 0)

View File

@ -22,7 +22,7 @@ type Result struct {
UpdatedUnix timeutil.TimeStamp UpdatedUnix timeutil.TimeStamp
Language string Language string
Color string Color string
Lines []ResultLine Lines []*ResultLine
} }
type ResultLine struct { type ResultLine struct {
@ -70,16 +70,18 @@ func writeStrings(buf *bytes.Buffer, strs ...string) error {
return nil return nil
} }
func HighlightSearchResultCode(filename string, lineNums []int, code string) []ResultLine { func HighlightSearchResultCode(filename, language string, lineNums []int, code string) []*ResultLine {
// we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting // we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
hl, _ := highlight.Code(filename, "", code) hl, _ := highlight.Code(filename, language, code)
highlightedLines := strings.Split(string(hl), "\n") highlightedLines := strings.Split(string(hl), "\n")
// The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n` // The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n`
lines := make([]ResultLine, min(len(highlightedLines), len(lineNums))) lines := make([]*ResultLine, min(len(highlightedLines), len(lineNums)))
for i := 0; i < len(lines); i++ { for i := 0; i < len(lines); i++ {
lines[i].Num = lineNums[i] lines[i] = &ResultLine{
lines[i].FormattedContent = template.HTML(highlightedLines[i]) Num: lineNums[i],
FormattedContent: template.HTML(highlightedLines[i]),
}
} }
return lines return lines
} }
@ -122,7 +124,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
UpdatedUnix: result.UpdatedUnix, UpdatedUnix: result.UpdatedUnix,
Language: result.Language, Language: result.Language,
Color: result.Color, Color: result.Color,
Lines: HighlightSearchResultCode(result.Filename, lineNums, formattedLinesBuffer.String()), Lines: HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String()),
}, nil }, nil
} }

View File

@ -44,7 +44,7 @@ func (c *FilesystemClient) Download(ctx context.Context, objects []Pointer, call
if err != nil { if err != nil {
return err return err
} }
defer f.Close()
if err := callback(p, f, nil); err != nil { if err := callback(p, f, nil); err != nil {
return err return err
} }
@ -75,7 +75,7 @@ func (c *FilesystemClient) Upload(ctx context.Context, objects []Pointer, callba
if err != nil { if err != nil {
return err return err
} }
defer f.Close()
_, err = io.Copy(f, content) _, err = io.Copy(f, content)
return err return err

View File

@ -171,6 +171,7 @@ type processor func(ctx *RenderContext, node *html.Node)
var defaultProcessors = []processor{ var defaultProcessors = []processor{
fullIssuePatternProcessor, fullIssuePatternProcessor,
comparePatternProcessor, comparePatternProcessor,
codePreviewPatternProcessor,
fullHashPatternProcessor, fullHashPatternProcessor,
shortLinkProcessor, shortLinkProcessor,
linkProcessor, linkProcessor,

View File

@ -0,0 +1,92 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markup
import (
"html/template"
"net/url"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"golang.org/x/net/html"
)
// codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20"
var codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`)
type RenderCodePreviewOptions struct {
FullURL string
OwnerName string
RepoName string
CommitID string
FilePath string
LineStart, LineStop int
}
func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosStop int, htm template.HTML, err error) {
m := codePreviewPattern.FindStringSubmatchIndex(node.Data)
if m == nil {
return 0, 0, "", nil
}
opts := RenderCodePreviewOptions{
FullURL: node.Data[m[0]:m[1]],
OwnerName: node.Data[m[2]:m[3]],
RepoName: node.Data[m[4]:m[5]],
CommitID: node.Data[m[6]:m[7]],
FilePath: node.Data[m[8]:m[9]],
}
if !httplib.IsCurrentGiteaSiteURL(opts.FullURL) {
return 0, 0, "", nil
}
u, err := url.Parse(opts.FilePath)
if err != nil {
return 0, 0, "", err
}
opts.FilePath = strings.TrimPrefix(u.Path, "/")
lineStartStr, lineStopStr, _ := strings.Cut(node.Data[m[10]:m[11]], "-")
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
opts.LineStart, opts.LineStop = lineStart, lineStop
h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx.Ctx, opts)
return m[0], m[1], h, err
}
func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
for node != nil {
if node.Type != html.TextNode {
node = node.NextSibling
continue
}
urlPosStart, urlPosEnd, h, err := renderCodeBlock(ctx, node)
if err != nil || h == "" {
if err != nil {
log.Error("Unable to render code preview: %v", err)
}
node = node.NextSibling
continue
}
next := node.NextSibling
textBefore := node.Data[:urlPosStart]
textAfter := node.Data[urlPosEnd:]
// "textBefore" could be empty if there is only a URL in the text node, then an empty node (p, or li) will be left here.
// However, the empty node can't be simply removed, because:
// 1. the following processors will still try to access it (need to double-check undefined behaviors)
// 2. the new node is inserted as "<p>{TextBefore}<div NewNode/>{TextAfter}</p>" (the parent could also be "li")
// then it is resolved as: "<p>{TextBefore}</p><div NewNode/><p>{TextAfter}</p>",
// so unless it could correctly replace the parent "p/li" node, it is very difficult to eliminate the "TextBefore" empty node.
node.Data = textBefore
node.Parent.InsertBefore(&html.Node{Type: html.RawNode, Data: string(h)}, next)
if textAfter != "" {
node.Parent.InsertBefore(&html.Node{Type: html.TextNode, Data: textAfter}, next)
}
node = next
}
}

View File

@ -0,0 +1,34 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markup_test
import (
"context"
"html/template"
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup"
"github.com/stretchr/testify/assert"
)
func TestRenderCodePreview(t *testing.T) {
markup.Init(&markup.ProcessorHelper{
RenderRepoFileCodePreview: func(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) {
return "<div>code preview</div>", nil
},
})
test := func(input, expected string) {
buffer, err := markup.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext,
Type: "markdown",
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
}

View File

@ -436,6 +436,10 @@ func TestColorPreview(t *testing.T) {
testcase string testcase string
expected string expected string
}{ }{
{ // do not render color names
"The CSS class `red` is there",
"<p>The CSS class <code>red</code> is there</p>\n",
},
{ // hex { // hex
"`#FF0000`", "`#FF0000`",
`<p><code>#FF0000<span class="color-preview" style="background-color: #FF0000"></span></code></p>` + nl, `<p><code>#FF0000<span class="color-preview" style="background-color: #FF0000"></span></code></p>` + nl,
@ -445,8 +449,8 @@ func TestColorPreview(t *testing.T) {
`<p><code>rgb(16, 32, 64)<span class="color-preview" style="background-color: rgb(16, 32, 64)"></span></code></p>` + nl, `<p><code>rgb(16, 32, 64)<span class="color-preview" style="background-color: rgb(16, 32, 64)"></span></code></p>` + nl,
}, },
{ // short hex { // short hex
"This is the color white `#000`", "This is the color white `#0a0`",
`<p>This is the color white <code>#000<span class="color-preview" style="background-color: #000"></span></code></p>` + nl, `<p>This is the color white <code>#0a0<span class="color-preview" style="background-color: #0a0"></span></code></p>` + nl,
}, },
{ // hsl { // hsl
"HSL stands for hue, saturation, and lightness. An example: `hsl(0, 100%, 50%)`.", "HSL stands for hue, saturation, and lightness. An example: `hsl(0, 100%, 50%)`.",
@ -507,9 +511,17 @@ func TestMathBlock(t *testing.T) {
`\(a\) \(b\)`, `\(a\) \(b\)`,
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl, `<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
}, },
{
`$a$.`,
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
},
{
`.$a$`,
`<p>.$a$</p>` + nl,
},
{ {
`$a a$b b$`, `$a a$b b$`,
`<p><code class="language-math is-loading">a a$b b</code></p>` + nl, `<p>$a a$b b$</p>` + nl,
}, },
{ {
`a a$b b`, `a a$b b`,
@ -517,7 +529,15 @@ func TestMathBlock(t *testing.T) {
}, },
{ {
`a$b $a a$b b$`, `a$b $a a$b b$`,
`<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl, `<p>a$b $a a$b b$</p>` + nl,
},
{
"a$x$",
`<p>a$x$</p>` + nl,
},
{
"$x$a",
`<p>$x$a</p>` + nl,
}, },
{ {
"$$a$$", "$$a$$",

View File

@ -41,9 +41,12 @@ func (parser *inlineParser) Trigger() []byte {
return parser.start[0:1] return parser.start[0:1]
} }
func isPunctuation(b byte) bool {
return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
}
func isAlphanumeric(b byte) bool { func isAlphanumeric(b byte) bool {
// Github only cares about 0-9A-Za-z return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
} }
// Parse parses the current line and returns a result of parsing. // Parse parses the current line and returns a result of parsing.
@ -56,7 +59,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
} }
precedingCharacter := block.PrecendingCharacter() precedingCharacter := block.PrecendingCharacter()
if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) { if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
// need to exclude things like `a$` from being considered a start // need to exclude things like `a$` from being considered a start
return nil return nil
} }
@ -75,14 +78,19 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
ender += pos ender += pos
// Now we want to check the character at the end of our parser section // Now we want to check the character at the end of our parser section
// that is ender + len(parser.end) // that is ender + len(parser.end) and check if char before ender is '\'
pos = ender + len(parser.end) pos = ender + len(parser.end)
if len(line) <= pos { if len(line) <= pos {
break break
} }
if !isAlphanumeric(line[pos]) { suceedingCharacter := line[pos]
if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') {
return nil
}
if line[ender-1] != '\\' {
break break
} }
// move the pointer onwards // move the pointer onwards
ender += len(parser.end) ender += len(parser.end)
} }

View File

@ -49,9 +49,28 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod
return ast.WalkContinue, nil return ast.WalkContinue, nil
} }
// cssColorHandler checks if a string is a render-able CSS color value.
// The code is from "github.com/microcosm-cc/bluemonday/css.ColorHandler", except that it doesn't handle color words like "red".
func cssColorHandler(value string) bool {
value = strings.ToLower(value)
if css.HexRGB.MatchString(value) {
return true
}
if css.RGB.MatchString(value) {
return true
}
if css.RGBA.MatchString(value) {
return true
}
if css.HSL.MatchString(value) {
return true
}
return css.HSLA.MatchString(value)
}
func (g *ASTTransformer) transformCodeSpan(ctx *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) { func (g *ASTTransformer) transformCodeSpan(ctx *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) {
colorContent := v.Text(reader.Source()) colorContent := v.Text(reader.Source())
if css.ColorHandler(strings.ToLower(string(colorContent))) { if cssColorHandler(string(colorContent)) {
v.AppendChild(v, NewColorPreview(colorContent)) v.AppendChild(v, NewColorPreview(colorContent))
} }
} }

View File

@ -8,6 +8,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"html/template"
"io" "io"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -33,6 +34,8 @@ type ProcessorHelper struct {
IsUsernameMentionable func(ctx context.Context, username string) bool IsUsernameMentionable func(ctx context.Context, username string) bool
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
} }
var DefaultProcessorHelper ProcessorHelper var DefaultProcessorHelper ProcessorHelper

View File

@ -60,6 +60,21 @@ func createDefaultPolicy() *bluemonday.Policy {
// For JS code copy and Mermaid loading state // For JS code copy and Mermaid loading state
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-block( is-loading)?$`)).OnElements("pre") policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-block( is-loading)?$`)).OnElements("pre")
// For code preview
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-preview-[-\w]+( file-content)?$`)).Globally()
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-num$`)).OnElements("td")
policy.AllowAttrs("data-line-number").OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-code chroma$`)).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-inner$`)).OnElements("div")
// For code preview (unicode escape)
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^file-view( unicode-escaped)?$`)).OnElements("table")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-escape$`)).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^toggle-escape-button btn interact-bg$`)).OnElements("a") // don't use button, button might submit a form
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(ambiguous-code-point|escaped-code-point|broken-code-point)$`)).OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^char$`)).OnElements("span")
policy.AllowAttrs("data-tooltip-content", "data-escaped").OnElements("span")
// For color preview // For color preview
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span") policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span")

View File

@ -58,6 +58,7 @@ type Package struct {
type Metadata struct { type Metadata struct {
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
ReleaseNotes string `json:"release_notes,omitempty"` ReleaseNotes string `json:"release_notes,omitempty"`
Readme string `json:"readme,omitempty"`
Authors string `json:"authors,omitempty"` Authors string `json:"authors,omitempty"`
ProjectURL string `json:"project_url,omitempty"` ProjectURL string `json:"project_url,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"` RepositoryURL string `json:"repository_url,omitempty"`
@ -71,6 +72,7 @@ type Dependency struct {
Version string `json:"version"` Version string `json:"version"`
} }
// https://learn.microsoft.com/en-us/nuget/reference/nuspec
type nuspecPackage struct { type nuspecPackage struct {
Metadata struct { Metadata struct {
ID string `xml:"id"` ID string `xml:"id"`
@ -80,6 +82,7 @@ type nuspecPackage struct {
ProjectURL string `xml:"projectUrl"` ProjectURL string `xml:"projectUrl"`
Description string `xml:"description"` Description string `xml:"description"`
ReleaseNotes string `xml:"releaseNotes"` ReleaseNotes string `xml:"releaseNotes"`
Readme string `xml:"readme"`
PackageTypes struct { PackageTypes struct {
PackageType []struct { PackageType []struct {
Name string `xml:"name,attr"` Name string `xml:"name,attr"`
@ -89,6 +92,11 @@ type nuspecPackage struct {
URL string `xml:"url,attr"` URL string `xml:"url,attr"`
} `xml:"repository"` } `xml:"repository"`
Dependencies struct { Dependencies struct {
Dependency []struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr"`
} `xml:"dependency"`
Group []struct { Group []struct {
TargetFramework string `xml:"targetFramework,attr"` TargetFramework string `xml:"targetFramework,attr"`
Dependency []struct { Dependency []struct {
@ -122,14 +130,14 @@ func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) {
} }
defer f.Close() defer f.Close()
return ParseNuspecMetaData(f) return ParseNuspecMetaData(archive, f)
} }
} }
return nil, ErrMissingNuspecFile return nil, ErrMissingNuspecFile
} }
// ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package // ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package
func ParseNuspecMetaData(r io.Reader) (*Package, error) { func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
var p nuspecPackage var p nuspecPackage
if err := xml.NewDecoder(r).Decode(&p); err != nil { if err := xml.NewDecoder(r).Decode(&p); err != nil {
return nil, err return nil, err
@ -166,6 +174,28 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
Dependencies: make(map[string][]Dependency), Dependencies: make(map[string][]Dependency),
} }
if p.Metadata.Readme != "" {
f, err := archive.Open(p.Metadata.Readme)
if err == nil {
buf, _ := io.ReadAll(f)
m.Readme = string(buf)
_ = f.Close()
}
}
if len(p.Metadata.Dependencies.Dependency) > 0 {
deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency))
for _, dep := range p.Metadata.Dependencies.Dependency {
if dep.ID == "" || dep.Version == "" {
continue
}
deps = append(deps, Dependency{
ID: dep.ID,
Version: dep.Version,
})
}
m.Dependencies[""] = deps
}
for _, group := range p.Metadata.Dependencies.Group { for _, group := range p.Metadata.Dependencies.Group {
deps := make([]Dependency, 0, len(group.Dependency)) deps := make([]Dependency, 0, len(group.Dependency))
for _, dep := range group.Dependency { for _, dep := range group.Dependency {

View File

@ -6,7 +6,6 @@ package nuget
import ( import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -19,6 +18,7 @@ const (
projectURL = "https://gitea.io" projectURL = "https://gitea.io"
description = "Package Description" description = "Package Description"
releaseNotes = "Package Release Notes" releaseNotes = "Package Release Notes"
readme = "Readme"
repositoryURL = "https://gitea.io/gitea/gitea" repositoryURL = "https://gitea.io/gitea/gitea"
targetFramework = ".NETStandard2.1" targetFramework = ".NETStandard2.1"
dependencyID = "System.Text.Json" dependencyID = "System.Text.Json"
@ -36,6 +36,7 @@ const nuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<description>` + description + `</description> <description>` + description + `</description>
<releaseNotes>` + releaseNotes + `</releaseNotes> <releaseNotes>` + releaseNotes + `</releaseNotes>
<repository url="` + repositoryURL + `" /> <repository url="` + repositoryURL + `" />
<readme>README.md</readme>
<dependencies> <dependencies>
<group targetFramework="` + targetFramework + `"> <group targetFramework="` + targetFramework + `">
<dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" /> <dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
@ -60,17 +61,19 @@ const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?>
</package>` </package>`
func TestParsePackageMetaData(t *testing.T) { func TestParsePackageMetaData(t *testing.T) {
createArchive := func(name, content string) []byte { createArchive := func(files map[string]string) []byte {
var buf bytes.Buffer var buf bytes.Buffer
archive := zip.NewWriter(&buf) archive := zip.NewWriter(&buf)
w, _ := archive.Create(name) for name, content := range files {
w.Write([]byte(content)) w, _ := archive.Create(name)
w.Write([]byte(content))
}
archive.Close() archive.Close()
return buf.Bytes() return buf.Bytes()
} }
t.Run("MissingNuspecFile", func(t *testing.T) { t.Run("MissingNuspecFile", func(t *testing.T) {
data := createArchive("dummy.txt", "") data := createArchive(map[string]string{"dummy.txt": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np) assert.Nil(t, np)
@ -78,7 +81,7 @@ func TestParsePackageMetaData(t *testing.T) {
}) })
t.Run("MissingNuspecFileInRoot", func(t *testing.T) { t.Run("MissingNuspecFileInRoot", func(t *testing.T) {
data := createArchive("sub/package.nuspec", "") data := createArchive(map[string]string{"sub/package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np) assert.Nil(t, np)
@ -86,7 +89,7 @@ func TestParsePackageMetaData(t *testing.T) {
}) })
t.Run("InvalidNuspecFile", func(t *testing.T) { t.Run("InvalidNuspecFile", func(t *testing.T) {
data := createArchive("package.nuspec", "") data := createArchive(map[string]string{"package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np) assert.Nil(t, np)
@ -94,10 +97,10 @@ func TestParsePackageMetaData(t *testing.T) {
}) })
t.Run("InvalidPackageId", func(t *testing.T) { t.Run("InvalidPackageId", func(t *testing.T) {
data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?> data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata></metadata> <metadata></metadata>
</package>`) </package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np) assert.Nil(t, np)
@ -105,30 +108,34 @@ func TestParsePackageMetaData(t *testing.T) {
}) })
t.Run("InvalidPackageVersion", func(t *testing.T) { t.Run("InvalidPackageVersion", func(t *testing.T) {
data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?> data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata> <metadata>
<id>`+id+`</id> <id>` + id + `</id>
</metadata> </metadata>
</package>`) </package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np) assert.Nil(t, np)
assert.ErrorIs(t, err, ErrNuspecInvalidVersion) assert.ErrorIs(t, err, ErrNuspecInvalidVersion)
}) })
t.Run("Valid", func(t *testing.T) { t.Run("MissingReadme", func(t *testing.T) {
data := createArchive("package.nuspec", nuspecContent) data := createArchive(map[string]string{"package.nuspec": nuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, np) assert.NotNil(t, np)
assert.Empty(t, np.Metadata.Readme)
}) })
}
func TestParseNuspecMetaData(t *testing.T) {
t.Run("Dependency Package", func(t *testing.T) { t.Run("Dependency Package", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(nuspecContent)) data := createArchive(map[string]string{
"package.nuspec": nuspecContent,
"README.md": readme,
})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, np) assert.NotNil(t, np)
assert.Equal(t, DependencyPackage, np.PackageType) assert.Equal(t, DependencyPackage, np.PackageType)
@ -139,6 +146,7 @@ func TestParseNuspecMetaData(t *testing.T) {
assert.Equal(t, projectURL, np.Metadata.ProjectURL) assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, description, np.Metadata.Description) assert.Equal(t, description, np.Metadata.Description)
assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
assert.Equal(t, readme, np.Metadata.Readme)
assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL)
assert.Len(t, np.Metadata.Dependencies, 1) assert.Len(t, np.Metadata.Dependencies, 1)
assert.Contains(t, np.Metadata.Dependencies, targetFramework) assert.Contains(t, np.Metadata.Dependencies, targetFramework)
@ -148,13 +156,15 @@ func TestParseNuspecMetaData(t *testing.T) {
assert.Equal(t, dependencyVersion, deps[0].Version) assert.Equal(t, dependencyVersion, deps[0].Version)
t.Run("NormalizedVersion", func(t *testing.T) { t.Run("NormalizedVersion", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(`<?xml version="1.0" encoding="utf-8"?> data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata> <metadata>
<id>test</id> <id>test</id>
<version>1.04.5.2.5-rc.1+metadata</version> <version>1.04.5.2.5-rc.1+metadata</version>
</metadata> </metadata>
</package>`)) </package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, np) assert.NotNil(t, np)
assert.Equal(t, "1.4.5.2-rc.1", np.Version) assert.Equal(t, "1.4.5.2-rc.1", np.Version)
@ -162,7 +172,9 @@ func TestParseNuspecMetaData(t *testing.T) {
}) })
t.Run("Symbols Package", func(t *testing.T) { t.Run("Symbols Package", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(symbolsNuspecContent)) data := createArchive(map[string]string{"package.nuspec": symbolsNuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, np) assert.NotNil(t, np)
assert.Equal(t, SymbolsPackage, np.PackageType) assert.Equal(t, SymbolsPackage, np.PackageType)

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -32,13 +33,13 @@ const (
) )
// Bool checks for a key in the map and parses as a boolean // Bool checks for a key in the map and parses as a boolean
func (g GitPushOptions) Bool(key string, def bool) bool { func (g GitPushOptions) Bool(key string) optional.Option[bool] {
if val, ok := g[key]; ok { if val, ok := g[key]; ok {
if b, err := strconv.ParseBool(val); err == nil { if b, err := strconv.ParseBool(val); err == nil {
return b return optional.Some(b)
} }
} }
return def return optional.None[bool]()
} }
// HookOptions represents the options for the Hook calls // HookOptions represents the options for the Hook calls
@ -87,13 +88,17 @@ type HookProcReceiveResult struct {
// HookProcReceiveRefResult represents an individual result from ProcReceive // HookProcReceiveRefResult represents an individual result from ProcReceive
type HookProcReceiveRefResult struct { type HookProcReceiveRefResult struct {
OldOID string OldOID string
NewOID string NewOID string
Ref string Ref string
OriginalRef git.RefName OriginalRef git.RefName
IsForcePush bool IsForcePush bool
IsNotMatched bool IsNotMatched bool
Err string Err string
IsCreatePR bool
URL string
ShouldShowMessage bool
HeadBranch string
} }
// HookPreReceive check whether the provided commits are allowed // HookPreReceive check whether the provided commits are allowed

View File

@ -18,6 +18,12 @@ type Store interface {
// RegenerateSession regenerates the underlying session and returns the new store // RegenerateSession regenerates the underlying session and returns the new store
func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, error) { func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, error) {
for _, f := range BeforeRegenerateSession {
f(resp, req)
}
s, err := session.RegenerateSession(resp, req) s, err := session.RegenerateSession(resp, req)
return s, err return s, err
} }
// BeforeRegenerateSession is a list of functions that are called before a session is regenerated.
var BeforeRegenerateSession []func(http.ResponseWriter, *http.Request)

View File

@ -315,21 +315,25 @@ func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
} }
} }
// DeprecatedWarnings contains the warning message for various deprecations, including: setting option, file/folder, etc // StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc
var DeprecatedWarnings []string var StartupProblems []string
func logStartupProblem(skip int, level log.Level, format string, args ...any) {
msg := fmt.Sprintf(format, args...)
log.Log(skip+1, level, "%s", msg)
StartupProblems = append(StartupProblems, msg)
}
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) { func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
if rootCfg.Section(oldSection).HasKey(oldKey) { if rootCfg.Section(oldSection).HasKey(oldKey) {
msg := fmt.Sprintf("Deprecated config option `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version) logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
log.Error("%v", msg)
DeprecatedWarnings = append(DeprecatedWarnings, msg)
} }
} }
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini // deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
if rootCfg.Section(oldSection).HasKey(oldKey) { if rootCfg.Section(oldSection).HasKey(oldKey) {
log.Error("Deprecated `[%s]` `%s` present which has been copied to database table sys_setting", oldSection, oldKey) logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
} }
} }

View File

@ -58,7 +58,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
if !filepath.IsAbs(Indexer.IssuePath) { if !filepath.IsAbs(Indexer.IssuePath) {
Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath)) Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath))
} }
fatalDuplicatedPath("issue_indexer", Indexer.IssuePath) checkOverlappedPath("[indexer].ISSUE_INDEXER_PATH", Indexer.IssuePath)
} else { } else {
Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr) Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr)
if Indexer.IssueType == "meilisearch" { if Indexer.IssueType == "meilisearch" {

View File

@ -185,8 +185,13 @@ func InitLoggersForTest() {
initAllLoggers() initAllLoggers()
} }
var initLoggerDisabled bool
// initAllLoggers creates all the log services // initAllLoggers creates all the log services
func initAllLoggers() { func initAllLoggers() {
if initLoggerDisabled {
return
}
initManagedLoggers(log.GetManager(), CfgProvider) initManagedLoggers(log.GetManager(), CfgProvider)
golog.SetFlags(0) golog.SetFlags(0)
@ -194,6 +199,10 @@ func initAllLoggers() {
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
} }
func DisableLoggerInit() {
initLoggerDisabled = true
}
func initManagedLoggers(manager *log.LoggerManager, cfg ConfigProvider) { func initManagedLoggers(manager *log.LoggerManager, cfg ConfigProvider) {
loadLogGlobalFrom(cfg) loadLogGlobalFrom(cfg)
prepareLoggerConfig(cfg) prepareLoggerConfig(cfg)

View File

@ -118,6 +118,10 @@ func loadOAuth2From(rootCfg ConfigProvider) {
return return
} }
if sec.HasKey("DEFAULT_APPLICATIONS") && sec.Key("DEFAULT_APPLICATIONS").String() == "" {
OAuth2.DefaultApplications = nil
}
// Handle the rename of ENABLE to ENABLED // Handle the rename of ENABLE to ENABLED
deprecatedSetting(rootCfg, "oauth2", "ENABLE", "oauth2", "ENABLED", "v1.23.0") deprecatedSetting(rootCfg, "oauth2", "ENABLE", "oauth2", "ENABLED", "v1.23.0")
if sec.HasKey("ENABLE") && !sec.HasKey("ENABLED") { if sec.HasKey("ENABLE") && !sec.HasKey("ENABLED") {
@ -168,7 +172,7 @@ func GetGeneralTokenSigningSecret() []byte {
} }
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...) // FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
log.Warn("OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes") logStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
return jwtSecret return jwtSecret
} }
return *generalSigningSecret.Load() return *generalSigningSecret.Load()

View File

@ -32,3 +32,21 @@ JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
assert.Len(t, actual, 32) assert.Len(t, actual, 32)
assert.EqualValues(t, expected, actual) assert.EqualValues(t, expected, actual)
} }
func TestOauth2DefaultApplications(t *testing.T) {
cfg, _ := NewConfigProviderFromData(``)
loadOAuth2From(cfg)
assert.Equal(t, []string{"git-credential-oauth", "git-credential-manager", "tea"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS = tea
`)
loadOAuth2From(cfg)
assert.Equal(t, []string{"tea"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS =
`)
loadOAuth2From(cfg)
assert.Nil(t, nil, OAuth2.DefaultApplications)
}

View File

@ -66,12 +66,8 @@ func init() {
AppWorkPath = filepath.Dir(AppPath) AppWorkPath = filepath.Dir(AppPath)
} }
fatalDuplicatedPath("app_work_path", AppWorkPath)
appWorkPathBuiltin = AppWorkPath appWorkPathBuiltin = AppWorkPath
customPathBuiltin = CustomPath customPathBuiltin = CustomPath
fatalDuplicatedPath("custom_path", CustomPath)
customConfBuiltin = CustomConf customConfBuiltin = CustomConf
} }

View File

@ -286,7 +286,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
RepoRootPath = filepath.Clean(RepoRootPath) RepoRootPath = filepath.Clean(RepoRootPath)
} }
fatalDuplicatedPath("repository.ROOT", RepoRootPath) checkOverlappedPath("[repository].ROOT", RepoRootPath)
defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder)) defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder))
for _, charset := range Repository.DetectedCharsetsOrder { for _, charset := range Repository.DetectedCharsetsOrder {

View File

@ -324,7 +324,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
if !filepath.IsAbs(AppDataPath) { if !filepath.IsAbs(AppDataPath) {
AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath)) AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath))
} }
fatalDuplicatedPath("app_data_path", AppDataPath)
EnableGzip = sec.Key("ENABLE_GZIP").MustBool() EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
@ -332,7 +331,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
if !filepath.IsAbs(PprofDataPath) { if !filepath.IsAbs(PprofDataPath) {
PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath) PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
} }
fatalDuplicatedPath("pprof_data_path", PprofDataPath) checkOverlappedPath("[server].PPROF_DATA_PATH", PprofDataPath)
landingPage := sec.Key("LANDING_PAGE").MustString("home") landingPage := sec.Key("LANDING_PAGE").MustString("home")
switch landingPage { switch landingPage {

View File

@ -46,7 +46,7 @@ func loadSessionFrom(rootCfg ConfigProvider) {
SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(filepath.Join(AppDataPath, "sessions")), "\" ") SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(filepath.Join(AppDataPath, "sessions")), "\" ")
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) { if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = filepath.Join(AppWorkPath, SessionConfig.ProviderConfig) SessionConfig.ProviderConfig = filepath.Join(AppWorkPath, SessionConfig.ProviderConfig)
fatalDuplicatedPath("session", SessionConfig.ProviderConfig) checkOverlappedPath("[session].PROVIDER_CONFIG", SessionConfig.ProviderConfig)
} }
SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea") SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
SessionConfig.CookiePath = AppSubURL SessionConfig.CookiePath = AppSubURL

View File

@ -230,11 +230,12 @@ func LoadSettingsForInstall() {
loadMailerFrom(CfgProvider) loadMailerFrom(CfgProvider)
} }
var uniquePaths = make(map[string]string) var configuredPaths = make(map[string]string)
func fatalDuplicatedPath(name, p string) { func checkOverlappedPath(name, path string) {
if targetName, ok := uniquePaths[p]; ok && targetName != name { // TODO: some paths shouldn't overlap (storage.xxx.path), while some could (data path is the base path for storage path)
log.Fatal("storage path %q is being used by %q and %q and all storage paths must be unique to prevent data loss.", p, targetName, name) if targetName, ok := configuredPaths[path]; ok && targetName != name {
logStartupProblem(1, log.ERROR, "Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
} }
uniquePaths[p] = name configuredPaths[path] = name
} }

View File

@ -240,7 +240,7 @@ func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType,
} }
} }
fatalDuplicatedPath("storage."+name, storage.Path) checkOverlappedPath("[storage."+name+"].PATH", storage.Path)
return &storage, nil return &storage, nil
} }

View File

@ -53,13 +53,13 @@ func NewFuncMap() template.FuncMap {
"JsonUtils": NewJsonUtils, "JsonUtils": NewJsonUtils,
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// svg / avatar / icon // svg / avatar / icon / color
"svg": svg.RenderHTML, "svg": svg.RenderHTML,
"EntryIcon": base.EntryIcon, "EntryIcon": base.EntryIcon,
"MigrationIcon": MigrationIcon, "MigrationIcon": MigrationIcon,
"ActionIcon": ActionIcon, "ActionIcon": ActionIcon,
"SortArrow": SortArrow,
"SortArrow": SortArrow, "ContrastColor": util.ContrastColor,
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// time / number / format // time / number / format

View File

@ -142,35 +142,39 @@ type remoteAddress struct {
Password string Password string
} }
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress { func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
a := remoteAddress{} ret := remoteAddress{}
remoteURL, err := git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
remoteURL := m.OriginalURL if err != nil {
if ignoreOriginalURL || remoteURL == "" { log.Error("GetRemoteURL %v", err)
var err error return ret
remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
if err != nil {
log.Error("GetRemoteURL %v", err)
return a
}
} }
u, err := giturl.Parse(remoteURL) u, err := giturl.Parse(remoteURL)
if err != nil { if err != nil {
log.Error("giturl.Parse %v", err) log.Error("giturl.Parse %v", err)
return a return ret
} }
if u.Scheme != "ssh" && u.Scheme != "file" { if u.Scheme != "ssh" && u.Scheme != "file" {
if u.User != nil { if u.User != nil {
a.Username = u.User.Username() ret.Username = u.User.Username()
a.Password, _ = u.User.Password() ret.Password, _ = u.User.Password()
} }
u.User = nil
} }
a.Address = u.String()
return a // The URL stored in the git repo could contain authentication,
// erase it, or it will be shown in the UI.
u.User = nil
ret.Address = u.String()
// Why not use m.OriginalURL to set ret.Address?
// It should be OK to use it, since m.OriginalURL should be the same as the authentication-erased URL from the Git repository.
// However, the old code has already stored authentication in m.OriginalURL when updating mirror settings.
// That means we need to use "giturl.Parse" for m.OriginalURL again to ensure authentication is erased.
// Instead of doing this, why not directly use the authentication-erased URL from the Git repository?
// It should be the same as long as there are no bugs.
return ret
} }
func FilenameIsImage(filename string) bool { func FilenameIsImage(filename string) bool {

View File

@ -123,16 +123,10 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML { func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
var ( var (
archivedCSSClass string archivedCSSClass string
textColor = "#111" textColor = util.ContrastColor(label.Color)
labelScope = label.ExclusiveScope() labelScope = label.ExclusiveScope()
) )
r, g, b := util.HexToRBGColor(label.Color)
// Determine if label text should be light or dark to be readable on background color
if util.UseLightTextOnBackground(r, g, b) {
textColor = "#eee"
}
description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description))
if label.IsArchived() { if label.IsArchived() {
@ -153,7 +147,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
// Make scope and item background colors slightly darker and lighter respectively. // Make scope and item background colors slightly darker and lighter respectively.
// More contrast needed with higher luminance, empirically tweaked. // More contrast needed with higher luminance, empirically tweaked.
luminance := util.GetLuminance(r, g, b) luminance := util.GetRelativeLuminance(label.Color)
contrast := 0.01 + luminance*0.03 contrast := 0.01 + luminance*0.03
// Ensure we add the same amount of contrast also near 0 and 1. // Ensure we add the same amount of contrast also near 0 and 1.
darken := contrast + math.Max(luminance+contrast-1.0, 0.0) darken := contrast + math.Max(luminance+contrast-1.0, 0.0)
@ -162,6 +156,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0) darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0)
lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0) lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0)
r, g, b := util.HexToRBGColor(label.Color)
scopeBytes := []byte{ scopeBytes := []byte{
uint8(math.Min(math.Round(r*darkenFactor), 255)), uint8(math.Min(math.Round(r*darkenFactor), 255)),
uint8(math.Min(math.Round(g*darkenFactor), 255)), uint8(math.Min(math.Round(g*darkenFactor), 255)),
@ -221,15 +216,16 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
return output return output
} }
func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string) template.HTML { func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
isPullRequest := issue != nil && issue.IsPull
baseLink := fmt.Sprintf("%s/%s", repoLink, util.Iif(isPullRequest, "pulls", "issues"))
htmlCode := `<span class="labels-list">` htmlCode := `<span class="labels-list">`
for _, label := range labels { for _, label := range labels {
// Protect against nil value in labels - shouldn't happen but would cause a panic if so // Protect against nil value in labels - shouldn't happen but would cause a panic if so
if label == nil { if label == nil {
continue continue
} }
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ", htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, RenderLabel(ctx, locale, label))
repoLink, label.ID, RenderLabel(ctx, locale, label))
} }
htmlCode += "</span>" htmlCode += "</span>"
return template.HTML(htmlCode) return template.HTML(htmlCode)

View File

@ -7,17 +7,21 @@ import (
"context" "context"
"html/template" "html/template"
"os" "os"
"strings"
"testing" "testing"
"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const testInput = ` space @mention-user func testInput() string {
s := ` space @mention-user<SPACE><SPACE>
/just/a/path.bin /just/a/path.bin
https://example.com/file.bin https://example.com/file.bin
[local link](file.bin) [local link](file.bin)
@ -36,8 +40,10 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com mail@domain.com
@mention-user test @mention-user test
#123 #123
space space<SPACE><SPACE>
` `
return strings.ReplaceAll(s, "<SPACE>", " ")
}
var testMetas = map[string]string{ var testMetas = map[string]string{
"user": "user13", "user": "user13",
@ -121,23 +127,23 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a> <a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space` space`
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas)) assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput(), testMetas))
} }
func TestRenderCommitMessage(t *testing.T) { func TestRenderCommitMessage(t *testing.T) {
expected := `space <a href="/mention-user" class="mention">@mention-user</a> ` expected := `space <a href="/mention-user" class="mention">@mention-user</a> `
assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas)) assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput(), testMetas))
} }
func TestRenderCommitMessageLinkSubject(t *testing.T) { func TestRenderCommitMessageLinkSubject(t *testing.T) {
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" class="mention">@mention-user</a>` expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" class="mention">@mention-user</a>`
assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas)) assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput(), "https://example.com/link", testMetas))
} }
func TestRenderIssueTitle(t *testing.T) { func TestRenderIssueTitle(t *testing.T) {
expected := ` space @mention-user expected := ` space @mention-user<SPACE><SPACE>
/just/a/path.bin /just/a/path.bin
https://example.com/file.bin https://example.com/file.bin
[local link](file.bin) [local link](file.bin)
@ -156,9 +162,10 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com mail@domain.com
@mention-user test @mention-user test
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a> <a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space space<SPACE><SPACE>
` `
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas)) expected = strings.ReplaceAll(expected, "<SPACE>", " ")
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput(), testMetas))
} }
func TestRenderMarkdownToHtml(t *testing.T) { func TestRenderMarkdownToHtml(t *testing.T) {
@ -183,5 +190,20 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
#123 #123
space</p> space</p>
` `
assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput)) assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput()))
}
func TestRenderLabels(t *testing.T) {
ctx := context.Background()
locale := &translation.MockLocale{}
label := &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
issue := &issues.Issue{}
expected := `/owner/repo/issues?labels=123`
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
label = &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
issue = &issues.Issue{IsPull: true}
expected = `/owner/repo/pulls?labels=123`
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
} }

View File

@ -21,8 +21,9 @@ var (
) )
// MockSet sets the time to a mocked time.Time // MockSet sets the time to a mocked time.Time
func MockSet(now time.Time) { func MockSet(now time.Time) func() {
mockNow = now mockNow = now
return MockUnset
} }
// MockUnset will unset the mocked time.Time // MockUnset will unset the mocked time.Time

View File

@ -6,6 +6,7 @@ package translation
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"strings"
) )
// MockLocale provides a mocked locale without any translations // MockLocale provides a mocked locale without any translations
@ -19,18 +20,25 @@ func (l MockLocale) Language() string {
return "en" return "en"
} }
func (l MockLocale) TrString(s string, _ ...any) string { func (l MockLocale) TrString(s string, args ...any) string {
return s return sprintAny(s, args...)
} }
func (l MockLocale) Tr(s string, a ...any) template.HTML { func (l MockLocale) Tr(s string, args ...any) template.HTML {
return template.HTML(s) return template.HTML(sprintAny(s, args...))
} }
func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML { func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return template.HTML(key1) return template.HTML(sprintAny(key1, args...))
} }
func (l MockLocale) PrettyNumber(v any) string { func (l MockLocale) PrettyNumber(v any) string {
return fmt.Sprint(v) return fmt.Sprint(v)
} }
func sprintAny(s string, args ...any) string {
if len(args) == 0 {
return s
}
return s + ":" + fmt.Sprintf(strings.Repeat(",%v", len(args))[1:], args...)
}

View File

@ -4,22 +4,10 @@ package util
import ( import (
"fmt" "fmt"
"math"
"strconv" "strconv"
"strings" "strings"
) )
// Check similar implementation in web_src/js/utils/color.js and keep synchronization
// Return R, G, B values defined in reletive luminance
func getLuminanceRGB(channel float64) float64 {
sRGB := channel / 255
if sRGB <= 0.03928 {
return sRGB / 12.92
}
return math.Pow((sRGB+0.055)/1.055, 2.4)
}
// Get color as RGB values in 0..255 range from the hex color string (with or without #) // Get color as RGB values in 0..255 range from the hex color string (with or without #)
func HexToRBGColor(colorString string) (float64, float64, float64) { func HexToRBGColor(colorString string) (float64, float64, float64) {
hexString := colorString hexString := colorString
@ -47,19 +35,23 @@ func HexToRBGColor(colorString string) (float64, float64, float64) {
return r, g, b return r, g, b
} }
// return luminance given RGB channels // Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance
// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance // Keep this in sync with web_src/js/utils/color.js
func GetLuminance(r, g, b float64) float64 { func GetRelativeLuminance(color string) float64 {
R := getLuminanceRGB(r) r, g, b := HexToRBGColor(color)
G := getLuminanceRGB(g) return (0.2126729*r + 0.7151522*g + 0.0721750*b) / 255
B := getLuminanceRGB(b)
luminance := 0.2126*R + 0.7152*G + 0.0722*B
return luminance
} }
// Reference from: https://firsching.ch/github_labels.html func UseLightText(backgroundColor string) bool {
// In the future WCAG 3 APCA may be a better solution. return GetRelativeLuminance(backgroundColor) < 0.453
// Check if text should use light color based on RGB of background }
func UseLightTextOnBackground(r, g, b float64) bool {
return GetLuminance(r, g, b) < 0.453 // Given a background color, returns a black or white foreground color that the highest
// contrast ratio. In the future, the APCA contrast function, or CSS `contrast-color` will be better.
// https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42
func ContrastColor(backgroundColor string) string {
if UseLightText(backgroundColor) {
return "#fff"
}
return "#000"
} }

View File

@ -33,33 +33,31 @@ func Test_HexToRBGColor(t *testing.T) {
} }
} }
func Test_UseLightTextOnBackground(t *testing.T) { func Test_UseLightText(t *testing.T) {
cases := []struct { cases := []struct {
r float64 color string
g float64 expected string
b float64
expected bool
}{ }{
{215, 58, 74, true}, {"#d73a4a", "#fff"},
{0, 117, 202, true}, {"#0075ca", "#fff"},
{207, 211, 215, false}, {"#cfd3d7", "#000"},
{162, 238, 239, false}, {"#a2eeef", "#000"},
{112, 87, 255, true}, {"#7057ff", "#fff"},
{0, 134, 114, true}, {"#008672", "#fff"},
{228, 230, 105, false}, {"#e4e669", "#000"},
{216, 118, 227, true}, {"#d876e3", "#000"},
{255, 255, 255, false}, {"#ffffff", "#000"},
{43, 134, 133, true}, {"#2b8684", "#fff"},
{43, 135, 134, true}, {"#2b8786", "#fff"},
{44, 135, 134, true}, {"#2c8786", "#000"},
{59, 182, 179, true}, {"#3bb6b3", "#000"},
{124, 114, 104, true}, {"#7c7268", "#fff"},
{126, 113, 108, true}, {"#7e716c", "#fff"},
{129, 112, 109, true}, {"#81706d", "#fff"},
{128, 112, 112, true}, {"#807070", "#fff"},
{"#84b6eb", "#000"},
} }
for n, c := range cases { for n, c := range cases {
result := UseLightTextOnBackground(c.r, c.g, c.b) assert.Equal(t, c.expected, ContrastColor(c.color), "case %d: error should match", n)
assert.Equal(t, c.expected, result, "case %d: error should match", n)
} }
} }

View File

@ -213,6 +213,14 @@ func ToPointer[T any](val T) *T {
return &val return &val
} }
// Iif is an "inline-if", it returns "trueVal" if "condition" is true, otherwise "falseVal"
func Iif[T any](condition bool, trueVal, falseVal T) T {
if condition {
return trueVal
}
return falseVal
}
// IfZero returns "def" if "v" is a zero value, otherwise "v" // IfZero returns "def" if "v" is a zero value, otherwise "v"
func IfZero[T comparable](v, def T) T { func IfZero[T comparable](v, def T) T {
var zero T var zero T

View File

@ -128,6 +128,16 @@ func hasResponseBeenWritten(argsIn []reflect.Value) bool {
return false return false
} }
func wrapHandlerProvider[T http.Handler](hp func(next http.Handler) T, funcInfo *routing.FuncInfo) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
routing.UpdateFuncInfo(req.Context(), funcInfo)
h.ServeHTTP(resp, req)
})
}
}
// toHandlerProvider converts a handler to a handler provider // toHandlerProvider converts a handler to a handler provider
// A handler provider is a function that takes a "next" http.Handler, it can be used as a middleware // A handler provider is a function that takes a "next" http.Handler, it can be used as a middleware
func toHandlerProvider(handler any) func(next http.Handler) http.Handler { func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
@ -138,13 +148,9 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
} }
if hp, ok := handler.(func(next http.Handler) http.Handler); ok { if hp, ok := handler.(func(next http.Handler) http.Handler); ok {
return func(next http.Handler) http.Handler { return wrapHandlerProvider(hp, funcInfo)
h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info } else if hp, ok := handler.(func(http.Handler) http.HandlerFunc); ok {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return wrapHandlerProvider(hp, funcInfo)
routing.UpdateFuncInfo(req.Context(), funcInfo)
h.ServeHTTP(resp, req)
})
}
} }
provider := func(next http.Handler) http.Handler { provider := func(next http.Handler) http.Handler {

View File

@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -45,10 +46,40 @@ func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
SameSite: setting.SessionConfig.SameSite, SameSite: setting.SessionConfig.SameSite,
} }
resp.Header().Add("Set-Cookie", cookie.String()) resp.Header().Add("Set-Cookie", cookie.String())
if maxAge < 0 { // Previous versions would use a cookie path with a trailing /.
// There was a bug in "setting.SessionConfig.CookiePath" code, the old default value of it was empty "". // These are more specific than cookies without a trailing /, so
// So we have to delete the cookie on path="" again, because some old code leaves cookies on path="". // we need to delete these if they exist.
cookie.Path = strings.TrimSuffix(setting.SessionConfig.CookiePath, "/") deleteLegacySiteCookie(resp, name)
resp.Header().Add("Set-Cookie", cookie.String()) }
}
// deleteLegacySiteCookie deletes the cookie with the given name at the cookie
// path with a trailing /, which would unintentionally override the cookie.
func deleteLegacySiteCookie(resp http.ResponseWriter, name string) {
if setting.SessionConfig.CookiePath == "" || strings.HasSuffix(setting.SessionConfig.CookiePath, "/") {
// If the cookie path ends with /, no legacy cookies will take
// precedence, so do nothing. The exception is that cookies with no
// path could override other cookies, but it's complicated and we don't
// currently handle that.
return
}
cookie := &http.Cookie{
Name: name,
Value: "",
MaxAge: -1,
Path: setting.SessionConfig.CookiePath + "/",
Domain: setting.SessionConfig.Domain,
Secure: setting.SessionConfig.Secure,
HttpOnly: true,
SameSite: setting.SessionConfig.SameSite,
}
resp.Header().Add("Set-Cookie", cookie.String())
}
func init() {
session.BeforeRegenerateSession = append(session.BeforeRegenerateSession, func(resp http.ResponseWriter, _ *http.Request) {
// Ensure that a cookie with a trailing slash does not take precedence over
// the cookie written by the middleware.
deleteLegacySiteCookie(resp, setting.SessionConfig.CookieName)
})
} }

View File

@ -1233,6 +1233,8 @@ file_view_rendered = View Rendered
file_view_raw = View Raw file_view_raw = View Raw
file_permalink = Permalink file_permalink = Permalink
file_too_large = The file is too large to be shown. file_too_large = The file is too large to be shown.
code_preview_line_from_to = Lines %[1]d to %[2]d in %[3]s
code_preview_line_in = Line %[1]d in %[2]s
invisible_runes_header = `This file contains invisible Unicode characters` invisible_runes_header = `This file contains invisible Unicode characters`
invisible_runes_description = `This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.` invisible_runes_description = `This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.`
ambiguous_runes_header = `This file contains ambiguous Unicode characters` ambiguous_runes_header = `This file contains ambiguous Unicode characters`
@ -2775,6 +2777,7 @@ teams.invite.by = Invited by %s
teams.invite.description = Please click the button below to join the team. teams.invite.description = Please click the button below to join the team.
[admin] [admin]
maintenance = Maintenance
dashboard = Dashboard dashboard = Dashboard
self_check = Self Check self_check = Self Check
identity_access = Identity & Access identity_access = Identity & Access
@ -2798,7 +2801,7 @@ settings = Admin Settings
dashboard.new_version_hint = Gitea %s is now available, you are running %s. Check <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">the blog</a> for more details. dashboard.new_version_hint = Gitea %s is now available, you are running %s. Check <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">the blog</a> for more details.
dashboard.statistic = Summary dashboard.statistic = Summary
dashboard.operations = Maintenance Operations dashboard.maintenance_operations = Maintenance Operations
dashboard.system_status = System Status dashboard.system_status = System Status
dashboard.operation_name = Operation Name dashboard.operation_name = Operation Name
dashboard.operation_switch = Switch dashboard.operation_switch = Switch
@ -3305,6 +3308,7 @@ notices.op = Op.
notices.delete_success = The system notices have been deleted. notices.delete_success = The system notices have been deleted.
self_check.no_problem_found = No problem found yet. self_check.no_problem_found = No problem found yet.
self_check.startup_warnings = Startup warnings:
self_check.database_collation_mismatch = Expect database to use collation: %s self_check.database_collation_mismatch = Expect database to use collation: %s
self_check.database_collation_case_insensitive = Database is using a collation %s, which is an insensitive collation. Although Gitea could work with it, there might be some rare cases which don't work as expected. self_check.database_collation_case_insensitive = Database is using a collation %s, which is an insensitive collation. Although Gitea could work with it, there might be some rare cases which don't work as expected.
self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems. self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems.

View File

@ -9,6 +9,8 @@ import (
"net/http" "net/http"
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -52,6 +54,18 @@ func (s *Service) Register(
return nil, errors.New("runner registration token has been invalidated, please use the latest one") return nil, errors.New("runner registration token has been invalidated, please use the latest one")
} }
if runnerToken.OwnerID > 0 {
if _, err := user_model.GetUserByID(ctx, runnerToken.OwnerID); err != nil {
return nil, errors.New("owner of the token not found")
}
}
if runnerToken.RepoID > 0 {
if _, err := repo_model.GetRepositoryByID(ctx, runnerToken.RepoID); err != nil {
return nil, errors.New("repository of the token not found")
}
}
labels := req.Msg.Labels labels := req.Msg.Labels
// TODO: agent_labels should be removed from pb after Gitea 1.20 released. // TODO: agent_labels should be removed from pb after Gitea 1.20 released.
// Old version runner's agent_labels slice is not empty and labels slice is empty. // Old version runner's agent_labels slice is not empty and labels slice is empty.

View File

@ -30,7 +30,7 @@ import (
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
) )
func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64, loginName string) { func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64) {
if sourceID == 0 { if sourceID == 0 {
return return
} }
@ -47,7 +47,6 @@ func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64
u.LoginType = source.Type u.LoginType = source.Type
u.LoginSource = source.ID u.LoginSource = source.ID
u.LoginName = loginName
} }
// CreateUser create a user // CreateUser create a user
@ -83,12 +82,13 @@ func CreateUser(ctx *context.APIContext) {
Passwd: form.Password, Passwd: form.Password,
MustChangePassword: true, MustChangePassword: true,
LoginType: auth.Plain, LoginType: auth.Plain,
LoginName: form.LoginName,
} }
if form.MustChangePassword != nil { if form.MustChangePassword != nil {
u.MustChangePassword = *form.MustChangePassword u.MustChangePassword = *form.MustChangePassword
} }
parseAuthSource(ctx, u, form.SourceID, form.LoginName) parseAuthSource(ctx, u, form.SourceID)
if ctx.Written() { if ctx.Written() {
return return
} }

View File

@ -10,7 +10,6 @@ import (
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
@ -201,7 +200,6 @@ func ReadRepoNotifications(ctx *context.APIContext) {
if !ctx.FormBool("all") { if !ctx.FormBool("all") {
statuses := ctx.FormStrings("status-types") statuses := ctx.FormStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"}) opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
log.Error("%v", opts.Status)
} }
nl, err := db.Find[activities_model.Notification](ctx, opts) nl, err := db.Find[activities_model.Notification](ctx, opts)
if err != nil { if err != nil {

View File

@ -437,7 +437,7 @@ func GetBranchProtection(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp)) ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
} }
// ListBranchProtections list branch protections for a repo // ListBranchProtections list branch protections for a repo
@ -470,7 +470,7 @@ func ListBranchProtections(ctx *context.APIContext) {
} }
apiBps := make([]*api.BranchProtection, len(bps)) apiBps := make([]*api.BranchProtection, len(bps))
for i := range bps { for i := range bps {
apiBps[i] = convert.ToBranchProtection(ctx, bps[i]) apiBps[i] = convert.ToBranchProtection(ctx, bps[i], repo)
} }
ctx.JSON(http.StatusOK, apiBps) ctx.JSON(http.StatusOK, apiBps)
@ -681,7 +681,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp)) ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp, repo))
} }
// EditBranchProtection edits a branch protection for a repo // EditBranchProtection edits a branch protection for a repo
@ -959,7 +959,7 @@ func EditBranchProtection(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp)) ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
} }
// DeleteBranchProtection deletes a branch protection for a repo // DeleteBranchProtection deletes a branch protection for a repo

View File

@ -311,7 +311,7 @@ func SearchIssues(ctx *context.APIContext) {
ctx.SetLinkHeader(int(total), limit) ctx.SetLinkHeader(int(total), limit)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
} }
// ListIssues list the issues of a repository // ListIssues list the issues of a repository
@ -548,7 +548,7 @@ func ListIssues(ctx *context.APIContext) {
ctx.SetLinkHeader(int(total), listOptions.PageSize) ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
} }
func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 { func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 {
@ -614,7 +614,7 @@ func GetIssue(ctx *context.APIContext) {
ctx.NotFound() ctx.NotFound()
return return
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue)) ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, ctx.Doer, issue))
} }
// CreateIssue create an issue of a repository // CreateIssue create an issue of a repository
@ -737,7 +737,7 @@ func CreateIssue(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetIssueByID", err) ctx.Error(http.StatusInternalServerError, "GetIssueByID", err)
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, issue)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, issue))
} }
// EditIssue modify an issue of a repository // EditIssue modify an issue of a repository
@ -911,7 +911,7 @@ func EditIssue(ctx *context.APIContext) {
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, issue)) ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, issue))
} }
func DeleteIssue(ctx *context.APIContext) { func DeleteIssue(ctx *context.APIContext) {

View File

@ -107,7 +107,7 @@ func ListIssueAttachments(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue).Attachments) ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, ctx.Doer, issue).Attachments)
} }
// CreateIssueAttachment creates an attachment and saves the given file // CreateIssueAttachment creates an attachment and saves the given file

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