Compare commits

..

61 Commits

Author SHA1 Message Date
Giteabot
bcb0f3a90f Update 1.20.3 changelog (#26609) (#26610)
Backport #26609 by @delvh

Co-authored-by: delvh <dev.lh@web.de>
2023-08-20 21:25:55 +02:00
Giteabot
c99374b151 Use "input" event instead of "keyup" event for migration form (#26602) (#26605)
Backport #26602 by @wxiaoguang

Otherwise, "pasted" content won't update the UI.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-20 23:29:16 +08:00
Giteabot
b643b2ca9c Do not use deprecated log config options by default (#26592) (#26600)
Backport #26592 by @wxiaoguang

Simplify the log config

* Remove unnecessary `ROUTER` config, it defaults to the `MODE`.
* `XORM` config was deprecated

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-20 16:11:57 +08:00
CaiCandong
94f86964b4 Fix project filter bugs (#26490) (#26558)
Backport  #26490

related: #26012

1. missing project filter on the issue page.


1e76a824bc/modules/indexer/issues/dboptions.go (L11-L15)
2. incorrect SQL condition: some issue does not belong to a project but
exists on the project_issue table.


f5dbac9d36/models/issues/issue_search.go (L233)


![before](https://github.com/go-gitea/gitea/assets/50507092/1dcde39e-3e2f-4151-b2c6-4d67bf493c2f)


![after](https://github.com/go-gitea/gitea/assets/50507092/badfb81f-056d-4a2f-9838-1cba9c15768d)
2023-08-19 12:21:45 +02:00
Giteabot
1f29cfa683 Add minimum polyfill to support "relative-time-element" in PaleMoon (#26575) (#26578)
Backport #26575 by @wxiaoguang

Close #26525

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-18 17:54:52 +08:00
Giteabot
1cedf36d30 Fix "issueReposQueryPattern does not match query" (#26556) (#26564)
Backport #26556 by @wolfogre

Fix
`https://github.com/go-gitea/gitea/pull/26545#discussion_r1295734340`

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-17 21:13:00 +08:00
Giteabot
7da85fa0c3 Sync repo's IsEmpty status correctly (#26517) (#26560)
Backport #26517 by @wxiaoguang

Close #26509

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-17 05:12:14 +00:00
Giteabot
0ac8b774e9 Fix typo of RunerOwnerID (#26508) (#26528)
Backport #26508 by @yp05327

Co-authored-by: yp05327 <576951401@qq.com>
2023-08-16 06:28:39 +00:00
KN4CK3R
762d4245fb Fix NuGet search endpoints (#25613) (#26499)
Backport of #25613

Fixes #25564
Fixes #23191

- Api v2 search endpoint should return only the latest version matching
the query
- Api v3 search endpoint should return `take` packages not package
versions
2023-08-16 06:01:20 +00:00
Giteabot
3571cbba34 Fix dark theme highlight for "NameNamespace" (#26519) (#26527)
Backport #26519 by @wxiaoguang

The color is taken from "Name"

Before:


![image](https://github.com/go-gitea/gitea/assets/2114189/b94d7521-770c-4e14-a63b-f30c44fe883f)


After:


![image](https://github.com/go-gitea/gitea/assets/2114189/d99c1f13-a0c0-4dc8-82ab-bfdd451e46ec)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-16 07:31:29 +08:00
Giteabot
6d60d4e554 Use hidden over clip for text truncation (#26520) (#26522)
Backport #26520 by @silverwind

Avoid browser bugs:

- Firefox not cutting off -
https://github.com/go-gitea/gitea/pull/26354#issuecomment-1678456052
- Safari not showing ellipsis -
https://github.com/go-gitea/gitea/pull/26354#issuecomment-1678812801

Co-authored-by: silverwind <me@silverwind.io>
2023-08-15 13:34:08 +00:00
Giteabot
31208fe9a1 Set "type=button" for editor's toolbar buttons (#26510) (#26518)
Backport #26510 by @wxiaoguang

The editor usually is in a form, so the buttons should have
"type=button", avoid conflicting with the form's submit.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-15 14:10:50 +02:00
Giteabot
4bdb8dd9cc Detect ogg mime-type as audio or video (#26494) (#26505)
Backport #26494 by @wxiaoguang

"ogg" is just a "container" format for audio and video.

Golang's `DetectContentType` only reports "application/ogg" for
potential ogg files.

Actually it could do more "guess" to see whether it is a audio file or a
video file.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-15 11:15:26 +08:00
Giteabot
24d6aacc7e Use object-fit: contain for oauth2 custom icons (#26493) (#26498)
Backport #26493 by @wxiaoguang

It works for various sizes.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-14 16:56:15 +00:00
Giteabot
d1a55aabc9 Move dropzone progress bar to bottom to show filename when uploading (#26492) (#26497)
Backport #26492 by @wxiaoguang

1. Make the "filename" visible
2. Avoiding UI flicker when the uploading is completing

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-14 23:55:18 +08:00
Giteabot
f1c5d33d3e Fix storage path logic especially for relative paths (#26441) (#26481)
Backport #26441 by @lunny

This PR rewrites the function `getStorage` and make it more clear.

Include tests from #26435, thanks @earl-warren

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Earl Warren <contact@earl-warren.org>
2023-08-13 22:38:18 -04:00
Giteabot
acc0fd22d8 Add ThreadID parameter for Telegram webhooks (#25996) (#26480)
Backport #25996

Telegram has recently implemented threads (channels) for group chats.

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: neveraskedtoexist <matikot415@gmail.com>
2023-08-14 08:55:17 +08:00
Giteabot
fe1b11b639 Close stdout correctly for "git blame" (#26470) (#26473)
Backport #26470 by @wxiaoguang

Close stdout correctly for "git blame", otherwise the failed "git blame"
would cause the request hanging forever.

And "os.Stderr" should never (seldom) be used as git command's stderr
(there seems some similar problems in code, they could be fixed later).

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-13 16:00:28 +08:00
Giteabot
80d7288ea4 Remove last newline from config file (#26468) (#26471)
Backport #26468 by @wxiaoguang

When users put the secrets into a file (GITEA__sec__KEY__FILE), the
newline sometimes is different to avoid (eg: echo/vim/...)

So the last newline could be removed when reading, it makes the users
easier to maintain the secret files.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-13 00:19:33 +08:00
Giteabot
2d1202b32c Check first if minio bucket exists before trying to create it (#26420) (#26465)
Backport #26420 by @lunny

For some reason, the permission of the client_id and secret may cannot
create bucket, so now we will check whether bucket does exist first and
then try to create a bucket if it doesn't exist.

Try to fix #25984

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-08-12 09:00:51 +00:00
wxiaoguang
9112ce22a4 Avoiding accessing undefined tributeValues #26461 (#26462)
Backport #26461
2023-08-12 10:30:29 +02:00
Giteabot
2bdc38e592 Fix incorrect color of selected assignees when create issue (#26324) (#26372)
Backport #26324 by @yp05327

Before:

![image](https://github.com/go-gitea/gitea/assets/18380374/75d610b2-3823-4366-be85-c77c9106feff)

After:

![image](https://github.com/go-gitea/gitea/assets/18380374/15afc6ac-f5ad-4e24-8983-fea8ace5921f)

Co-authored-by: yp05327 <576951401@qq.com>
2023-08-11 12:38:39 +02:00
crystal
f155cf60d3 Improve profile readme rendering (#25988) (#26453)
manual backport of #25988 to v1.20

- Tell the renderer to use the `document` mode, so it's consistent with
other renderers.
- Use the same padding as `.file-view.markup`, so it's consistent with
other containers that contain markup rendering.
- Resolves https://codeberg.org/forgejo/forgejo/issues/833

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
2023-08-11 10:58:50 +02:00
Giteabot
0f5e07f538 Update upgrade documentation to add a check for deprecated configurations (#26451) (#26452)
Backport #26451 by @lunny

fix
https://github.com/go-gitea/gitea/issues/25995#issuecomment-1674096710

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-08-11 08:17:27 +02:00
Lunny Xiao
368e9e0f1b Add transaction when creating pull request created dirty data (#26259) (#26437)
Backport #26259 

This PR will introduce a transaction on creating pull request so that if
some step failed, it will rollback totally. And there will be no dirty
pull request exist.

Co-authored-by: Giteabot <teabot@gitea.io>
2023-08-11 05:27:23 +00:00
Giteabot
d6cf261be8 Call git.InitSimple for runRepoSyncReleases (#26396) (#26450)
Backport #26396 by @wxiaoguang

Fix #26394

Otherwise, the git module is not initialized and it doesn't respect the
"timeout" config in app.ini

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-10 23:16:12 +00:00
Giteabot
7c555b2231 Add changelog for 1.20.3 (#26373) (#26375)
Backport #26373 by @delvh

Co-authored-by: delvh <dev.lh@web.de>
2023-08-10 23:40:17 +08:00
Giteabot
981ab48503 minio: add missing region on client initialization (#26412) (#26438)
Backport #26412 by @nekrondev

The MinIO client isn't redirecting to the correct AWS endpoint if a
non-default data center is used.

In my use case I created an AWS bucket at `eu-central-1` region. Because
of the missing region initialization of the client the default
`us-east-1` API endpoint is used returning a `301 Moved Permanently`
response that's not handled properly by MinIO client. This in return
aborts using S3 storage on AWS as the `BucketExists()` call will fail
with the http moved error.

MinIO client trace shows the issue:

```text
---------START-HTTP---------
HEAD / HTTP/1.1
Host: xxxxxxxxxxx-prod-gitea-data.s3.dualstack.us-east-1.amazonaws.com
User-Agent: MinIO (windows; amd64) minio-go/v7.0.61
Authorization: AWS4-HMAC-SHA256 Credential=**REDACTED**/20230809/accesspoint.eu-central-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=**REDACTED**
X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Amz-Date: 20230809T141143Z

HTTP/1.1 301 Moved Permanently
Connection: close
Content-Type: application/xml
Date: Wed, 09 Aug 2023 14:11:43 GMT
Server: AmazonS3
X-Amz-Bucket-Region: eu-central-1
X-Amz-Id-2: UK7wfeYi0HcTcytNvQ3wTAZ5ZP1mOSMnvRZ9Fz4xXzeNsS47NB/KfFx2unFxo3L7XckHpMNPPVo=
X-Amz-Request-Id: S1V2MJV8SZ11GEVN
---------END-HTTP---------
```

Co-authored-by: nekrondev <heiko@noordsee.de>
Co-authored-by: Heiko Besemann <heiko.besemann@qbeyond.de>
2023-08-10 14:11:22 +00:00
Giteabot
ec37ea5945 Fix wrong middleware sequence (#26428) (#26436)
Backport #26428 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-08-10 11:26:22 +00:00
wxiaoguang
8ad331c9d2 Fix admin queue page title and fix CI failures (#26409) (#26421)
Backport #26409

* Fix #26408
* Bypass the data race issue in "ssh" package
2023-08-10 11:04:48 +02:00
Giteabot
dbabdf6d71 Add pull request review request webhook event (#26401) (#26407)
Backport #26401 by @yardenshoham

Add webhook events for pull request review requests

- Fixes #26371
- Added support for the "Pull request review requested" and "Pull
request review request removed" webhook events.
- Updated the `getPullRequestPayloadInfo` function in `general.go` to
handle these new webhook events.

# Before

![image](https://github.com/go-gitea/gitea/assets/20454870/bd942971-fb1d-40f3-8961-46638e3588fa)

# After

![image](https://github.com/go-gitea/gitea/assets/20454870/216e9c7d-0a4d-49f9-8492-2d14c88bbf4e)

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
Co-authored-by: Yarden Shoham <git@yardenshoham.com>
2023-08-09 09:00:34 +00:00
Giteabot
2d1a7e1cd4 Introduce ctx.PathParamRaw to avoid incorrect unescaping (#26392) (#26405)
Backport #26392 by @wxiaoguang

Fix #26389

And complete an old TODO: `ctx.Params does un-escaping,..., which is
incorrect.`

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-09 07:31:44 +00:00
Giteabot
df5558135b Fix incorrect sort link with .profile repository (#26374) (#26379)
Backport #26374 by @CaiCandong

fix #26360
Before:

![before](https://github.com/go-gitea/gitea/assets/50507092/5606afe1-9aa2-455e-8d6f-123ff1ac7011)

After:

![After](https://github.com/go-gitea/gitea/assets/50507092/14ff544a-e614-4d41-8615-5244b4ba56eb)

Co-authored-by: CaiCandong <50507092+CaiCandong@users.noreply.github.com>
2023-08-09 08:57:47 +08:00
Giteabot
f329982b6e Fix text truncate (#26354) (#26384)
Backport #26354 by @Maks1mS

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

Before:

![image](https://github.com/go-gitea/gitea/assets/36362599/c8c27bcb-469f-4def-8521-d9e054c16ecb)


After:


![image](https://github.com/go-gitea/gitea/assets/36362599/2405b6e8-fc5c-4b13-b66b-007bc11edbc4)

Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2023-08-08 10:14:25 +08:00
Giteabot
cb1a4da5c2 Bypass MariaDB performance bug of the "IN" sub-query, fix incorrect IssueIndex (#26279) (#26368)
Backport #26279 by @wxiaoguang

Close #26277
Fix #26285

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-07 10:55:25 +00:00
delvh
39cbca0f95 Display human-readable text instead of cryptic filemodes (#26352) (#26358)
Backport #26352

Now, you don't need to be a git expert anymore to know what these numbers mean.

## Before

![grafik](https://github.com/go-gitea/gitea/assets/51889757/9a964bf6-10fd-40a6-aeb2-ac8f437f8c32)

## After

![grafik](https://github.com/go-gitea/gitea/assets/51889757/84573cb9-55b6-4dde-9866-95f71b657554)

or when the mode actually changed:

![grafik](https://github.com/go-gitea/gitea/assets/51889757/0f327538-ebdc-40e7-8c99-f9e21b67f638)
2023-08-06 23:15:01 +02:00
Giteabot
9aadc25bc1 [docs] Add missing backtick in quickstart.zh-cn.md (#26349) (#26357) 2023-08-06 15:57:21 -04:00
Brian Lachniet
b94370504f [docs] Fix Gmail configuration (#26356) 2023-08-06 12:02:43 -04:00
wxiaoguang
4fd8ac0653 Fix API leaking Usermail if not logged in (#25097) (#26350)
Backport #25097

The API should only return the real Mail of a User, if the caller is
logged in. The check do to this don't work. This PR fixes this. This not
really a security issue, but can lead to Spam.

Co-authored-by: JakobDev <jakobdev@gmx.de>
Co-authored-by: silverwind <me@silverwind.io>
2023-08-06 20:11:39 +08:00
Giteabot
59354d7135 Hide last indexed SHA when a repo could not be indexed yet (#26340) (#26345)
Backport #26340 by @CaiCandong

Now, for a new repo without any commit, the Last indexed SHA field looks
like this:
Before:

![image](https://github.com/go-gitea/gitea/assets/50507092/cecc6e24-3366-4093-ae07-c361ea34b373)
After: 

![image](https://github.com/go-gitea/gitea/assets/50507092/9b6ba703-b0d5-4648-ad6b-9a2341dd60f9)


fix #26336

Co-authored-by: CaiCandong <50507092+CaiCandong@users.noreply.github.com>
2023-08-05 16:08:17 +00:00
Giteabot
0b97463cef Remove backslashed newlines on markdown (#26344) (#26348)
Backport #26344 by @lunny

Fix https://gitea.com/gitea/gitea-docusaurus/issues/56

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-08-05 23:42:45 +08:00
wxiaoguang
fa431b377d Fix incorrect CLI exit code and duplicate error message (#26346) (#26347)
Backport #26346

Follow the CLI refactoring, and add tests.
2023-08-05 23:37:04 +08:00
Giteabot
8a97cdd91b Fix log typo in task.go (#26337) (#26343)
Backport #26337 by @cassiozareck

Signed-off-by: cassiozareck <cassiomilczareck@gmail.com>
2023-08-05 10:15:49 +00:00
Giteabot
3e9475b3b2 Prevent newline errors with Debian packages (#26332) (#26342)
Backport #26332 by @KN4CK3R

Fixes #26313

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-08-05 11:41:30 +02:00
Giteabot
9be9042479 Fix bug with sqlite load read (#26305) (#26339)
Backport #26305 by @lunny

Possible fix #26280

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-08-05 10:35:08 +03:00
Giteabot
9451781ebe Make git batch operations use parent context timeout instead of default timeout (#26325) (#26330)
Backport #26325 by @wxiaoguang

Fix #26064

Some git commands should use parent context, otherwise it would exit too
early (by the default timeout, 10m), and the "cmd.Wait" waits till the
pipes are closed.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-04 15:44:25 +02:00
Giteabot
88f6f7579c Fix the wrong derive path (#26271) (#26318)
Backport #26271 by @lunny

This PR will fix #26264, caused by #23911.

The package configuration derive is totally wrong when storage type is
local in that PR.

This PR fixed the inherit logic when storage type is local with some
unit tests.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-04 06:24:13 +00:00
Giteabot
fcd055c34a Fix the topic validation rule and suport dots (#26286) (#26303)
Backport #26286 by @wxiaoguang

1. Allow leading and trailing spaces by user input, these spaces have
already been trimmed at backend
2. Allow using dots in the topic

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-04 13:00:49 +08:00
Giteabot
a57568bad7 Support getting changed files when commit ID is EmptySHA (#26290) (#26316)
Backport #26290 by @Zettat123

Fixes #26270.

Co-Author: @wxiaoguang 

Thanks @lunny for providing this solution

As
https://github.com/go-gitea/gitea/issues/26270#issuecomment-1661695151
said, at present we cannot get the names of changed files correctly when
the `OldCommitID` is `EmptySHA`. In this PR, the `GetCommitFilesChanged`
method is added and will be used to get the changed files by commit ID.

References:
- https://stackoverflow.com/a/424142

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-04 03:27:45 +00:00
Lunny Xiao
8e07d53fe4 Upgrade x/net to 0.13.0 (#26301)
backport #26297
2023-08-03 18:05:07 +08:00
Giteabot
a758337046 Clarify the logger's MODE config option (#26267) (#26281)
Backport #26267 by @wxiaoguang

1. Fix the wrong document (add the missing `MODE=`)
2. Add a more friendly log message to tell users to add `MODE=` in their
config

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-08-02 01:08:28 +02:00
Giteabot
2cf1515f5c Fix due date rendering the wrong date in issue (#26268) (#26274)
Backport #26268 by @yardenshoham

Closes #26263

We have to pass the date without the time.

# Before

![image](https://github.com/go-gitea/gitea/assets/20454870/6b6cb43d-2b1c-4679-951d-20f48c94bfdd)

# After

![image](https://github.com/go-gitea/gitea/assets/20454870/50441baf-2c52-452b-bb0d-6034a407abde)

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
Co-authored-by: Yarden Shoham <git@yardenshoham.com>
2023-08-01 16:40:35 +00:00
Giteabot
a075285f24 Update Arch linux URL from community to extra (#26273) (#26276)
Co-authored-by: minijaws <minijaws@gmail.com>
2023-08-01 10:32:23 -05:00
Giteabot
2517da90aa Use shared template for webhook icons (#26242) (#26246)
Backport #26242 by @silverwind

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

Co-authored-by: silverwind <me@silverwind.io>
2023-07-31 10:09:13 +00:00
Giteabot
060026995a Fix pull request check list is limited (#26179) (#26245)
Backport #26179 by @CaiCandong

In the original implementation, we can only get the first 30 records of
the commit status (the default paging size), if the commit status is
more than 30, it will lead to the bug #25990. I made the following two
changes.
- On the page, use the ` db.ListOptions{ListAll: true}` parameter
instead of `db.ListOptions{}`
- The `GetLatestCommitStatus` function makes a determination as to
whether or not a pager is being used.

fixed #25990

Co-authored-by: caicandong <50507092+CaiCandong@users.noreply.github.com>
2023-07-31 08:56:46 +00:00
Giteabot
0f265a2489 Don't autosize textarea in diff view (#26233) (#26244)
Backport #26233 by @silverwind

Resizing the comment editor can be a very expensive operation because it
triggers page reflows, which on large PRs can take upwards of seconds to
complete. Disable this mechanism on the diff page only where we know
that the page can get large.

Fixes https://github.com/go-gitea/gitea/issues/26201 for the textarea
editor.

I don't think this can be fixed for EasyMDE because as far as I can
tell, it exposes no option to disable this resizing.

Co-authored-by: silverwind <me@silverwind.io>
2023-07-31 10:02:25 +02:00
silverwind
0d04f70d6a Fix attachment clipboard copy on insecure origin (#26224) (#26231)
Backport https://github.com/go-gitea/gitea/pull/26224.
2023-07-31 00:12:01 +02:00
Giteabot
2122743093 Fixed incorrect locale references (#26218) (#26222)
Backport #26218 by @kerwin612

Fixed two incorrect headers for setting the page navigation bar:
* User settings page, should not use the title "`org.settings`"
* Repo settings page, should not use the title "`org.settings`"

Co-authored-by: Kerwin Bryant <kerwin612@qq.com>
2023-07-29 21:54:35 +00:00
Giteabot
3a29712e0a Fix access check for org-level project (#26182) (#26223)
Backport #26182 by @Zettat123

Fix #25934

Add `ignoreGlobal` parameter to `reqUnitAccess` and only check global
disabled units when `ignoreGlobal` is true. So the org-level projects
and user-level projects won't be affected by global disabled
`repo.projects` unit.

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-07-29 18:50:58 +00:00
Giteabot
81d3dc1da5 Fix commit compare style (#26209) (#26226)
Backport #26209 by @puni9869

as title

Fixes : #25825
Before
<img width="1334" alt="image"
src="https://github.com/go-gitea/gitea/assets/80308335/c54a41b0-39bd-4094-a956-081a8f4128f2">

After change
<img width="1340" alt="image"
src="https://github.com/go-gitea/gitea/assets/80308335/c112d235-6bbe-4bcb-9529-78da3ab0fa14">

Co-authored-by: puni9869 <80308335+puni9869@users.noreply.github.com>
2023-07-29 16:54:51 +00:00
Giteabot
c5fe09db72 Warn instead of reporting an error when a webhook cannot be found (#26039) (#26211)
Backport #26039 by @puni9869

Attemp fix: #25744
Fixing the log level when we delete any repo then we get error hook not
found by id. That should be warn level to reduce the noise in the logs.

Co-authored-by: puni9869 <80308335+puni9869@users.noreply.github.com>
2023-07-29 14:47:46 +00:00
126 changed files with 1495 additions and 471 deletions
+63 -3
View File
@@ -2,9 +2,69 @@
This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.20.2](https://github.com/go-gitea/gitea/releases/tag/1.20.2) - 2023-07-29
## [1.20.3](https://github.com/go-gitea/gitea/releases/tag/v1.20.3) - 2023-08-20
* BREAKING
* Fix the wrong derive path (#26271) (#26318)
* SECURITY
* Fix API leaking Usermail if not logged in (#25097) (#26350)
* FEATURES
* Add ThreadID parameter for Telegram webhooks (#25996) (#26480)
* ENHANCEMENTS
* Add minimum polyfill to support "relative-time-element" in PaleMoon (#26575) (#26578)
* Fix dark theme highlight for "NameNamespace" (#26519) (#26527)
* Detect ogg mime-type as audio or video (#26494) (#26505)
* Use `object-fit: contain` for oauth2 custom icons (#26493) (#26498)
* Move dropzone progress bar to bottom to show filename when uploading (#26492) (#26497)
* Remove last newline from config file (#26468) (#26471)
* Minio: add missing region on client initialization (#26412) (#26438)
* Add pull request review request webhook event (#26401) (#26407)
* Fix text truncate (#26354) (#26384)
* Fix incorrect color of selected assignees when create issue (#26324) (#26372)
* Display human-readable text instead of cryptic filemodes (#26352) (#26358)
* Hide `last indexed SHA` when a repo could not be indexed yet (#26340) (#26345)
* Fix the topic validation rule and suport dots (#26286) (#26303)
* Fix due date rendering the wrong date in issue (#26268) (#26274)
* Don't autosize textarea in diff view (#26233) (#26244)
* Fix commit compare style (#26209) (#26226)
* Warn instead of reporting an error when a webhook cannot be found (#26039) (#26211)
* BUGFIXES
* Use "input" event instead of "keyup" event for migration form (#26602) (#26605)
* Do not use deprecated log config options by default (#26592) (#26600)
* Fix "issueReposQueryPattern does not match query" (#26556) (#26564)
* Sync repo's IsEmpty status correctly (#26517) (#26560)
* Fix project filter bugs (#26490) (#26558)
* Use `hidden` over `clip` for text truncation (#26520) (#26522)
* Set "type=button" for editor's toolbar buttons (#26510) (#26518)
* Fix NuGet search endpoints (#25613) (#26499)
* Fix storage path logic especially for relative paths (#26441) (#26481)
* Close stdout correctly for "git blame" (#26470) (#26473)
* Check first if minio bucket exists before trying to create it (#26420) (#26465)
* Avoiding accessing undefined tributeValues #26461 (#26462)
* Call git.InitSimple for runRepoSyncReleases (#26396) (#26450)
* Add transaction when creating pull request created dirty data (#26259) (#26437)
* Fix wrong middleware sequence (#26428) (#26436)
* Fix admin queue page title and fix CI failures (#26409) (#26421)
* Introduce ctx.PathParamRaw to avoid incorrect unescaping (#26392) (#26405)
* Bypass MariaDB performance bug of the "IN" sub-query, fix incorrect IssueIndex (#26279) (#26368)
* Fix incorrect CLI exit code and duplicate error message (#26346) (#26347)
* Prevent newline errors with Debian packages (#26332) (#26342)
* Fix bug with sqlite load read (#26305) (#26339)
* Make git batch operations use parent context timeout instead of default timeout (#26325) (#26330)
* Support getting changed files when commit ID is `EmptySHA` (#26290) (#26316)
* Clarify the logger's MODE config option (#26267) (#26281)
* Use shared template for webhook icons (#26242) (#26246)
* Fix pull request check list is limited (#26179) (#26245)
* Fix attachment clipboard copy on insecure origin (#26224) (#26231)
* Fix access check for org-level project (#26182) (#26223)
* MISC
* Improve profile readme rendering (#25988) (#26453)
* [docs] Add missing backtick in quickstart.zh-cn.md (#26349) (#26357)
* Upgrade x/net to 0.13.0 (#26301)
## [1.20.2](https://github.com/go-gitea/gitea/releases/tag/v1.20.2) - 2023-07-29
* ENHANCEMENTS
* Calculate MAX_WORKERS default value by CPU number (#26177) (#26183)
@@ -32,7 +92,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Fix wrong workflow status when rerun a job in an already finished workflow (#26119) (#26124)
* Fix duplicated url prefix on issue context menu (#26066) (#26067)
## [1.20.1](https://github.com/go-gitea/gitea/releases/tag/1.20.1) - 2023-07-22
## [1.20.1](https://github.com/go-gitea/gitea/releases/tag/v1.20.1) - 2023-07-22
* SECURITY
* Disallow dangerous URL schemes (#25960) (#25964)
+4
View File
@@ -348,6 +348,10 @@ func runRepoSyncReleases(_ *cli.Context) error {
return err
}
if err := git.InitSimple(ctx); err != nil {
return err
}
log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ {
repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"strings"
"github.com/urfave/cli"
)
func RunMainApp(app *cli.App, args ...string) error {
err := app.Run(args)
if err == nil {
return nil
}
if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
// the cli package should already have output the error message, so just exit
cli.OsExiter(1)
return err
}
_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
cli.OsExiter(1)
return err
}
+68
View File
@@ -4,9 +4,16 @@
package cmd
import (
"fmt"
"io"
"strings"
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
func TestMain(m *testing.M) {
@@ -14,3 +21,64 @@ func TestMain(m *testing.M) {
GiteaRootPath: "..",
})
}
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
app := cli.NewApp()
app.HelpName = "gitea"
testCmd := cli.Command{Name: "test-cmd", Action: testCmdAction}
app.Commands = append(app.Commands, testCmd)
return app
}
type runResult struct {
Stdout string
Stderr string
ExitCode int
}
func runTestApp(app *cli.App, args ...string) (runResult, error) {
outBuf := new(strings.Builder)
errBuf := new(strings.Builder)
app.Writer = outBuf
app.ErrWriter = errBuf
exitCode := -1
defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)()
defer test.MockVariableValue(&cli.OsExiter, func(code int) {
if exitCode == -1 {
exitCode = code // save the exit code once and then reset the writer (to simulate the exit)
app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard
}
})()
err := RunMainApp(app, args...)
return runResult{outBuf.String(), errBuf.String(), exitCode}, err
}
func TestCliCmdError(t *testing.T) {
app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
r, err := runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Equal(t, "", r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(func(ctx *cli.Context) error { return cli.NewExitError("exit error", 2) })
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 2, r.ExitCode)
assert.Equal(t, "", r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.EqualValues(t, "Incorrect Usage: flag provided but not defined: -no-such\n\nNAME:\n gitea test-cmd - \n\nUSAGE:\n gitea test-cmd [arguments...]\n", r.Stdout)
assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
assert.Equal(t, "", r.Stdout)
assert.Equal(t, "", r.Stderr)
}
-1
View File
@@ -46,7 +46,6 @@ PATH = /data/gitea/attachments
[log]
MODE = console
LEVEL = info
ROUTER = console
ROOT_PATH = /data/gitea/log
[security]
@@ -79,8 +79,9 @@ SMTP_PORT = 465
FROM = example.user@gmail.com
USER = example.user
PASSWD = `***`
PROTOCOL = smtp
IS_TLS_ENABLED = true
PROTOCOL = smtps ; Gitea >= 1.19.0
; PROTOCOL = smtp ; Gitea < 1.19.0
; IS_TLS_ENABLED = true ; Gitea < 1.19.0
```
Note that you'll need to create and use an [App password](https://support.google.com/accounts/answer/185833?hl=en) by enabling 2FA on your Google
@@ -102,8 +102,11 @@ MODE = file, file-error
; by default, the "file" mode will record logs to %(log.ROOT_PATH)/gitea.log, so we don't need to set it
; [log.file]
; by default, the MODE (actually it's the output writer of this logger) is taken from the section name, so we don't need to set it either
; MODE = file
[log.file-error]
MODE = file
LEVEL = Error
FILE_NAME = file-error.log
```
@@ -40,7 +40,7 @@ apk add gitea
## Arch Linux
The rolling release distribution has [Gitea](https://www.archlinux.org/packages/community/x86_64/gitea/) in their official community repository and package updates are provided with new Gitea releases.
The rolling release distribution has [Gitea](https://www.archlinux.org/packages/extra/x86_64/gitea/) in their official extra repository and package updates are provided with new Gitea releases.
```sh
pacman -S gitea
@@ -17,16 +17,20 @@ menu:
# Upgrade from an old Gitea
To update Gitea, download a newer version, stop the old one, perform a backup, and run the new one.
Every time a Gitea instance starts up, it checks whether a database migration should be run.
If a database migration is required, Gitea will take some time to complete the upgrade and then serve.
Follow below steps to ensure a smooth upgrade to a new Gitea version.
## Check the Changelog for breaking changes
To make Gitea better, some breaking changes are unavoidable, especially for big milestone releases.
Before upgrade, please read the [Changelog on Gitea blog](https://blog.gitea.io/)
Before upgrading, please read the [Changelog on Gitea blog](https://blog.gitea.com/)
and check whether the breaking changes affect your Gitea instance.
## Verify there are no deprecated configuration options
New versions of Gitea often come with changed configuration syntax or options which are usually displayed for
at least one release cycle inside at the top of the Site Administration panel. If these warnings are not
resolved, Gitea may refuse to start in the following version.
## Backup for downgrade
Gitea keeps compatibility for patch versions whose first two fields are the same (`a.b.x` -> `a.b.y`),
@@ -60,6 +64,11 @@ Backup steps:
If you are using cloud services or filesystems with snapshot feature,
a snapshot for the Gitea data volume and related object storage is more convenient.
After all of steps have been prepared, download the new version, stop the application, perform a backup and
then start the new application. On each startup, Gitea verifies that the database is up to date and will automtically
perform any necessary migrations. Depending on the size of the database, this can take some additional time on the
first launch during which the application will be unavailable.
## Upgrade with Docker
* `docker pull` the latest Gitea release.
@@ -15,16 +15,20 @@ menu:
# 从旧版 Gitea 升级
想要升级 Gitea只需要下载新版停止运行旧版进行数据备份然后运行新版就好
每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。
如果需要进行数据库迁移Gitea 会花一些时间完成升级然后继续服务。
在升级之前,您需要做如下的准备工作
## 为重大变更检查更新日志
为了让 Gitea 变得更好,进行重大变更是不可避免的,尤其是一些里程碑更新的发布。
在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.io/)
在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.com/)
并检查重大变更是否会影响你的 Gitea 实例。
## 在控制面板中检查过期的配置项
一些配置项可能会在后续版本中过期,你需要在控制面板中检查他们。如果不解决过期的配置项,
Gitea也许会在升级后无法重启。你可以访问 https://docs.gitea.com 获得要升级的版本
对应的文档来修改你的配置文件。
## 降级前的备份
Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`)
@@ -56,6 +60,10 @@ Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`
如果你在使用云服务或拥有快照功能的文件系统,
最好对 Gitea 的数据盘及相关资料存储进行一次快照。
在所有上述步骤准备妥当之后,要升级 Gitea只需要下载新版停止运行旧版进行数据备份然后运行新版就好。
每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。
如果需要进行数据库迁移Gitea 会花一些时间完成升级然后继续服务。
## 从 Docker 升级
* `docker pull` 拉取 Gitea 的最新发布版。
@@ -123,7 +123,7 @@ jobs:
请注意,演示文件中包含一些表情符号。
请确保您的数据库支持它们特别是在使用MySQL时。
如果字符集不是`utf8mb4将出现错误例如`Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\x8E\\x89 T...' for column 'name' at row 1`。
如果字符集不是`utf8mb4`,将出现错误,例如`Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\x8E\\x89 T...' for column 'name' at row 1`
有关更多信息,请参阅[数据库准备工作](installation/database-preparation.md#mysql)。
或者,您可以从演示文件中删除所有表情符号,然后再尝试一次。
+2 -2
View File
@@ -21,8 +21,8 @@ In Gitea `1.13`, support for [agit](https://git-repo.info/en/2020/03/agit-flow-a
## Creating PRs with Agit
Agit allows to create PRs while pushing code to the remote repo. \
This can be done by pushing to the branch followed by a specific refspec (a location identifier known to git). \
Agit allows to create PRs while pushing code to the remote repo.
This can be done by pushing to the branch followed by a specific refspec (a location identifier known to git).
The following example illustrates this:
```shell
+1 -1
View File
@@ -109,7 +109,7 @@ require (
github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.11.0
golang.org/x/image v0.7.0
golang.org/x/net v0.12.0
golang.org/x/net v0.13.0
golang.org/x/oauth2 v0.8.0
golang.org/x/sys v0.10.0
golang.org/x/text v0.11.0
+2 -2
View File
@@ -1269,8 +1269,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+4 -4
View File
@@ -146,11 +146,11 @@ func main() {
app.Commands = append(app.Commands, subCmdWithIni...)
app.Commands = append(app.Commands, subCmdStandalone...)
err := app.Run(os.Args)
if err != nil {
_, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err)
cli.OsExiter = func(code int) {
log.GetManager().Close()
os.Exit(code)
}
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
log.GetManager().Close()
}
+1 -1
View File
@@ -281,7 +281,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil {
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
} else if len(gots) != 1 {
return nil, false, fmt.Errorf("workflow of job %d: not signle workflow", job.ID)
return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID)
} else {
_, workflowJob = gots[0].Job()
}
+24 -8
View File
@@ -685,18 +685,34 @@ func NotifyWatchersActions(acts []*Action) error {
}
// DeleteIssueActions delete all actions related with issueID
func DeleteIssueActions(ctx context.Context, repoID, issueID int64) error {
func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64) error {
// delete actions assigned to this issue
subQuery := builder.Select("`id`").
From("`comment`").
Where(builder.Eq{"`issue_id`": issueID})
if _, err := db.GetEngine(ctx).In("comment_id", subQuery).Delete(&Action{}); err != nil {
return err
e := db.GetEngine(ctx)
// MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
// so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
var lastCommentID int64
commentIDs := make([]int64, 0, db.DefaultMaxInSize)
for {
commentIDs = commentIDs[:0]
err := e.Select("`id`").Table(&issues_model.Comment{}).
Where(builder.Eq{"issue_id": issueID}).And("`id` > ?", lastCommentID).
OrderBy("`id`").Limit(db.DefaultMaxInSize).
Find(&commentIDs)
if err != nil {
return err
} else if len(commentIDs) == 0 {
break
} else if _, err = db.GetEngine(ctx).In("comment_id", commentIDs).Delete(&Action{}); err != nil {
return err
} else {
lastCommentID = commentIDs[len(commentIDs)-1]
}
}
_, err := db.GetEngine(ctx).Table("action").Where("repo_id = ?", repoID).
_, err := e.Where("repo_id = ?", repoID).
In("op_type", ActionCreateIssue, ActionCreatePullRequest).
Where("content LIKE ?", strconv.FormatInt(issueID, 10)+"|%").
Where("content LIKE ?", strconv.FormatInt(issueIndex, 10)+"|%"). // "IssueIndex|content..."
Delete(&Action{})
return err
}
+34
View File
@@ -4,6 +4,7 @@
package activities_test
import (
"fmt"
"path"
"testing"
@@ -284,3 +285,36 @@ func TestConsistencyUpdateAction(t *testing.T) {
assert.NoError(t, db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions))
unittest.CheckConsistencyFor(t, &activities_model.Action{})
}
func TestDeleteIssueActions(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// load an issue
issue := unittest.AssertExistsAndLoadBean(t, &issue_model.Issue{ID: 4})
assert.NotEqualValues(t, issue.ID, issue.Index) // it needs to use different ID/Index to test the DeleteIssueActions to delete some actions by IssueIndex
// insert a comment
err := db.Insert(db.DefaultContext, &issue_model.Comment{Type: issue_model.CommentTypeComment, IssueID: issue.ID})
assert.NoError(t, err)
comment := unittest.AssertExistsAndLoadBean(t, &issue_model.Comment{Type: issue_model.CommentTypeComment, IssueID: issue.ID})
// truncate action table and insert some actions
err = db.TruncateBeans(db.DefaultContext, &activities_model.Action{})
assert.NoError(t, err)
err = db.Insert(db.DefaultContext, &activities_model.Action{
OpType: activities_model.ActionCommentIssue,
CommentID: comment.ID,
})
assert.NoError(t, err)
err = db.Insert(db.DefaultContext, &activities_model.Action{
OpType: activities_model.ActionCreateIssue,
RepoID: issue.RepoID,
Content: fmt.Sprintf("%d|content...", issue.Index),
})
assert.NoError(t, err)
// assert that the actions exist, then delete them
unittest.AssertCount(t, &activities_model.Action{}, 2)
assert.NoError(t, activities_model.DeleteIssueActions(db.DefaultContext, issue.RepoID, issue.ID, issue.Index))
unittest.AssertCount(t, &activities_model.Action{}, 0)
}
+6 -5
View File
@@ -310,7 +310,7 @@ func createIssueNotification(ctx context.Context, userID int64, issue *issues_mo
}
func updateIssueNotification(ctx context.Context, userID, issueID, commentID, updatedByID int64) error {
notification, err := getIssueNotification(ctx, userID, issueID)
notification, err := GetIssueNotification(ctx, userID, issueID)
if err != nil {
return err
}
@@ -331,7 +331,8 @@ func updateIssueNotification(ctx context.Context, userID, issueID, commentID, up
return err
}
func getIssueNotification(ctx context.Context, userID, issueID int64) (*Notification, error) {
// GetIssueNotification return the notification about an issue
func GetIssueNotification(ctx context.Context, userID, issueID int64) (*Notification, error) {
notification := new(Notification)
_, err := db.GetEngine(ctx).
Where("user_id = ?", userID).
@@ -742,7 +743,7 @@ func GetUIDsAndNotificationCounts(since, until timeutil.TimeStamp) ([]UserIDCoun
// SetIssueReadBy sets issue to be read by given user.
func SetIssueReadBy(ctx context.Context, issueID, userID int64) error {
if err := issues_model.UpdateIssueUserByRead(userID, issueID); err != nil {
if err := issues_model.UpdateIssueUserByRead(ctx, userID, issueID); err != nil {
return err
}
@@ -750,7 +751,7 @@ func SetIssueReadBy(ctx context.Context, issueID, userID int64) error {
}
func setIssueNotificationStatusReadIfUnread(ctx context.Context, userID, issueID int64) error {
notification, err := getIssueNotification(ctx, userID, issueID)
notification, err := GetIssueNotification(ctx, userID, issueID)
// ignore if not exists
if err != nil {
return nil
@@ -762,7 +763,7 @@ func setIssueNotificationStatusReadIfUnread(ctx context.Context, userID, issueID
notification.Status = NotificationStatusRead
_, err = db.GetEngine(ctx).ID(notification.ID).Update(notification)
_, err = db.GetEngine(ctx).ID(notification.ID).Cols("status").Update(notification)
return err
}
+14
View File
@@ -4,6 +4,7 @@
package activities_test
import (
"context"
"testing"
activities_model "code.gitea.io/gitea/models/activities"
@@ -109,3 +110,16 @@ func TestUpdateNotificationStatuses(t *testing.T) {
unittest.AssertExistsAndLoadBean(t,
&activities_model.Notification{ID: notfPinned.ID, Status: activities_model.NotificationStatusPinned})
}
func TestSetIssueReadBy(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
assert.NoError(t, db.WithTx(db.DefaultContext, func(ctx context.Context) error {
return activities_model.SetIssueReadBy(ctx, issue.ID, user.ID)
}))
nt, err := activities_model.GetIssueNotification(db.DefaultContext, user.ID, issue.ID)
assert.NoError(t, err)
assert.EqualValues(t, activities_model.NotificationStatusRead, nt.Status)
}
+16
View File
@@ -20,3 +20,19 @@ func BuildCaseInsensitiveLike(key, value string) builder.Cond {
}
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
}
// BuilderDialect returns the xorm.Builder dialect of the engine
func BuilderDialect() string {
switch {
case setting.Database.Type.IsMySQL():
return builder.MYSQL
case setting.Database.Type.IsSQLite3():
return builder.SQLITE
case setting.Database.Type.IsPostgreSQL():
return builder.POSTGRES
case setting.Database.Type.IsMSSQL():
return builder.MSSQL
default:
return ""
}
}
+6
View File
@@ -280,3 +280,9 @@
team_id: 20
type: 9 # package
access_mode: 2
-
id: 48
team_id: 2
type: 8
access_mode: 2
+3 -3
View File
@@ -283,9 +283,9 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
Where("repo_id = ?", repoID).And("sha = ?", sha).
Select("max( id ) as id").
GroupBy("context_hash").OrderBy("max( id ) desc")
sess = db.SetSessionPagination(sess, &listOptions)
if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions)
}
count, err := sess.FindAndCount(&ids)
if err != nil {
return nil, count, err
+13 -6
View File
@@ -155,6 +155,18 @@ func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sess
return sess
}
func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
if opts.ProjectID > 0 { // specific project
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
And("project_issue.project_id=?", opts.ProjectID)
} else if opts.ProjectID == db.NoConditionID { // show those that are in no project
sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue").And(builder.Neq{"project_id": 0})))
}
// opts.ProjectID == 0 means all projects,
// do not need to apply any condition
return sess
}
func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
if len(opts.RepoIDs) == 1 {
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]}
@@ -213,12 +225,7 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
sess.And(builder.Lte{"issue.updated_unix": opts.UpdatedBeforeUnix})
}
if opts.ProjectID > 0 {
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
And("project_issue.project_id=?", opts.ProjectID)
} else if opts.ProjectID == db.NoConditionID { // show those that are in no project
sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue")))
}
applyProjectCondition(sess, opts)
if opts.ProjectBoardID != 0 {
if opts.ProjectBoardID > 0 {
+1 -4
View File
@@ -133,10 +133,7 @@ func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, err
applyMilestoneCondition(sess, opts)
if opts.ProjectID > 0 {
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
And("project_issue.project_id=?", opts.ProjectID)
}
applyProjectCondition(sess, opts)
if opts.AssigneeID > 0 {
applyAssigneeCondition(sess, opts.AssigneeID)
+2 -2
View File
@@ -55,8 +55,8 @@ func NewIssueUsers(ctx context.Context, repo *repo_model.Repository, issue *Issu
}
// UpdateIssueUserByRead updates issue-user relation for reading.
func UpdateIssueUserByRead(uid, issueID int64) error {
_, err := db.GetEngine(db.DefaultContext).Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
func UpdateIssueUserByRead(ctx context.Context, uid, issueID int64) error {
_, err := db.GetEngine(ctx).Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
return err
}
+3 -3
View File
@@ -40,13 +40,13 @@ func TestUpdateIssueUserByRead(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
assert.NoError(t, issues_model.UpdateIssueUserByRead(4, issue.ID))
assert.NoError(t, issues_model.UpdateIssueUserByRead(db.DefaultContext, 4, issue.ID))
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
assert.NoError(t, issues_model.UpdateIssueUserByRead(4, issue.ID))
assert.NoError(t, issues_model.UpdateIssueUserByRead(db.DefaultContext, 4, issue.ID))
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
assert.NoError(t, issues_model.UpdateIssueUserByRead(unittest.NonexistentID, unittest.NonexistentID))
assert.NoError(t, issues_model.UpdateIssueUserByRead(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID))
}
func TestUpdateIssueUsersByMentions(t *testing.T) {
+2 -3
View File
@@ -531,13 +531,12 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
}
// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
ctx, committer, err := db.TxContext(outerCtx)
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
ctx.WithContext(outerCtx)
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
if err != nil {
+2 -2
View File
@@ -87,14 +87,14 @@ func TestLoadRequestedReviewers(t *testing.T) {
user1, err := user_model.GetUserByID(db.DefaultContext, 1)
assert.NoError(t, err)
comment, err := issues_model.AddReviewRequest(issue, user1, &user_model.User{})
comment, err := issues_model.AddReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
assert.NoError(t, err)
assert.NotNil(t, comment)
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
assert.Len(t, pull.RequestedReviewers, 1)
comment, err = issues_model.RemoveReviewRequest(issue, user1, &user_model.User{})
comment, err = issues_model.RemoveReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
assert.NoError(t, err)
assert.NotNil(t, comment)
+8 -8
View File
@@ -557,8 +557,8 @@ func InsertReviews(reviews []*Review) error {
}
// AddReviewRequest add a review request from one reviewer
func AddReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
@@ -612,8 +612,8 @@ func AddReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment,
}
// RemoveReviewRequest remove a review request from one reviewer
func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
@@ -673,8 +673,8 @@ func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64)
}
// AddTeamReviewRequest add a review request from one team
func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
func AddTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
@@ -732,8 +732,8 @@ func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_
}
// RemoveTeamReviewRequest remove a review request from one team
func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"context"
"strings"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"xorm.io/builder"
)
// SearchVersions gets all versions of packages matching the search options
func SearchVersions(ctx context.Context, opts *packages_model.PackageSearchOptions) ([]*packages_model.PackageVersion, int64, error) {
cond := toConds(opts)
e := db.GetEngine(ctx)
total, err := e.
Where(cond).
Count(&packages_model.Package{})
if err != nil {
return nil, 0, err
}
inner := builder.
Dialect(db.BuilderDialect()). // builder needs the sql dialect to build the Limit() below
Select("*").
From("package").
Where(cond).
OrderBy("package.name ASC")
if opts.Paginator != nil {
skip, take := opts.GetSkipTake()
inner = inner.Limit(take, skip)
}
sess := e.
Where(opts.ToConds()).
Table("package_version").
Join("INNER", inner, "package.id = package_version.package_id")
pvs := make([]*packages_model.PackageVersion, 0, 10)
return pvs, total, sess.Find(&pvs)
}
// CountPackages counts all packages matching the search options
func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOptions) (int64, error) {
return db.GetEngine(ctx).
Where(toConds(opts)).
Count(&packages_model.Package{})
}
func toConds(opts *packages_model.PackageSearchOptions) builder.Cond {
var cond builder.Cond = builder.Eq{
"package.is_internal": opts.IsInternal.IsTrue(),
"package.owner_id": opts.OwnerID,
"package.type": packages_model.TypeNuGet,
}
if opts.Name.Value != "" {
if opts.Name.ExactMatch {
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
} else {
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
}
}
return cond
}
+5 -5
View File
@@ -189,7 +189,7 @@ type PackageSearchOptions struct {
db.Paginator
}
func (opts *PackageSearchOptions) toConds() builder.Cond {
func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if !opts.IsInternal.IsNone() {
cond = builder.Eq{
@@ -283,7 +283,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
// SearchVersions gets all versions of packages matching the search options
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
sess := db.GetEngine(ctx).
Where(opts.toConds()).
Where(opts.ToConds()).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id")
@@ -300,7 +300,7 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package
// SearchLatestVersions gets the latest version of every package matching the search options
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
cond := opts.toConds().
cond := opts.ToConds().
And(builder.Expr("pv2.id IS NULL"))
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))")
@@ -328,7 +328,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
// ExistVersion checks if a version matching the search options exist
func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
return db.GetEngine(ctx).
Where(opts.toConds()).
Where(opts.ToConds()).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Exist(new(PackageVersion))
@@ -337,7 +337,7 @@ func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error)
// CountVersions counts all versions of packages matching the search options
func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) {
return db.GetEngine(ctx).
Where(opts.toConds()).
Where(opts.ToConds()).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Count(new(PackageVersion))
+1 -1
View File
@@ -22,7 +22,7 @@ func init() {
db.RegisterModel(new(RepoTopic))
}
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
var topicPattern = regexp.MustCompile(`^[a-z0-9][-.a-z0-9]*$`)
// Topic represents a topic of repositories
type Topic struct {
+2
View File
@@ -69,6 +69,7 @@ func TestAddTopic(t *testing.T) {
func TestTopicValidator(t *testing.T) {
assert.True(t, repo_model.ValidateTopic("12345"))
assert.True(t, repo_model.ValidateTopic("2-test"))
assert.True(t, repo_model.ValidateTopic("foo.bar"))
assert.True(t, repo_model.ValidateTopic("test-3"))
assert.True(t, repo_model.ValidateTopic("first"))
assert.True(t, repo_model.ValidateTopic("second-test-topic"))
@@ -77,4 +78,5 @@ func TestTopicValidator(t *testing.T) {
assert.False(t, repo_model.ValidateTopic("$fourth-test,topic"))
assert.False(t, repo_model.ValidateTopic("-fifth-test-topic"))
assert.False(t, repo_model.ValidateTopic("sixth-go-project-topic-with-excess-length"))
assert.False(t, repo_model.ValidateTopic(".foo"))
}
+6 -1
View File
@@ -203,11 +203,16 @@ func UpdateUserTheme(u *User, themeName string) error {
return UpdateUserCols(db.DefaultContext, u, "theme")
}
// GetPlaceholderEmail returns an noreply email
func (u *User) GetPlaceholderEmail() string {
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
}
// GetEmail returns an noreply email, if the user has set to keep his
// email address private, otherwise the primary email address.
func (u *User) GetEmail() string {
if u.KeepEmailPrivate {
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
return u.GetPlaceholderEmail()
}
return u.Email
}
+4
View File
@@ -143,6 +143,10 @@ func (b *Base) Params(p string) string {
return s
}
func (b *Base) PathParamRaw(p string) string {
return chi.URLParam(b.Req, strings.TrimPrefix(p, ":"))
}
// ParamsInt64 returns the param on route as int64
func (b *Base) ParamsInt64(p string) int64 {
v, _ := strconv.ParseInt(b.Params(p), 10, 64)
+4
View File
@@ -74,6 +74,8 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
Stderr: &stderr,
UseContextTimeout: true,
})
if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
@@ -124,6 +126,8 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
Stderr: &stderr,
UseContextTimeout: true,
})
if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
+12 -5
View File
@@ -5,11 +5,14 @@ package git
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"regexp"
"code.gitea.io/gitea/modules/log"
)
// BlamePart represents block of blame - continuous lines with one sha
@@ -115,15 +118,19 @@ func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*B
done := make(chan error, 1)
go func(cmd *Command, dir string, stdout io.WriteCloser, done chan error) {
if err := cmd.Run(&RunOpts{
stderr := bytes.Buffer{}
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
err := cmd.Run(&RunOpts{
UseContextTimeout: true,
Dir: dir,
Stdout: stdout,
Stderr: os.Stderr,
}); err == nil {
stdout.Close()
}
Stderr: &stderr,
})
done <- err
_ = stdout.Close()
if err != nil {
log.Error("Error running git blame (dir: %v): %v, stderr: %v", repoPath, err, stderr.String())
}
}(cmd, repoPath, stdout, done)
bufferedReader := bufio.NewReader(reader)
+1 -1
View File
@@ -80,7 +80,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error {
// IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) {
var errbuf, output strings.Builder
if err := NewCommand(repo.Ctx).AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("show-ref", "--head", "^HEAD$").
if err := NewCommand(repo.Ctx).AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all").
Run(&RunOpts{
Dir: repo.Path,
Stdout: &output,
+9 -1
View File
@@ -280,8 +280,16 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
}
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
// If base is undefined empty SHA (zeros), it only returns the files changed in the head commit
// If base is the SHA of an empty tree (EmptyTreeSHA), it returns the files changes from the initial commit to the head commit
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
if base == EmptySHA {
cmd.AddDynamicArguments(head)
} else {
cmd.AddDynamicArguments(base, head)
}
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
+39
View File
@@ -119,3 +119,42 @@ func TestReadWritePullHead(t *testing.T) {
err = repo.RemoveReference(PullPrefix + "1/head")
assert.NoError(t, err)
}
func TestGetCommitFilesChanged(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer repo.Close()
testCases := []struct {
base, head string
files []string
}{
{
EmptySHA,
"95bb4d39648ee7e325106df01a621c530863a653",
[]string{"file1.txt"},
},
{
EmptySHA,
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
[]string{"file2.txt"},
},
{
"95bb4d39648ee7e325106df01a621c530863a653",
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
[]string{"file2.txt"},
},
{
EmptyTreeSHA,
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
[]string{"file1.txt", "file2.txt"},
},
}
for _, tc := range testCases {
changedFiles, err := repo.GetFilesChangedBetween(tc.base, tc.head)
assert.NoError(t, err)
assert.ElementsMatch(t, tc.files, changedFiles)
}
}
+2 -2
View File
@@ -11,10 +11,10 @@ import (
"strings"
)
// EmptySHA defines empty git SHA
// EmptySHA defines empty git SHA (undefined, non-existent)
const EmptySHA = "0000000000000000000000000000000000000000"
// EmptyTreeSHA is the SHA of an empty tree
// EmptyTreeSHA is the SHA of an empty tree, the root of all git repositories
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
// SHAFullLength is the full length of a git SHA
+11 -10
View File
@@ -172,19 +172,10 @@ func ParseControlFile(r io.Reader) (*Package, error) {
value := strings.TrimSpace(parts[1])
switch key {
case "Package":
if !namePattern.MatchString(value) {
return nil, ErrInvalidName
}
p.Name = value
case "Version":
if !versionPattern.MatchString(value) {
return nil, ErrInvalidVersion
}
p.Version = value
case "Architecture":
if value == "" {
return nil, ErrInvalidArchitecture
}
p.Architecture = value
case "Maintainer":
a, err := mail.ParseAddress(value)
@@ -208,13 +199,23 @@ func ParseControlFile(r io.Reader) (*Package, error) {
return nil, err
}
if !namePattern.MatchString(p.Name) {
return nil, ErrInvalidName
}
if !versionPattern.MatchString(p.Version) {
return nil, ErrInvalidVersion
}
if p.Architecture == "" {
return nil, ErrInvalidArchitecture
}
dependencies := strings.Split(depends.String(), ",")
for i := range dependencies {
dependencies[i] = strings.TrimSpace(dependencies[i])
}
p.Metadata.Dependencies = dependencies
p.Control = control.String()
p.Control = strings.TrimSpace(control.String())
return p, nil
}
+6
View File
@@ -4,6 +4,7 @@
package setting
import (
"bytes"
"os"
"regexp"
"strconv"
@@ -131,6 +132,11 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
log.Error("Error reading file for %s : %v", envKey, envValue, err)
continue
}
if bytes.HasSuffix(fileContent, []byte("\r\n")) {
fileContent = fileContent[:len(fileContent)-2]
} else if bytes.HasSuffix(fileContent, []byte("\n")) {
fileContent = fileContent[:len(fileContent)-1]
}
keyValue = string(fileContent)
}
+15
View File
@@ -99,4 +99,19 @@ key = old
changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
assert.True(t, changed)
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
cfg, _ = NewConfigProviderFromData("")
_ = os.WriteFile(tmpFile, []byte("value-from-file\n"), 0o644)
EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
cfg, _ = NewConfigProviderFromData("")
_ = os.WriteFile(tmpFile, []byte("value-from-file\r\n"), 0o644)
EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
cfg, _ = NewConfigProviderFromData("")
_ = os.WriteFile(tmpFile, []byte("value-from-file\n\n"), 0o644)
EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
assert.Equal(t, "value-from-file\n", cfg.Section("sec").Key("key").String())
}
+1 -1
View File
@@ -165,7 +165,7 @@ func loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (wri
writerMode.WriterOption = writerOption
default:
if !log.HasEventWriter(writerType) {
return "", "", writerMode, fmt.Errorf("invalid log writer type (mode): %s", writerType)
return "", "", writerMode, fmt.Errorf("invalid log writer type (mode): %s, maybe it needs something like 'MODE=file' in [log.%s] section", writerType, modeName)
}
}
+150 -73
View File
@@ -84,102 +84,179 @@ func getDefaultStorageSection(rootCfg ConfigProvider) ConfigSection {
return storageSec
}
// getStorage will find target section and extra special section first and then read override
// items from extra section
func getStorage(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (*Storage, error) {
if name == "" {
return nil, errors.New("no name for storage")
}
var targetSec ConfigSection
if typ != "" {
var err error
targetSec, err = rootCfg.GetSection(storageSectionName + "." + typ)
if err != nil {
if !IsValidStorageType(StorageType(typ)) {
return nil, fmt.Errorf("get section via storage type %q failed: %v", typ, err)
}
}
if targetSec != nil {
targetType := targetSec.Key("STORAGE_TYPE").String()
if targetType == "" {
if !IsValidStorageType(StorageType(typ)) {
return nil, fmt.Errorf("unknow storage type %q", typ)
}
targetSec.Key("STORAGE_TYPE").SetValue(typ)
} else if !IsValidStorageType(StorageType(targetType)) {
return nil, fmt.Errorf("unknow storage type %q for section storage.%v", targetType, typ)
}
}
targetSec, tp, err := getStorageTargetSection(rootCfg, name, typ, sec)
if err != nil {
return nil, err
}
storageNameSec, _ := rootCfg.GetSection(storageSectionName + "." + name)
overrideSec := getStorageOverrideSection(rootCfg, targetSec, sec, tp, name)
if targetSec == nil {
targetSec = sec
targetType := targetSec.Key("STORAGE_TYPE").String()
switch targetType {
case string(LocalStorageType):
return getStorageForLocal(targetSec, overrideSec, tp, name)
case string(MinioStorageType):
return getStorageForMinio(targetSec, overrideSec, tp, name)
default:
return nil, fmt.Errorf("unsupported storage type %q", targetType)
}
if targetSec == nil {
targetSec = storageNameSec
}
if targetSec == nil {
targetSec = getDefaultStorageSection(rootCfg)
} else {
targetType := targetSec.Key("STORAGE_TYPE").String()
switch {
case targetType == "":
if targetSec.Key("PATH").String() == "" {
targetSec = getDefaultStorageSection(rootCfg)
} else {
targetSec.Key("STORAGE_TYPE").SetValue("local")
}
default:
newTargetSec, _ := rootCfg.GetSection(storageSectionName + "." + targetType)
if newTargetSec == nil {
if !IsValidStorageType(StorageType(targetType)) {
return nil, fmt.Errorf("invalid storage section %s.%q", storageSectionName, targetType)
}
} else {
targetSec = newTargetSec
if IsValidStorageType(StorageType(targetType)) {
tp := targetSec.Key("STORAGE_TYPE").String()
if tp == "" {
targetSec.Key("STORAGE_TYPE").SetValue(targetType)
}
}
}
}
type targetSecType int
const (
targetSecIsTyp targetSecType = iota // target section is [storage.type] which the type from parameter
targetSecIsStorage // target section is [storage]
targetSecIsDefault // target section is the default value
targetSecIsStorageWithName // target section is [storage.name]
targetSecIsSec // target section is from the name seciont [name]
)
func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) {
targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
if err != nil {
if !IsValidStorageType(StorageType(typ)) {
return nil, 0, fmt.Errorf("get section via storage type %q failed: %v", typ, err)
}
// if typ is a valid storage type, but there is no [storage.local] or [storage.minio] section
// it's not an error
return nil, 0, nil
}
targetType := targetSec.Key("STORAGE_TYPE").String()
if !IsValidStorageType(StorageType(targetType)) {
return nil, fmt.Errorf("invalid storage type %q", targetType)
if targetType == "" {
if !IsValidStorageType(StorageType(typ)) {
return nil, 0, fmt.Errorf("unknow storage type %q", typ)
}
targetSec.Key("STORAGE_TYPE").SetValue(typ)
} else if !IsValidStorageType(StorageType(targetType)) {
return nil, 0, fmt.Errorf("unknow storage type %q for section storage.%v", targetType, typ)
}
var storage Storage
storage.Type = StorageType(targetType)
return targetSec, targetSecIsTyp, nil
}
switch targetType {
case string(LocalStorageType):
storage.Path = ConfigSectionKeyString(targetSec, "PATH", filepath.Join(AppDataPath, name))
if !filepath.IsAbs(storage.Path) {
storage.Path = filepath.Join(AppWorkPath, storage.Path)
func getStorageTargetSection(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (ConfigSection, targetSecType, error) {
// check typ first
if typ == "" {
if sec != nil { // check sec's type secondly
typ = sec.Key("STORAGE_TYPE").String()
if IsValidStorageType(StorageType(typ)) {
if targetSec, _ := rootCfg.GetSection(storageSectionName + "." + typ); targetSec == nil {
return sec, targetSecIsSec, nil
}
}
}
case string(MinioStorageType):
storage.MinioConfig.BasePath = name + "/"
}
if err := targetSec.MapTo(&storage.MinioConfig); err != nil {
return nil, fmt.Errorf("map minio config failed: %v", err)
if typ != "" {
targetSec, tp, err := getStorageSectionByType(rootCfg, typ)
if targetSec != nil || err != nil {
return targetSec, tp, err
}
// extra config section will be read SERVE_DIRECT, PATH, MINIO_BASE_PATH to override the targetsec
extraConfigSec := sec
if extraConfigSec == nil {
extraConfigSec = storageNameSec
}
// check stoarge name thirdly
targetSec, _ := rootCfg.GetSection(storageSectionName + "." + name)
if targetSec != nil {
targetType := targetSec.Key("STORAGE_TYPE").String()
switch {
case targetType == "":
if targetSec.Key("PATH").String() == "" { // both storage type and path are empty, use default
return getDefaultStorageSection(rootCfg), targetSecIsDefault, nil
}
targetSec.Key("STORAGE_TYPE").SetValue("local")
default:
targetSec, tp, err := getStorageSectionByType(rootCfg, targetType)
if targetSec != nil || err != nil {
return targetSec, tp, err
}
}
if extraConfigSec != nil {
storage.MinioConfig.ServeDirect = ConfigSectionKeyBool(extraConfigSec, "SERVE_DIRECT", storage.MinioConfig.ServeDirect)
storage.MinioConfig.BasePath = ConfigSectionKeyString(extraConfigSec, "MINIO_BASE_PATH", storage.MinioConfig.BasePath)
storage.MinioConfig.Bucket = ConfigSectionKeyString(extraConfigSec, "MINIO_BUCKET", storage.MinioConfig.Bucket)
return targetSec, targetSecIsStorageWithName, nil
}
return getDefaultStorageSection(rootCfg), targetSecIsDefault, nil
}
// getStorageOverrideSection override section will be read SERVE_DIRECT, PATH, MINIO_BASE_PATH, MINIO_BUCKET to override the targetsec when possible
func getStorageOverrideSection(rootConfig ConfigProvider, targetSec, sec ConfigSection, targetSecType targetSecType, name string) ConfigSection {
if targetSecType == targetSecIsSec {
return nil
}
if sec != nil {
return sec
}
if targetSecType != targetSecIsStorageWithName {
nameSec, _ := rootConfig.GetSection(storageSectionName + "." + name)
return nameSec
}
return nil
}
func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) {
storage := Storage{
Type: StorageType(targetSec.Key("STORAGE_TYPE").String()),
}
targetPath := ConfigSectionKeyString(targetSec, "PATH", "")
var fallbackPath string
if targetPath == "" { // no path
fallbackPath = filepath.Join(AppDataPath, name)
} else {
if tp == targetSecIsStorage || tp == targetSecIsDefault {
fallbackPath = filepath.Join(targetPath, name)
} else {
fallbackPath = targetPath
}
if !filepath.IsAbs(fallbackPath) {
fallbackPath = filepath.Join(AppDataPath, fallbackPath)
}
}
if overrideSec == nil { // no override section
storage.Path = fallbackPath
} else {
storage.Path = ConfigSectionKeyString(overrideSec, "PATH", "")
if storage.Path == "" { // overrideSec has no path
storage.Path = fallbackPath
} else if !filepath.IsAbs(storage.Path) {
if targetPath == "" {
storage.Path = filepath.Join(AppDataPath, storage.Path)
} else {
storage.Path = filepath.Join(targetPath, storage.Path)
}
}
}
return &storage, nil
}
func getStorageForMinio(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) {
var storage Storage
storage.Type = StorageType(targetSec.Key("STORAGE_TYPE").String())
if err := targetSec.MapTo(&storage.MinioConfig); err != nil {
return nil, fmt.Errorf("map minio config failed: %v", err)
}
if storage.MinioConfig.BasePath == "" {
storage.MinioConfig.BasePath = name + "/"
}
if overrideSec != nil {
storage.MinioConfig.ServeDirect = ConfigSectionKeyBool(overrideSec, "SERVE_DIRECT", storage.MinioConfig.ServeDirect)
storage.MinioConfig.BasePath = ConfigSectionKeyString(overrideSec, "MINIO_BASE_PATH", storage.MinioConfig.BasePath)
storage.MinioConfig.Bucket = ConfigSectionKeyString(overrideSec, "MINIO_BUCKET", storage.MinioConfig.Bucket)
}
return &storage, nil
}
+322
View File
@@ -4,6 +4,7 @@
package setting
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -26,12 +27,15 @@ MINIO_BUCKET = gitea-storage
assert.NoError(t, loadAttachmentFrom(cfg))
assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
assert.NoError(t, loadAvatarsFrom(cfg))
assert.EqualValues(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "avatars/", Avatar.Storage.MinioConfig.BasePath)
}
func Test_getStorageUseOtherNameAsType(t *testing.T) {
@@ -48,9 +52,11 @@ MINIO_BUCKET = gitea-storage
assert.NoError(t, loadAttachmentFrom(cfg))
assert.EqualValues(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
}
func Test_getStorageInheritStorageType(t *testing.T) {
@@ -90,3 +96,319 @@ STORAGE_TYPE = minio
assert.EqualValues(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "repo-avatars/", RepoAvatar.Storage.MinioConfig.BasePath)
}
type testLocalStoragePathCase struct {
loader func(rootCfg ConfigProvider) error
storagePtr **Storage
expectedPath string
}
func testLocalStoragePath(t *testing.T, appDataPath, iniStr string, cases []testLocalStoragePathCase) {
cfg, err := NewConfigProviderFromData(iniStr)
assert.NoError(t, err)
AppDataPath = appDataPath
for _, c := range cases {
assert.NoError(t, c.loader(cfg))
storage := *c.storagePtr
assert.EqualValues(t, "local", storage.Type)
assert.True(t, filepath.IsAbs(storage.Path))
assert.EqualValues(t, filepath.Clean(c.expectedPath), filepath.Clean(storage.Path))
}
}
func Test_getStorageInheritStorageTypeLocal(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage]
STORAGE_TYPE = local
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/repo-archive"},
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPath(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"},
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalRelativePath(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage]
STORAGE_TYPE = local
PATH = storages
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/appdata/storages/attachments"},
{loadLFSFrom, &LFS.Storage, "/appdata/storages/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/storages/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/appdata/storages/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/storages/repo-archive"},
{loadActionsFrom, &Actions.LogStorage, "/appdata/storages/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/appdata/storages/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/storages/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPathOverride(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
[repo-archive]
PATH = /data/gitea/the-archives-dir
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"},
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPathOverrideEmpty(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
[repo-archive]
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"},
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalRelativePathOverride(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage]
STORAGE_TYPE = local
PATH = /data/gitea
[repo-archive]
PATH = the-archives-dir
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"},
{loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"},
{loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPathOverride3(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = /data/gitea/archives
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"},
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPathOverride3_5(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = a-relative-path
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/a-relative-path"},
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPathOverride4(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = /data/gitea/archives
[repo-archive]
PATH = /tmp/gitea/archives
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/tmp/gitea/archives"},
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPathOverride5(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage.repo-archive]
STORAGE_TYPE = local
PATH = /data/gitea/archives
[repo-archive]
`, []testLocalStoragePathCase{
{loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"},
{loadLFSFrom, &LFS.Storage, "/appdata/lfs"},
{loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"},
{loadPackagesFrom, &Packages.Storage, "/appdata/packages"},
{loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"},
{loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"},
{loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"},
{loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"},
})
}
func Test_getStorageInheritStorageTypeLocalPathOverride72(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[repo-archive]
STORAGE_TYPE = local
PATH = archives
`, []testLocalStoragePathCase{
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/archives"},
})
}
func Test_getStorageConfiguration20(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[repo-archive]
STORAGE_TYPE = my_storage
PATH = archives
`)
assert.NoError(t, err)
assert.Error(t, loadRepoArchiveFrom(cfg))
}
func Test_getStorageConfiguration21(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage.repo-archive]
`, []testLocalStoragePathCase{
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/repo-archive"},
})
}
func Test_getStorageConfiguration22(t *testing.T) {
testLocalStoragePath(t, "/appdata", `
[storage.repo-archive]
PATH = archives
`, []testLocalStoragePathCase{
{loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/archives"},
})
}
func Test_getStorageConfiguration23(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[repo-archive]
STORAGE_TYPE = minio
MINIO_ACCESS_KEY_ID = my_access_key
MINIO_SECRET_ACCESS_KEY = my_secret_key
`)
assert.NoError(t, err)
_, err = getStorage(cfg, "", "", nil)
assert.Error(t, err)
assert.NoError(t, loadRepoArchiveFrom(cfg))
cp := RepoArchive.Storage.ToShadowCopy()
assert.EqualValues(t, "******", cp.MinioConfig.AccessKeyID)
assert.EqualValues(t, "******", cp.MinioConfig.SecretAccessKey)
}
func Test_getStorageConfiguration24(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[repo-archive]
STORAGE_TYPE = my_archive
[storage.my_archive]
; unsupported, storage type should be defined explicitly
PATH = archives
`)
assert.NoError(t, err)
assert.Error(t, loadRepoArchiveFrom(cfg))
}
func Test_getStorageConfiguration25(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[repo-archive]
STORAGE_TYPE = my_archive
[storage.my_archive]
; unsupported, storage type should be known type
STORAGE_TYPE = unknown // should be local or minio
PATH = archives
`)
assert.NoError(t, err)
assert.Error(t, loadRepoArchiveFrom(cfg))
}
func Test_getStorageConfiguration26(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[repo-archive]
STORAGE_TYPE = minio
MINIO_ACCESS_KEY_ID = my_access_key
MINIO_SECRET_ACCESS_KEY = my_secret_key
; wrong configuration
MINIO_USE_SSL = abc
`)
assert.NoError(t, err)
// assert.Error(t, loadRepoArchiveFrom(cfg))
// FIXME: this should return error but now ini package's MapTo() doesn't check type
assert.NoError(t, loadRepoArchiveFrom(cfg))
}
func Test_getStorageConfiguration27(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[storage.repo-archive]
STORAGE_TYPE = minio
MINIO_ACCESS_KEY_ID = my_access_key
MINIO_SECRET_ACCESS_KEY = my_secret_key
MINIO_USE_SSL = true
`)
assert.NoError(t, err)
assert.NoError(t, loadRepoArchiveFrom(cfg))
assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey)
assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL)
assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
}
+7 -2
View File
@@ -17,6 +17,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
@@ -164,6 +165,10 @@ func sessionHandler(session ssh.Session) {
}
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
// FIXME: the "ssh.Context" is not thread-safe, so db operations should use the immutable parent "Context"
// TODO: Remove after https://github.com/gliderlabs/ssh/pull/211
parentCtx := reflect.ValueOf(ctx).Elem().FieldByName("Context").Interface().(context.Context)
if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
}
@@ -189,7 +194,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
// look for the exact principal
principalLoop:
for _, principal := range cert.ValidPrincipals {
pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal)
pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal)
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal)
@@ -246,7 +251,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
}
pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
pkey, err := asymkey_model.SearchPublicKeyByContent(parentCtx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
+11 -6
View File
@@ -84,17 +84,22 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
Secure: config.UseSSL,
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
Region: config.Location,
})
if err != nil {
return nil, convertMinioErr(err)
}
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{
Region: config.Location,
}); err != nil {
// Check to see if we already own this bucket (which happens if you run this twice)
exists, errBucketExists := minioClient.BucketExists(ctx, config.Bucket)
if !exists || errBucketExists != nil {
// Check to see if we already own this bucket
exists, errBucketExists := minioClient.BucketExists(ctx, config.Bucket)
if errBucketExists != nil {
return nil, convertMinioErr(err)
}
if !exists {
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{
Region: config.Location,
}); err != nil {
return nil, convertMinioErr(err)
}
}
+6
View File
@@ -16,3 +16,9 @@ func RedirectURL(resp http.ResponseWriter) string {
func IsNormalPageCompleted(s string) bool {
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
}
func MockVariableValue[T any](p *T, v T) (reset func()) {
old := *p
*p = v
return func() { *p = old }
}
+12 -1
View File
@@ -71,7 +71,7 @@ func (ct SniffedType) IsRepresentableAsText() bool {
return ct.IsText() || ct.IsSvgImage()
}
// IsBrowsableType returns whether a non-text type can be displayed in a browser
// IsBrowsableBinaryType returns whether a non-text type can be displayed in a browser
func (ct SniffedType) IsBrowsableBinaryType() bool {
return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio()
}
@@ -116,6 +116,17 @@ func DetectContentType(data []byte) SniffedType {
}
}
if ct == "application/ogg" {
dataHead := data
if len(dataHead) > 256 {
dataHead = dataHead[:256] // only need to do a quick check for the file header
}
if bytes.Contains(dataHead, []byte("theora")) || bytes.Contains(dataHead, []byte("dirac")) {
ct = "video/ogg" // ogg is only used for some video formats, and it's not popular
} else {
ct = "audio/ogg" // for most cases, it is used as an audio container
}
}
return SniffedType{ct}
}
+13
View File
@@ -6,6 +6,7 @@ package typesniffer
import (
"bytes"
"encoding/base64"
"encoding/hex"
"strings"
"testing"
@@ -121,3 +122,15 @@ func TestDetectContentTypeFromReader(t *testing.T) {
assert.NoError(t, err)
assert.True(t, st.IsAudio())
}
func TestDetectContentTypeOgg(t *testing.T) {
oggAudio, _ := hex.DecodeString("4f67675300020000000000000000352f0000000000007dc39163011e01766f72626973000000000244ac0000000000000071020000000000b8014f6767530000")
st, err := DetectContentTypeFromReader(bytes.NewReader(oggAudio))
assert.NoError(t, err)
assert.True(t, st.IsAudio())
oggVideo, _ := hex.DecodeString("4f676753000200000000000000007d9747ef000000009b59daf3012a807468656f7261030201001e00110001e000010e00020000001e00000001000001000001")
st, err = DetectContentTypeFromReader(bytes.NewReader(oggVideo))
assert.NoError(t, err)
assert.True(t, st.IsVideo())
}
+11 -1
View File
@@ -2284,6 +2284,7 @@ settings.tags.protection.none = There are no protected tags.
settings.tags.protection.pattern.description = You can use a single name or a glob pattern or regular expression to match multiple tags. Read more in the <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/protected-tags/">protected tags guide</a>.
settings.bot_token = Bot Token
settings.chat_id = Chat ID
settings.thread_id = Thread ID
settings.matrix.homeserver_url = Homeserver URL
settings.matrix.room_id = Room ID
settings.matrix.message_type = Message Type
@@ -2484,7 +2485,7 @@ tag.create_success = Tag "%s" has been created.
topic.manage_topics = Manage Topics
topic.done = Done
topic.count_prompt = You cannot select more than 25 topics
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
find_file.go_to_file = Go to file
find_file.no_matching = No matching file found
@@ -3474,3 +3475,12 @@ need_approval_desc = Need approval to run workflows for fork pull request.
type-1.display_name = Individual Project
type-2.display_name = Repository Project
type-3.display_name = Organization Project
[git.filemode]
changed_filemode = %[1]s → %[2]s
# Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", …
directory = Directory
normal_file = Normal file
executable_file = Executable file
symbolic_link = Symbolic link
submodule = Submodule
+11 -2
View File
@@ -9,6 +9,9 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"golang.org/x/text/collate"
"golang.org/x/text/language"
)
// https://docs.microsoft.com/en-us/nuget/api/service-index#resources
@@ -207,9 +210,15 @@ func createSearchResultResponse(l *linkBuilder, totalHits int64, pds []*packages
grouped[pd.Package.Name] = append(grouped[pd.Package.Name], pd)
}
keys := make([]string, 0, len(grouped))
for key := range grouped {
keys = append(keys, key)
}
collate.New(language.English, collate.IgnoreCase).SortStrings(keys)
data := make([]*SearchResult, 0, len(pds))
for _, group := range grouped {
data = append(data, createSearchResult(l, group))
for _, key := range keys {
data = append(data, createSearchResult(l, grouped[key]))
}
return &SearchResultResponse{
+4 -5
View File
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
nuget_model "code.gitea.io/gitea/models/packages/nuget"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -115,7 +116,7 @@ func SearchServiceV2(ctx *context.Context) {
skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
paginator := db.NewAbsoluteListOptions(skip, take)
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
@@ -166,9 +167,8 @@ func SearchServiceV2(ctx *context.Context) {
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
func SearchServiceV2Count(ctx *context.Context) {
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
@@ -184,9 +184,8 @@ func SearchServiceV2Count(ctx *context.Context) {
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
func SearchServiceV3(ctx *context.Context) {
pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
IsInternal: util.OptionalBoolFalse,
Paginator: db.NewAbsoluteListOptions(
+4 -4
View File
@@ -127,7 +127,7 @@ func EditWikiPage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
oldWikiName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(newWikiName) == 0 {
@@ -231,7 +231,7 @@ func DeleteWikiPage(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
wikiName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
if err.Error() == "file does not exist" {
@@ -359,7 +359,7 @@ func GetWikiPage(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
wikiPage := getWikiPage(ctx, pageName)
if !ctx.Written() {
@@ -409,7 +409,7 @@ func ListPageRevisions(ctx *context.APIContext) {
}
// get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params(":pageName"))
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
if len(pageName) == 0 {
pageName = "Home"
}
+1 -1
View File
@@ -16,7 +16,7 @@ func Queues(ctx *context.Context) {
if !setting.IsProd {
initTestQueueOnce()
}
ctx.Data["Title"] = ctx.Tr("admin.monitor.queue")
ctx.Data["Title"] = ctx.Tr("admin.monitor.queues")
ctx.Data["PageIsAdminMonitorQueue"] = true
ctx.Data["Queues"] = queue.GetManager().ManagedQueues()
ctx.HTML(http.StatusOK, tplQueue)
+1 -1
View File
@@ -339,7 +339,7 @@ func Diff(ctx *context.Context) {
ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{})
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{ListAll: true})
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
+6 -2
View File
@@ -370,7 +370,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
}
if ctx.Repo.Repository.IsEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
}
}
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
@@ -763,7 +765,9 @@ func UploadFilePost(ctx *context.Context) {
}
if ctx.Repo.Repository.IsEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
}
}
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
+1 -1
View File
@@ -2226,7 +2226,7 @@ func UpdateIssueAssignee(ctx *context.Context) {
return
}
_, _, err = issue_service.ToggleAssignee(ctx, issue, ctx.Doer, assigneeID)
_, _, err = issue_service.ToggleAssigneeWithNotify(ctx, issue, ctx.Doer, assigneeID)
if err != nil {
ctx.ServerError("ToggleAssignee", err)
return
+3 -3
View File
@@ -421,7 +421,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
if len(compareInfo.Commits) != 0 {
sha := compareInfo.Commits[0].ID.String()
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptions{})
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptions{ListAll: true})
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
@@ -483,7 +483,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
return nil
}
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true})
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
@@ -575,7 +575,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return nil
}
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true})
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
+1 -1
View File
@@ -834,7 +834,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
ctx.Data["LatestCommitVerification"] = verification
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit)
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptions{})
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptions{ListAll: true})
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
+2 -1
View File
@@ -425,12 +425,13 @@ func telegramHookParams(ctx *context.Context) webhookParams {
return webhookParams{
Type: webhook_module.TELEGRAM,
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)),
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)),
ContentType: webhook.ContentTypeJSON,
WebhookForm: form.WebhookForm,
Meta: &webhook_service.TelegramMeta{
BotToken: form.BotToken,
ChatID: form.ChatID,
ThreadID: form.ThreadID,
},
}
}
+6 -6
View File
@@ -186,7 +186,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
ctx.Data["Pages"] = pages
// get requested page name
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 {
pageName = "Home"
}
@@ -333,7 +333,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
}
// get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 {
pageName = "Home"
}
@@ -416,7 +416,7 @@ func renderEditPage(ctx *context.Context) {
}()
// get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params("*"))
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 {
pageName = "Home"
}
@@ -648,7 +648,7 @@ func WikiRaw(ctx *context.Context) {
return
}
providedWebPath := wiki_service.WebPathFromRequest(ctx.Params("*"))
providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
providedGitPath := wiki_service.WebPathToGitPath(providedWebPath)
var entry *git.TreeEntry
if commit != nil {
@@ -760,7 +760,7 @@ func EditWikiPost(ctx *context.Context) {
return
}
oldWikiName := wiki_service.WebPathFromRequest(ctx.Params("*"))
oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(form.Message) == 0 {
@@ -779,7 +779,7 @@ func EditWikiPost(ctx *context.Context) {
// DeleteWikiPagePost delete wiki page
func DeleteWikiPagePost(ctx *context.Context) {
wikiName := wiki_service.WebPathFromRequest(ctx.Params("*"))
wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(wikiName) == 0 {
wikiName = "Home"
}
+1 -1
View File
@@ -53,7 +53,7 @@ func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) {
ctx.Data["Runners"] = runners
ctx.Data["Total"] = count
ctx.Data["RegistrationToken"] = token.Token
ctx.Data["RunnerOnwerID"] = opts.OwnerID
ctx.Data["RunnerOwnerID"] = opts.OwnerID
ctx.Data["RunnerRepoID"] = opts.RepoID
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
+1 -1
View File
@@ -695,7 +695,7 @@ func getRepoIDs(reposQuery string) []int64 {
return []int64{}
}
if !issueReposQueryPattern.MatchString(reposQuery) {
log.Warn("issueReposQueryPattern does not match query")
log.Warn("issueReposQueryPattern does not match query: %q", reposQuery)
return []int64{}
}
+1
View File
@@ -115,6 +115,7 @@ func Profile(ctx *context.Context) {
profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: gitRepo,
Metas: map[string]string{"mode": "document"},
}, bytes)
if err != nil {
ctx.ServerError("RenderString", err)
+8 -7
View File
@@ -262,9 +262,10 @@ func registerRoutes(m *web.Route) {
}
}
reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode) func(ctx *context.Context) {
reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode, ignoreGlobal bool) func(ctx *context.Context) {
return func(ctx *context.Context) {
if unitType.UnitGlobalDisabled() {
// only check global disabled units when ignoreGlobal is false
if !ignoreGlobal && unitType.UnitGlobalDisabled() {
ctx.NotFound(unitType.String(), nil)
return
}
@@ -828,7 +829,7 @@ func registerRoutes(m *web.Route) {
m.Group("", func() {
m.Get("", org.Projects)
m.Get("/{id}", org.ViewProject)
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead))
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
m.Group("", func() { //nolint:dupl
m.Get("/new", org.RenderNewProject)
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
@@ -849,17 +850,17 @@ func registerRoutes(m *web.Route) {
m.Post("/move", org.MoveIssues)
})
})
}, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite), func(ctx *context.Context) {
}, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), func(ctx *context.Context) {
if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID {
ctx.NotFound("NewProject", nil)
return
}
})
}, repo.MustEnableProjects)
})
m.Group("", func() {
m.Get("/code", user.CodeSearch)
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead))
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
// ***** Release Attachment Download without Signin
@@ -1118,7 +1119,7 @@ func registerRoutes(m *web.Route) {
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true))
m.Get("/releases/attachments/{uuid}", repo.GetAttachment, repo.MustBeNotEmpty, reqRepoReleaseReader)
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, reqRepoReleaseReader, repo.GetAttachment)
m.Group("/releases", func() {
m.Get("/new", repo.NewRelease)
m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)
+1 -1
View File
@@ -75,7 +75,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
}
ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
state := toCommitStatus(job.Status)
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil {
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true}); err == nil {
for _, v := range statuses {
if v.Context == ctxname {
if v.State == state {
+1 -1
View File
@@ -56,7 +56,7 @@ func (p *AuthSourceProvider) DisplayName() string {
func (p *AuthSourceProvider) IconHTML() template.HTML {
if p.iconURL != "" {
img := fmt.Sprintf(`<img class="gt-mr-3" width="20" height="20" src="%s" alt="%s">`,
img := fmt.Sprintf(`<img class="gt-object-contain gt-mr-3" width="20" height="20" src="%s" alt="%s">`,
html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
)
return template.HTML(img)
+1 -1
View File
@@ -51,7 +51,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
ID: user.ID,
UserName: user.Name,
FullName: user.FullName,
Email: user.GetEmail(),
Email: user.GetPlaceholderEmail(),
AvatarURL: user.AvatarLink(ctx),
Created: user.CreatedUnix.AsTime(),
Restricted: user.IsRestricted,
+1
View File
@@ -352,6 +352,7 @@ func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) b
type NewTelegramHookForm struct {
BotToken string `binding:"Required"`
ChatID string `binding:"Required"`
ThreadID string
WebhookForm
}
+17
View File
@@ -427,6 +427,23 @@ func (diffFile *DiffFile) ShouldBeHidden() bool {
return diffFile.IsGenerated || diffFile.IsViewed
}
func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
switch mode {
case "040000":
return "git.filemode.directory"
case "100644":
return "git.filemode.normal_file"
case "100755":
return "git.filemode.executable_file"
case "120000":
return "git.filemode.symbolic_link"
case "160000":
return "git.filemode.submodule"
default:
return mode
}
}
func getCommitFileLineCount(commit *git.Commit, filePath string) int {
blob, err := commit.GetBlobByPath(filePath)
if err != nil {
+7 -7
View File
@@ -33,7 +33,7 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe
if !found {
// This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here
if _, _, err := ToggleAssignee(ctx, issue, doer, assignee.ID); err != nil {
if _, _, err := ToggleAssigneeWithNotify(ctx, issue, doer, assignee.ID); err != nil {
return err
}
}
@@ -42,8 +42,8 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe
return nil
}
// ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
// ToggleAssigneeWithNoNotify changes a user between assigned and not assigned for this issue, and make issue comment for it.
func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
if err != nil {
return
@@ -63,9 +63,9 @@ func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_m
// ReviewRequest add or remove a review request from a user for this PR, and make comment for it.
func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) {
if isAdd {
comment, err = issues_model.AddReviewRequest(issue, reviewer, doer)
comment, err = issues_model.AddReviewRequest(ctx, issue, reviewer, doer)
} else {
comment, err = issues_model.RemoveReviewRequest(issue, reviewer, doer)
comment, err = issues_model.RemoveReviewRequest(ctx, issue, reviewer, doer)
}
if err != nil {
@@ -230,9 +230,9 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team,
// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it.
func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) {
if isAdd {
comment, err = issues_model.AddTeamReviewRequest(issue, reviewer, doer)
comment, err = issues_model.AddTeamReviewRequest(ctx, issue, reviewer, doer)
} else {
comment, err = issues_model.RemoveTeamReviewRequest(issue, reviewer, doer)
comment, err = issues_model.RemoveTeamReviewRequest(ctx, issue, reviewer, doer)
}
if err != nil {
+14 -14
View File
@@ -27,7 +27,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
}
for _, assigneeID := range assigneeIDs {
if err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID); err != nil {
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
return err
}
}
@@ -122,7 +122,7 @@ func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee
// has access to the repo.
for _, assignee := range allNewAssignees {
// Extra method to prevent double adding (which would result in removing)
err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID)
_, err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID, true)
if err != nil {
return err
}
@@ -167,36 +167,36 @@ func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Reposi
// AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue.
// Also checks for access of assigned user
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) {
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64, notify bool) (comment *issues_model.Comment, err error) {
assignee, err := user_model.GetUserByID(ctx, assigneeID)
if err != nil {
return err
return nil, err
}
// Check if the user is already assigned
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee)
if err != nil {
return err
return nil, err
}
if isAssigned {
// nothing to to
return nil
return nil, nil
}
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
if err != nil {
return err
return nil, err
}
if !valid {
return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
}
_, _, err = ToggleAssignee(ctx, issue, doer, assigneeID)
if err != nil {
return err
if notify {
_, comment, err = ToggleAssigneeWithNotify(ctx, issue, doer, assigneeID)
return comment, err
}
return nil
_, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
return comment, err
}
// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name)
@@ -242,7 +242,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
issue.MilestoneID, err)
}
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID); err != nil {
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID, issue.Index); err != nil {
return err
}
+1 -1
View File
@@ -212,7 +212,7 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa
}
addSeparator = true
fmt.Fprint(w, pfd.Properties.GetByName(debian_module.PropertyControl))
fmt.Fprintf(w, "%s\n", strings.TrimSpace(pfd.Properties.GetByName(debian_module.PropertyControl)))
fmt.Fprintf(w, "Filename: pool/%s/%s/%s\n", distribution, component, pfd.File.Name)
fmt.Fprintf(w, "Size: %d\n", pfd.Blob.Size)
+1 -1
View File
@@ -143,7 +143,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
return "", errors.Wrap(err, "LoadBaseRepo")
}
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{})
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true})
if err != nil {
return "", errors.Wrap(err, "GetLatestCommitStatus")
}
+7 -2
View File
@@ -62,14 +62,19 @@ func TestPatch(pr *issues_model.PullRequest) error {
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("TestPatch: %s", pr))
defer finished()
// Clone base repo.
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
if err != nil {
log.Error("createTemporaryRepoForPR %-v: %v", pr, err)
if !models.IsErrBranchDoesNotExist(err) {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
}
return err
}
defer cancel()
return testPatch(ctx, prCtx, pr)
}
func testPatch(ctx context.Context, prCtx *prContext, pr *issues_model.PullRequest) error {
gitRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath)
if err != nil {
return fmt.Errorf("OpenRepository: %w", err)
+83 -54
View File
@@ -23,7 +23,6 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/process"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
@@ -35,73 +34,70 @@ import (
var pullWorkingPool = sync.NewExclusivePool()
// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
if err := TestPatch(pr); err != nil {
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
if err != nil {
if !models.IsErrBranchDoesNotExist(err) {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
}
return err
}
defer cancel()
if err := testPatch(ctx, prCtx, pr); err != nil {
return err
}
divergence, err := GetDiverging(ctx, pr)
divergence, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
if err != nil {
return err
}
pr.CommitsAhead = divergence.Ahead
pr.CommitsBehind = divergence.Behind
if err := issues_model.NewPullRequest(ctx, repo, pull, labelIDs, uuids, pr); err != nil {
return err
}
for _, assigneeID := range assigneeIDs {
if err := issue_service.AddAssigneeIfNotAssigned(ctx, pull, pull.Poster, assigneeID); err != nil {
return err
}
}
pr.Issue = pull
pull.PullRequest = pr
// Now - even if the request context has been cancelled as the PR has been created
// in the db and there is no way to cancel that transaction we have to proceed - therefore
// create new context and work from there
prCtx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("NewPullRequest: %s:%d", repo.FullName(), pr.Index))
defer finished()
if pr.Flow == issues_model.PullRequestFlowGithub {
err = PushToBaseRepo(prCtx, pr)
} else {
err = UpdateRef(prCtx, pr)
}
if err != nil {
return err
}
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, pull, pull.Poster, pull.Content)
if err != nil {
return err
}
notification.NotifyNewPullRequest(prCtx, pr, mentions)
if len(pull.Labels) > 0 {
notification.NotifyIssueChangeLabels(prCtx, pull.Poster, pull, pull.Labels, nil)
}
if pull.Milestone != nil {
notification.NotifyIssueChangeMilestone(prCtx, pull.Poster, pull, 0)
}
assigneeCommentMap := make(map[int64]*issues_model.Comment)
// add first push codes comment
baseGitRepo, err := git.OpenRepository(prCtx, pr.BaseRepo.RepoPath())
baseGitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
return err
}
defer baseGitRepo.Close()
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
if err != nil {
return err
}
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
return err
}
for _, assigneeID := range assigneeIDs {
comment, err := issue_service.AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, false)
if err != nil {
return err
}
assigneeCommentMap[assigneeID] = comment
}
pr.Issue = issue
issue.PullRequest = pr
if pr.Flow == issues_model.PullRequestFlowGithub {
err = PushToBaseRepo(ctx, pr)
} else {
err = UpdateRef(ctx, pr)
}
if err != nil {
return err
}
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
if err != nil {
return err
}
if len(compareInfo.Commits) == 0 {
return nil
}
if len(compareInfo.Commits) > 0 {
data := issues_model.PushActionContent{IsForcePush: false}
data.CommitIDs = make([]string, 0, len(compareInfo.Commits))
for i := len(compareInfo.Commits) - 1; i >= 0; i-- {
@@ -115,14 +111,47 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu
ops := &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypePullRequestPush,
Doer: pull.Poster,
Doer: issue.Poster,
Repo: repo,
Issue: pr.Issue,
IsForcePush: false,
Content: string(dataJSON),
}
_, _ = issue_service.CreateComment(ctx, ops)
if _, err = issues_model.CreateComment(ctx, ops); err != nil {
return err
}
return nil
}); err != nil {
// cleanup: this will only remove the reference, the real commit will be clean up when next GC
if err1 := baseGitRepo.RemoveReference(pr.GetGitRefName()); err1 != nil {
log.Error("RemoveReference: %v", err1)
}
return err
}
baseGitRepo.Close() // close immediately to avoid notifications will open the repository again
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
if err != nil {
return err
}
notification.NotifyNewPullRequest(ctx, pr, mentions)
if len(issue.Labels) > 0 {
notification.NotifyIssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil)
}
if issue.Milestone != nil {
notification.NotifyIssueChangeMilestone(ctx, issue.Poster, issue, 0)
}
if len(assigneeIDs) > 0 {
for _, assigneeID := range assigneeIDs {
assignee, err := user_model.GetUserByID(ctx, assigneeID)
if err != nil {
return ErrDependenciesLeft
}
notification.NotifyIssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID])
}
}
return nil
@@ -794,7 +823,7 @@ func getAllCommitStatus(gitRepo *git.Repository, pr *issues_model.PullRequest) (
return nil, nil, shaErr
}
statuses, _, err = git_model.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{})
statuses, _, err = git_model.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true})
lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err
}
+3 -1
View File
@@ -340,7 +340,9 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
}
if repo.IsEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
if isEmpty, err := gitRepo.IsEmpty(); err == nil && !isEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
}
}
return filesResponse, nil
+4
View File
@@ -131,6 +131,10 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
case api.HookIssueReviewed:
text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink)
attachmentText = p.Review.Content
case api.HookIssueReviewRequested:
text = fmt.Sprintf("[%s] Pull request review requested: %s", repoLink, titleLink)
case api.HookIssueReviewRequestRemoved:
text = fmt.Sprintf("[%s] Pull request review request removed: %s", repoLink, titleLink)
}
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
+1
View File
@@ -28,6 +28,7 @@ type (
TelegramMeta struct {
BotToken string `json:"bot_token"`
ChatID string `json:"chat_id"`
ThreadID string `json:"thread_id"`
}
)
+6 -1
View File
@@ -5,6 +5,7 @@ package webhook
import (
"context"
"errors"
"fmt"
"strings"
@@ -111,7 +112,11 @@ func handler(items ...int64) []int64 {
for _, taskID := range items {
task, err := webhook_model.GetHookTaskByID(ctx, taskID)
if err != nil {
log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
if errors.Is(err, util.ErrNotExist) {
log.Warn("GetHookTaskByID[%d] warn: %v", taskID, err)
} else {
log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
}
continue
}
+10 -6
View File
@@ -22,15 +22,16 @@ import (
// - "/wiki/100%25+Free"
// - "/wiki/2000-01-02+meeting.-"
// - If a segment has a suffix "DashMarker(.-)", it means that there is no dash-space conversion for this segment.
// - If a WebPath is a "*.md" pattern, then use it directly as GitPath, to make users can access the raw file.
// - If a WebPath is a "*.md" pattern, then use the unescaped value directly as GitPath, to make users can access the raw file.
// * Git Path (only space doesn't need to be escaped):
// - "/.wiki.git/Home-Page.md"
// - "/.wiki.git/100%25 Free.md"
// - "/.wiki.git/2000-01-02 meeting.-.md"
// TODO: support subdirectory in the future
//
// Although this package now has the ablity to support subdirectory, but the route package doesn't:
// Although this package now has the ability to support subdirectory, but the route package doesn't:
// * Double-escaping problem: the URL "/wiki/abc%2Fdef" becomes "/wiki/abc/def" by ctx.Params, which is incorrect
// * This problem should have been 99% fixed, but it needs more tests.
// * The old wiki code's behavior is always using %2F, instead of subdirectory, so there are a lot of legacy "%2F" files in user wikis.
type WebPath string
@@ -91,7 +92,8 @@ func WebPathSegments(s WebPath) []string {
func WebPathToGitPath(s WebPath) string {
if strings.HasSuffix(string(s), ".md") {
return string(s)
ret, _ := url.PathUnescape(string(s))
return util.PathJoinRelX(ret)
}
a := strings.Split(string(s), "/")
@@ -124,7 +126,10 @@ func GitPathToWebPath(s string) (wp WebPath, err error) {
func WebPathToUserTitle(s WebPath) (dir, display string) {
dir = path.Dir(string(s))
display = path.Base(string(s))
display = strings.TrimSuffix(display, ".md")
if strings.HasSuffix(display, ".md") {
display = strings.TrimSuffix(display, ".md")
display, _ = url.PathUnescape(display)
}
display, _ = unescapeSegment(display)
return dir, display
}
@@ -141,8 +146,7 @@ func WebPathFromRequest(s string) WebPath {
}
func UserTitleToWebPath(base, title string) WebPath {
// TODO: ctx.Params does un-escaping, so the URL "/wiki/abc%2Fdef" becomes "wiki path = `abc/def`", which is incorrect.
// And the old wiki code's behavior is always using %2F, instead of subdirectory.
// TODO: no support for subdirectory, because the old wiki code's behavior is always using %2F, instead of subdirectory.
// So we do not add the support for writing slashes in title at the moment.
title = strings.TrimSpace(title)
title = util.PathJoinRelX(base, escapeSegToWeb(title, false))
+3 -1
View File
@@ -59,6 +59,7 @@ func TestWebPathToDisplayName(t *testing.T) {
{"name with / slash", "name-with %2F slash"},
{"name with % percent", "name-with %25 percent"},
{"2000-01-02 meeting", "2000-01-02+meeting.-.md"},
{"a b", "a%20b.md"},
} {
_, displayName := WebPathToUserTitle(test.WebPath)
assert.EqualValues(t, test.Expected, displayName)
@@ -73,7 +74,8 @@ func TestWebPathToGitPath(t *testing.T) {
for _, test := range []test{
{"wiki-name.md", "wiki%20name"},
{"wiki-name.md", "wiki+name"},
{"wiki%20name.md", "wiki%20name.md"},
{"wiki name.md", "wiki%20name.md"},
{"wiki%20name.md", "wiki%2520name.md"},
{"2000-01-02-meeting.md", "2000-01-02+meeting"},
{"2000-01-02 meeting.-.md", "2000-01-02%20meeting.-"},
} {
+1 -23
View File
@@ -11,29 +11,7 @@
{{.locale.Tr "admin.defaulthooks.update_webhook"}}
{{end}}
<div class="ui right">
{{if eq .HookType "gitea"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg">
{{else if eq .HookType "gogs"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico">
{{else if eq .HookType "slack"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png">
{{else if eq .HookType "discord"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png">
{{else if eq .HookType "dingtalk"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico">
{{else if eq .HookType "telegram"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png">
{{else if eq .HookType "msteams"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png">
{{else if eq .HookType "feishu"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png">
{{else if eq .HookType "matrix"}}
{{svg "gitea-matrix" 26}}
{{else if eq .HookType "wechatwork"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png">
{{else if eq .HookType "packagist"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png">
{{end}}
{{template "shared/webhook/icon" .}}
</div>
</h4>
<div class="ui attached segment">
+10 -10
View File
@@ -6,18 +6,18 @@
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<a class="{{if eq .SortType "newest"}}active {{end}}item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
<a class="{{if eq .SortType "newest"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=newest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=oldest&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=alphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=reversealphabetically&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=recentupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=leastupdate&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
{{if not .DisableStars}}
<a class="{{if eq .SortType "moststars"}}active {{end}}item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.moststars"}}</a>
<a class="{{if eq .SortType "feweststars"}}active {{end}}item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.feweststars"}}</a>
<a class="{{if eq .SortType "moststars"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=moststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.moststars"}}</a>
<a class="{{if eq .SortType "feweststars"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=feweststars&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.feweststars"}}</a>
{{end}}
<a class="{{if eq .SortType "mostforks"}}active {{end}}item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.mostforks"}}</a>
<a class="{{if eq .SortType "fewestforks"}}active {{end}}item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.fewestforks"}}</a>
<a class="{{if eq .SortType "mostforks"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=mostforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.mostforks"}}</a>
<a class="{{if eq .SortType "fewestforks"}}active {{end}}item" href="{{$.Link}}?tab={{$.TabName}}&sort=fewestforks&q={{$.Keyword}}&language={{$.Language}}">{{.locale.Tr "repo.issues.filter_sort.fewestforks"}}</a>
</div>
</div>
</div>
+1 -23
View File
@@ -3,29 +3,7 @@
<h4 class="ui top attached header">
{{if .PageIsSettingsHooksNew}}{{.locale.Tr "repo.settings.add_webhook"}}{{else}}{{.locale.Tr "repo.settings.update_webhook"}}{{end}}
<div class="ui right">
{{if eq .HookType "gitea"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg">
{{else if eq .HookType "gogs"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico">
{{else if eq .HookType "slack"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png">
{{else if eq .HookType "discord"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png">
{{else if eq .HookType "dingtalk"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico">
{{else if eq .HookType "telegram"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png">
{{else if eq .HookType "msteams"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png">
{{else if eq .HookType "feishu"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png">
{{else if eq .HookType "matrix"}}
{{svg "gitea-matrix" 26}}
{{else if eq .HookType "wechatwork"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png">
{{else if eq .HookType "packagist"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png">
{{end}}
{{template "shared/webhook/icon" .}}
</div>
</h4>
<div class="ui attached segment">
+1 -1
View File
@@ -8,7 +8,7 @@
{{.locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}} {{if .RefName}}({{.RefName}}){{end}}
{{end}}
</div>
<div class="commits-table-right">
<div class="commits-table-right gt-whitespace-nowrap">
{{if .PageIsCommits}}
<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/search">
<div class="ui tiny search input">
+4 -2
View File
@@ -110,9 +110,11 @@
<span class="ui label">{{$.locale.Tr "repo.diff.vendored"}}</span>
{{end}}
{{if and $file.Mode $file.OldMode}}
<span class="gt-ml-4 gt-mono">{{$file.OldMode}} &rarr; {{$file.Mode}}</span>
{{$old := $.locale.Tr ($file.ModeTranslationKey $file.OldMode)}}
{{$new := $.locale.Tr ($file.ModeTranslationKey $file.Mode)}}
<span class="gt-ml-4 gt-mono">{{$.locale.Tr "git.filemode.changed_filemode" $old $new}}</span>
{{else if $file.Mode}}
<span class="gt-ml-4 gt-mono">{{$file.Mode}}</span>
<span class="gt-ml-4 gt-mono">{{$.locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span>
{{end}}
</div>
<div class="diff-file-header-actions gt-df gt-ac gt-gap-2 gt-fw">
+1
View File
@@ -17,6 +17,7 @@
"TextareaName" "content"
"TextareaPlaceholder" ($.locale.Tr "repo.diff.comment.placeholder")
"DropzoneParentContainer" "form"
"DisableAutosize" "true"
)}}
<div class="field footer gt-mx-3">
+2
View File
@@ -170,11 +170,13 @@
<span class="no-select item {{if .HasSelectedLabel}}gt-hidden{{end}}">
{{.locale.Tr "repo.issues.new.no_assignees"}}
</span>
<div class="selected">
{{range .Assignees}}
<a class="item gt-p-2 muted gt-hidden" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}">
{{avatar $.Context . 28 "gt-mr-3 gt-vm"}}{{.GetDisplayName}}
</a>
{{end}}
</div>
</div>
{{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
<div class="ui divider"></div>
@@ -362,7 +362,7 @@
<div class="gt-df gt-sb gt-ac">
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{.locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
{{svg "octicon-calendar" 16 "gt-mr-3"}}
{{DateTime "long" .Issue.DeadlineUnix}}
{{DateTime "long" .Issue.DeadlineUnix.FormatDate}}
</div>
<div>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
+1 -1
View File
@@ -1,6 +1,6 @@
<div class="four wide column">
<div class="ui fluid vertical menu">
<div class="header item">{{.locale.Tr "org.settings"}}</div>
<div class="header item">{{.locale.Tr "repo.settings"}}</div>
<a class="{{if .PageIsSettingsOptions}}active {{end}}item" href="{{.RepoLink}}/settings">
{{.locale.Tr "repo.settings.options"}}
</a>
+4 -2
View File
@@ -711,9 +711,11 @@
{{end}}
<h4 class="ui header">{{.locale.Tr "repo.settings.admin_stats_indexer"}}</h4>
<div class="inline fields">
<label>{{.locale.Tr "repo.settings.admin_indexer_commit_sha"}}</label>
{{if and .StatsIndexerStatus .StatsIndexerStatus.CommitSha}}
<label>{{.locale.Tr "repo.settings.admin_indexer_commit_sha"}}</label>
{{end}}
<span class="field">
{{if .StatsIndexerStatus}}
{{if and .StatsIndexerStatus .StatsIndexerStatus.CommitSha}}
<a rel="nofollow" class="ui sha label" href="{{.RepoLink}}/commit/{{.StatsIndexerStatus.CommitSha}}">
<span class="shortsha">{{ShortSha .StatsIndexerStatus.CommitSha}}</span>
</a>
+22 -11
View File
@@ -5,37 +5,48 @@
<div class="ui primary tiny button">{{.locale.Tr "repo.settings.add_webhook"}}</div>
<div class="menu">
<a class="item" href="{{.BaseLinkNew}}/gitea/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/gitea.svg">{{.locale.Tr "repo.settings.web_hook_name_gitea"}}
{{template "shared/webhook/icon" (dict "HookType" "gitea" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_gitea"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/gogs/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/gogs.ico">{{.locale.Tr "repo.settings.web_hook_name_gogs"}}
{{template "shared/webhook/icon" (dict "HookType" "gogs" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_gogs"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/slack/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/slack.png">{{.locale.Tr "repo.settings.web_hook_name_slack"}}
{{template "shared/webhook/icon" (dict "HookType" "slack" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_slack"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/discord/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/discord.png">{{.locale.Tr "repo.settings.web_hook_name_discord"}}
{{template "shared/webhook/icon" (dict "HookType" "discord" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_discord"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/dingtalk/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/dingtalk.ico">{{.locale.Tr "repo.settings.web_hook_name_dingtalk"}}
{{template "shared/webhook/icon" (dict "HookType" "dingtalk" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_dingtalk"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/telegram/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/telegram.png">{{.locale.Tr "repo.settings.web_hook_name_telegram"}}
{{template "shared/webhook/icon" (dict "HookType" "telegram" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_telegram"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/msteams/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/msteams.png">{{.locale.Tr "repo.settings.web_hook_name_msteams"}}
{{template "shared/webhook/icon" (dict "HookType" "msteams" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_msteams"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/feishu/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/feishu.png">{{.locale.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
{{template "shared/webhook/icon" (dict "HookType" "feishu" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/matrix/new">
{{svg "gitea-matrix" 20 "img"}}{{.locale.Tr "repo.settings.web_hook_name_matrix"}}
{{template "shared/webhook/icon" (dict "HookType" "matrix" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_matrix"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/wechatwork/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/wechatwork.png">{{.locale.Tr "repo.settings.web_hook_name_wechatwork"}}
{{template "shared/webhook/icon" (dict "HookType" "wechatwork" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_wechatwork"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/packagist/new">
<img width="20" height="20" src="{{AssetUrlPrefix}}/img/packagist.png">{{.locale.Tr "repo.settings.web_hook_name_packagist"}}
{{template "shared/webhook/icon" (dict "HookType" "packagist" "Size" 20)}}
{{.locale.Tr "repo.settings.web_hook_name_packagist"}}
</a>
</div>
</div>
+1 -23
View File
@@ -3,29 +3,7 @@
<h4 class="ui top attached header">
{{if .PageIsSettingsHooksNew}}{{.locale.Tr "repo.settings.add_webhook"}}{{else}}{{.locale.Tr "repo.settings.update_webhook"}}{{end}}
<div class="ui right">
{{if eq .HookType "gitea"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg">
{{else if eq .HookType "gogs"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico">
{{else if eq .HookType "slack"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png">
{{else if eq .HookType "discord"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png">
{{else if eq .HookType "dingtalk"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico">
{{else if eq .HookType "telegram"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png">
{{else if eq .HookType "msteams"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png">
{{else if eq .HookType "feishu"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png">
{{else if eq .HookType "matrix"}}
{{svg "gitea-matrix" 26}}
{{else if eq .HookType "wechatwork"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png">
{{else if eq .HookType "packagist"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png">
{{end}}
{{template "shared/webhook/icon" .}}
</div>
</h4>
<div class="ui attached segment">
@@ -10,6 +10,10 @@
<label for="chat_id">{{.locale.Tr "repo.settings.chat_id"}}</label>
<input id="chat_id" name="chat_id" type="text" value="{{.TelegramHook.ChatID}}" required>
</div>
<div class="field {{if .Err_ThreadID}}error{{end}}">
<label for="thread_id">{{.locale.Tr "repo.settings.thread_id"}}</label>
<input id="thread_id" name="thread_id" type="text" value="{{.TelegramHook.ThreadID}}">
</div>
{{template "repo/settings/webhook/settings" .}}
</form>
{{end}}
+1 -1
View File
@@ -71,7 +71,7 @@
</td>
<td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline $.locale}}{{else}}{{$.locale.Tr "never"}}{{end}}</td>
<td class="runner-ops">
{{if .Editable $.RunnerOnwerID $.RunnerRepoID}}
{{if .Editable $.RunnerOwnerID $.RunnerRepoID}}
<a href="{{$.Link}}/{{.ID}}">{{svg "octicon-pencil"}}</a>
{{end}}
</td>

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