Compare commits

..

90 commits

Author SHA1 Message Date
209d9f3507 Dockerfile: rename user to _gitea instead of git 2023-08-21 15:24:52 -03:00
John Olheiser
07531cf953
Set errwriter for urfave/cli v1 (#26616)
Resolves #26615

(cherry picked from commit 11711c51cb497477176ea68309d2d061820996da)
2023-08-21 07:27:20 +02:00
Giteabot
4f8ae2881c
Update 1.20.3 changelog (#26609) (#26610)
Backport #26609 by @delvh

Co-authored-by: delvh <dev.lh@web.de>
(cherry picked from commit bcb0f3a90fae49047f73b37fcb50b075ed641dfa)
2023-08-21 07:27:20 +02:00
Giteabot
b31c44894e
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>
(cherry picked from commit c99374b1510249e3f8a535cdecd6cf1d5637e0ef)
2023-08-21 07:27:20 +02:00
Giteabot
98820fe4f2
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>
(cherry picked from commit b643b2ca9c2e4402c1bc2362d095c53d34c07db6)
2023-08-21 07:27:20 +02:00
CaiCandong
28acd6e262
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)

(cherry picked from commit 94f86964b4e62a8c825e3debae9afb0c0301331d)
2023-08-21 07:27:20 +02:00
Giteabot
c8f437b316
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>
(cherry picked from commit 1f29cfa68313c982497d09ef1b3b0fc5e5c9acba)
2023-08-21 07:27:20 +02:00
Giteabot
563fc65e35
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>
(cherry picked from commit 1cedf36d301913cb36dbc38ffd987ada9efb4a77)
2023-08-21 07:27:20 +02:00
Giteabot
4ac522c8aa
Sync repo's IsEmpty status correctly (#26517) (#26560)
Backport #26517 by @wxiaoguang

Close #26509

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit 7da85fa0c3d7ddf4907ca74ef81ee532d768df87)
2023-08-21 07:27:20 +02:00
Giteabot
44658b7d2c
Fix typo of RunerOwnerID (#26508) (#26528)
Backport #26508 by @yp05327

Co-authored-by: yp05327 <576951401@qq.com>
(cherry picked from commit 0ac8b774e95cecd1050d7a759f3830b3198b63fd)
2023-08-21 07:27:20 +02:00
KN4CK3R
471138829b
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

(cherry picked from commit 762d4245fb22a927861d30c6314d81e27eb1a06a)
2023-08-21 07:27:20 +02:00
Giteabot
a7ecb5a8bf
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>
(cherry picked from commit 3571cbba343b7a249c408cc7e6fc4d4f7efffa2a)
2023-08-21 07:27:20 +02:00
Giteabot
682f613810
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>
(cherry picked from commit 6d60d4e554702815c1da484bc1fa2048b166e56d)
2023-08-21 07:27:20 +02:00
Giteabot
5289619383
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>
(cherry picked from commit 31208fe9a138ae29a114a28155e73b3e9b259ce3)
2023-08-21 07:27:20 +02:00
Giteabot
622ec5c79f
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>
(cherry picked from commit 4bdb8dd9cc028a071819afd8f79cf29b30ab4d30)
2023-08-21 07:26:43 +02:00
Giteabot
2f34f1ec21
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>
(cherry picked from commit 24d6aacc7ee4745799bd2f778c0e2e882099cfa3)
2023-08-21 07:26:36 +02:00
Giteabot
601df4d472
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>
(cherry picked from commit d1a55aabc944a7428880943319d67a227e266f22)
2023-08-21 07:26:31 +02:00
Giteabot
b683b93d16
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>
(cherry picked from commit f1c5d33d3e8ec3dc5988c130b6899cffe6b0e651)
2023-08-21 07:22:19 +02:00
Giteabot
d297a87f75
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>
(cherry picked from commit acc0fd22d87d6b0549ba624e4f1a760169b0624b)
2023-08-21 07:22:19 +02:00
Giteabot
f74522a352
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>
(cherry picked from commit fe1b11b639792b601ac5ba78c0378058032ec8a8)
2023-08-21 07:22:19 +02:00
Giteabot
0c8a96896f
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>
(cherry picked from commit 80d7288ea45ea69060eb41709f188c09b33ca266)
2023-08-21 07:22:19 +02:00
Giteabot
82e5247a43
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>
(cherry picked from commit 2d1202b32c1a975fe6cf7b139d1ab4ed292bcca1)
2023-08-21 07:22:19 +02:00
wxiaoguang
a12ea7a7cd
Avoiding accessing undefined tributeValues #26461 (#26462)
Backport #26461

(cherry picked from commit 9112ce22a400d66c82b429fae72e8637a34727c7)
2023-08-21 07:22:19 +02:00
Earl Warren
3e48942b1e
[TESTS] upgrade tests for storage
(cherry picked from commit 884ca63738cc2e2c7cde31c649e9fa77cd590044)
2023-08-21 07:22:19 +02:00
Earl Warren
6ab5735e90
[UPGRADE] add sanity checks for [storage*]
Refs: https://forgejo.org/2023-08-release-v1-20-3-0/
(cherry picked from commit a266dd0ce3fca1296c6713ff1266f0065f0cd72b)
2023-08-21 07:22:19 +02:00
Earl Warren
fe55c78fb1
[UPGRADE] run sanity checks before the database is upgraded
(cherry picked from commit 69741e4e66932a9ac092089e7ba27399c55dcd1a)
2023-08-21 07:22:19 +02:00
Earl Warren
1b568e284f
[GITEA] add GetFile to config provider
(cherry picked from commit 88d1b53eeaa0d5ad0ed54c191236db928aadedf0)
2023-08-21 07:22:19 +02:00
Earl Warren
03f33a0320
[SEMVER] store SemVer in ForgejoSemVer after a database upgrade
(cherry picked from commit b7fe7cf401f4bddd6455efc651f7ac054f3fe1cf)
2023-08-21 07:22:18 +02:00
Earl Warren
2605e121d4
[DB] forgejo migration v2: create the forgejo_sem_ver table
(cherry picked from commit 86b26436af85e0eedb732e115e8be024e1d54ca6)

Conflicts:
	models/forgejo_migrations/migrate.go
	trivial context conflict
2023-08-21 07:22:18 +02:00
Earl Warren
1ffddf75d6
[DB] run all Forgejo migrations in integration tests
The tests at tests/integration/migration-test/migration_test.go will
not run any Forgejo migration when using the gitea-*.sql.gz files
because they do not contain a ForgejoVersion row which is interpreted
as a new Forgejo installation for which there is no need for migration.

Create a situation by which the ForgejoVersion table exists and has a
version of 0 in tests/integration/migration-test/forgejo-v1.19.0.*.sql.gz
thus ensuring all Forgejo migrations are run.

The forgejo*.sql.gz files do not have any Gitea related records, which
will be interpreted by the Gitea migrations as a new installation that
does not need any migration. As a consequence the migration tests run
when using forgejo-v1.19.0.*.sql.gz are exclusively about Forgejo
migrations.

(cherry picked from commit ec8003859c920ac05a071ad9b1d9d8af5a694ac0)
2023-08-21 07:22:18 +02:00
Giteabot
49d2a9e43c
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>
(cherry picked from commit 2bdc38e5921c28994437830561072257c3dca45e)
2023-08-21 07:22:18 +02:00
Giteabot
4e6b43e4bc
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>
(cherry picked from commit 0f5e07f5389ca36608eb840e35edc164d437aa40)
2023-08-21 07:22:18 +02:00
Giteabot
5cf57d940f
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>
(cherry picked from commit d6cf261be860e8022ccbad5585fc3508e840d409)
2023-08-21 07:22:18 +02:00
Giteabot
bbe06fcf17
Add changelog for 1.20.3 (#26373) (#26375)
Backport #26373 by @delvh

Co-authored-by: delvh <dev.lh@web.de>
(cherry picked from commit 7c555b2231cd199954b5e841b51f972552782f74)
2023-08-21 07:22:18 +02:00
Giteabot
3d69647e06
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>
(cherry picked from commit 981ab48503997bf3b2cefcfe623fba49c4317450)
2023-08-21 07:22:18 +02:00
Giteabot
c029b1a3bc
Fix wrong middleware sequence (#26428) (#26436)
Backport #26428 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
(cherry picked from commit ec37ea5945244f46026f4ec6a2e5f85b7dfbfcd6)
2023-08-21 07:22:18 +02:00
wxiaoguang
e64aa0d9c2
Fix admin queue page title and fix CI failures (#26409) (#26421)
Backport #26409

* Fix #26408
* Bypass the data race issue in "ssh" package

(cherry picked from commit 8ad331c9d209f50ed41c92b289ea2dcf575b1bdb)
2023-08-21 07:22:18 +02:00
Giteabot
0414e95cfb
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>
(cherry picked from commit dbabdf6d711ce0d72f98b2f099397c2ae7b7444b)
2023-08-21 07:22:18 +02:00
Giteabot
8265bece8e
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>
(cherry picked from commit 2d1a7e1cd42b31a62ca627423d088339809238c8)
2023-08-21 07:22:18 +02:00
Giteabot
119afd3761
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>
(cherry picked from commit df5558135b3520ad046588e781a3845b7b4aa24b)
2023-08-21 07:22:18 +02:00
Giteabot
802701acad
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>
(cherry picked from commit f329982b6ea1d2f0256a58d77415852b9cfc631c)
2023-08-21 07:22:17 +02:00
Giteabot
46ba658cc2
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>
(cherry picked from commit cb1a4da5c271d9639a8305b8f7d03dd54e835f4e)
2023-08-21 07:22:17 +02:00
Earl Warren
d83135c204
Revert "[GITEA] Use join for the deleting issue actions query"
This reverts commit 9b71369be9.
2023-08-21 07:22:17 +02:00
Earl Warren
695fda3dd0
[SEMVER] 5.0.2+0-gitea-1.20.3 2023-08-21 07:22:17 +02:00
delvh
64e2c8f6ff
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)

(cherry picked from commit 39cbca0f952ecdd1b985f20b9dd9fef4d621f99e)
2023-08-21 07:22:17 +02:00
Giteabot
678c611c3d
[docs] Add missing backtick in quickstart.zh-cn.md (#26349) (#26357)
(cherry picked from commit 9aadc25bc14d822099a0ea2b81c0abe126fae5f2)
2023-08-21 07:22:17 +02:00
Brian Lachniet
9db27d2602
[docs] Fix Gmail configuration (#26356)
(cherry picked from commit b94370504ff2c904707f25774da1d168ee4f37fd)
2023-08-21 07:22:17 +02:00
Giteabot
f597c789e1
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>
(cherry picked from commit 59354d7135c4d380e11cb35ae0ac59a0ec7bf041)
2023-08-21 07:22:17 +02:00
Giteabot
92589b9b8d
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>
(cherry picked from commit 0b97463cef7d86c930b808ec4f08f9ee700bff86)
2023-08-21 07:22:17 +02:00
Loïc Dachary
916ec9acab
[TESTS] MockVariable temporarily replaces a global value
defer test.MockVariable(&variable, 1234)()

(cherry picked from commit 9c78752444c2411f20e72b96b7b4fdf5359099d5)
(cherry picked from commit 8ab559df0d57a17a0ea73bd3676f40a523a15ab6)
(cherry picked from commit 2e7fe1ec9566e4840992e4049d8349873b7334ee)
(cherry picked from commit f9618b8896)

Conflicts:
	modules/test/utils.go
	https://codeberg.org/forgejo/forgejo/issues/1219
2023-08-21 07:22:17 +02:00
wxiaoguang
2e539d5190
Fix incorrect CLI exit code and duplicate error message (#26346) (#26347)
Backport #26346

Follow the CLI refactoring, and add tests.

(cherry picked from commit fa431b377d7055c6c1362787eebda5b8b67e8d58)
2023-08-21 07:22:17 +02:00
Earl Warren
149cd865ea
Revert "[TESTS] MockVariable temporarily replaces a global value"
This reverts commit f9618b8896.
2023-08-21 07:22:17 +02:00
Giteabot
2f6b7ce91a
Fix log typo in task.go (#26337) (#26343)
Backport #26337 by @cassiozareck

Signed-off-by: cassiozareck <cassiomilczareck@gmail.com>
(cherry picked from commit 8a97cdd91bff61129505d63311a0686c61abca71)
2023-08-21 07:22:17 +02:00
Giteabot
75417ed070
Prevent newline errors with Debian packages (#26332) (#26342)
Backport #26332 by @KN4CK3R

Fixes #26313

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
(cherry picked from commit 3e9475b3b25e1f7052842ef964be5da343ebe957)
2023-08-21 07:22:16 +02:00
Giteabot
33c52556a3
Fix bug with sqlite load read (#26305) (#26339)
Backport #26305 by @lunny

Possible fix #26280

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
(cherry picked from commit 9be9042479ccbe73b4d78fd2acc467972b48ecb3)
2023-08-21 07:22:16 +02:00
Giteabot
e5c26e38f5
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>
(cherry picked from commit 9451781ebea0d9945127c142136e5b6ef373141c)
2023-08-21 07:22:16 +02:00
Giteabot
5aa6a8288d
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>
(cherry picked from commit 88f6f7579cdaa557333bc86b3e45bf6458d889b6)
2023-08-21 07:22:16 +02:00
Giteabot
9e4be39acb
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>
(cherry picked from commit fcd055c34a21095123d6fc18449e855b6c626bec)
2023-08-21 07:22:16 +02:00
Giteabot
db326835e6
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>
(cherry picked from commit a57568bad7dac3c818d01a5b75fe66ce83bf140f)
2023-08-21 07:22:16 +02:00
Giteabot
b97dbf7a9e
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>
(cherry picked from commit a7583370462ecd409485606c3d0379adb320cd05)
2023-08-21 07:22:16 +02:00
Giteabot
bd416d0ba9
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>
(cherry picked from commit 2cf1515f5c5e048b5f1280e60c54ad3247988ad0)
2023-08-21 07:22:16 +02:00
Giteabot
6a15395f19
Update Arch linux URL from community to extra (#26273) (#26276)
Co-authored-by: minijaws <minijaws@gmail.com>
(cherry picked from commit a075285f2488dcbfe82c95fa9e6eb86c7e4d34fd)
2023-08-21 07:22:16 +02:00
Earl Warren
20557c6bdb
[BRANDING] define the forgejo webhook type
templates/swagger/v1_json.tmpl updated with `make generate-swagger`

(cherry picked from commit 88899c492efeedd138ba088a36b9c0bc733ead7b)
(cherry picked from commit 7171bd9617c32c4911e3bdbc23c02a19e80d2465)
(cherry picked from commit 1a742446c17aef9ca62fe75bfc0a388d40138154)
(cherry picked from commit d7c189d7b2f9fea299a31adf068db969920ae39d)

Conflicts:
	routers/web/web.go
(cherry picked from commit cbdea868e41fb38ca491f8b449c3e525ec82d6b9)
(cherry picked from commit 6cd150483b06e17aee023c0afd01a3f2460b3415)
(cherry picked from commit 47246da8d3f50a02d11b77b3d402618b144aa720)
(cherry picked from commit f2aa0e6b769d432e627798bcf294b04b7d253213)
(cherry picked from commit 5a4fc69a16de8d6199ea24198299297ef7a3587b)
(cherry picked from commit 48e444ca09c22f930514a01846b0c8bc3cef35ab)
(cherry picked from commit 888e53781175d8d977f66d78991bd66563fcddfb)
(cherry picked from commit 5121f493c99f19d8050aa09224ac3532b4100ec7)
(cherry picked from commit 9394e55fdf80bf3d7bf8b2aba561ad44a84e3913)
(cherry picked from commit 3a2ce51768de65892e3ec73596e3862354c9502e)
(cherry picked from commit 719ead3a651f12afbb59c856914b0085e5cee157)
(cherry picked from commit 83e6f82e2aea619a3cd502e133773d33c0e60133)
(cherry picked from commit 494a429b21c6234be38b9e3db0f930fbb8118205)
(cherry picked from commit 4d775db6b41f731e956cc6bb9217ef349b4a3635)
(cherry picked from commit b68f777dc2822ec5c4e30186675cc82daec092a9)
(cherry picked from commit 5b934023fa58820f27c349c26f2a1ce89aee6795)
(cherry picked from commit 3b1ed8b16c73374cd5b6339f5315229dc82488da)
(cherry picked from commit 6bc4a46c9fc6472e1c4bf0bb20dea6867f1b392f)
(cherry picked from commit 8064bb24a3c752a86271f154ad4d0c4763e07295)

Conflicts:
	templates/admin/hook_new.tmpl
	templates/org/settings/hook_new.tmpl
	templates/repo/settings/webhook/base_list.tmpl
	templates/repo/settings/webhook/new.tmpl
	templates/user/settings/hook_new.tmpl
	https://codeberg.org/forgejo/forgejo/pulls/1181

(cherry picked from commit 55f5588a9150d8912c0f8342495f858e4e1e2959)

Conflicts:
	routers/web/web.go
	https://codeberg.org/forgejo/forgejo/issues/1219
2023-08-21 07:22:16 +02:00
Giteabot
c1544754f8
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>
(cherry picked from commit 2517da90aacc735ce5a9d5401fe23990597139d4)
2023-08-21 07:22:16 +02:00
Earl Warren
c862cc15c8
Revert "[BRANDING] define the forgejo webhook type"
This reverts commit 02ba08ca84.
2023-08-21 07:22:16 +02:00
Giteabot
c2f2fed57a
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>
(cherry picked from commit 060026995a95a61a35535215105db015bab8a697)
2023-08-21 07:22:15 +02:00
Giteabot
cc8c7005a6
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>
(cherry picked from commit 0f265a2489bcdac6cf350a89eecb19ed78e133c1)
2023-08-21 07:22:15 +02:00
silverwind
b073f7fd6a
Fix attachment clipboard copy on insecure origin (#26224) (#26231)
Backport https://github.com/go-gitea/gitea/pull/26224.

(cherry picked from commit 0d04f70d6ae9a1cfd20117f57a901017021ea1f3)
2023-08-21 07:22:15 +02:00
Giteabot
1d900bc6a9
Avoid writing config file if not installed (#26107) (#26113)
Backport #26107 by @wxiaoguang

Just like others (oauth2 secret, internal token, etc), do not generate
if no install lock

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit e2596b0a999b2d2b6ce699ac8b6a3981a89d5bd5)
(cherry picked from commit 78722734fe)
2023-08-21 07:22:15 +02:00
Gusted
8126dadc8d Merge pull request '[GITEA] Add anchor to review types' (#1295) from Gusted/forgejo:forgejo-bp-1293 into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1295
2023-08-19 23:47:55 +00:00
Gusted
88e179d5ef
[GITEA] Add anchor to review types
- Backport of https://codeberg.org/forgejo/forgejo/pulls/1293
  - The review type '22' is a general comment type that is attached to
single codecomments, reviews with multiple comments or to simple approve
and request changes comment. This comment can be used to create a link
towards this action on an pull request.
  - Adds an anchor to the review comment type, so that when its getting
linked to it, it actually jumps towards that event.
  - This also now fixes the behavior that after you created a review you
will be redirected to that review and because this is an general comment
type other mails will also be 'fixed' such as the approved or request changes.
  - Resolves https://codeberg.org/forgejo/forgejo/issues/1248
2023-08-19 20:46:46 +02:00
Gusted
08f1fe5812 Merge pull request '[GITEA] Use vertical tabs on issue filters' (#1294) from Gusted/forgejo:forgejo-bp-1287 into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1294
2023-08-19 18:43:09 +00:00
Gusted
7e18a86a3a
[GITEA] Use vertical tabs on issue filters
- Backport of https://codeberg.org/forgejo/forgejo/pulls/1287
  - This is actually https://github.com/go-gitea/gitea/pull/19978 &
https://github.com/go-gitea/gitea/pull/19486 but was removed in one of
the UI refactors of v1.20
  - This is a very technical fix and is best explained in the CSS
comments. But the short version: When there's an overflow being set, but
you want an element to 'break out' of that overflow with `position:
absolute`, it sometimes doesn't work! You need to set some CSS to let
the browser know that the element needs to use an element outside of
that overflow as 'clip parent'.
  - Resolves my internal frustration with the mobile UI constantly getting broken.

(cherry picked from commit 879f842bed999190506e1d60508e7aede1a4be21)
2023-08-19 13:17:00 +02:00
Gusted
070904b531 Merge pull request 'Don't stack PR tab menu on small screens (#25789)' (#1288) from Gusted/forgejo:forgejo-gt-backport-25789 into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1288
2023-08-19 09:15:03 +00:00
sebastian-sauer
90053ce279
Don't stack PR tab menu on small screens (#25789)
the stacking takes up screen space - display the tabs as the navigation
bar. github uses the same layout.

Screenshots (left before, right after):

![image](https://github.com/go-gitea/gitea/assets/1135157/d7e2aaec-c67b-403d-8d56-d4c824b04eed)
![image](https://github.com/go-gitea/gitea/assets/1135157/9e150881-c265-4074-afd7-407bb52e1934)

Large screen:

![image](https://github.com/go-gitea/gitea/assets/1135157/d5cbdaa3-2962-4c4f-9595-5938981ff99e)

(cherry picked from commit b81c013057)
2023-08-18 15:40:21 +02:00
Gusted
30b11209d1 Merge pull request '[GITEA] Wrap branch information in PR list' (#1255) from Gusted/forgejo:forgejo-120-wrap-branches into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1255
2023-08-13 12:22:55 +00:00
Gusted
b45b87988b [GITEA] Wrap branch information in PR list
- On mobile there's not a lot of horizontal space, so sometimes
information such as icons has to be removed or information gets wrapped
in order to not result in overflowing or weird UI behavior.
- On mobile visiting the pull requests list of an repository, it shows
which head branch is merging into which base branch. This wasn't
properly made responsive and with sufficient long branch names (such as
those used in the Forgejo repository) it resulted in weird UI behavior.
- This patch fixes that by allowing it to wrap, such as the behavior in
1.21
- This already has been fixed in 1.21 with
b9baed2c74.
2023-08-12 17:14:21 +00:00
Gusted
291b1b6a26 Merge pull request '[GITEA] Fix media description render for orgmode' (#1256) from Gusted/forgejo:forgejo-backport-1224 into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1256
2023-08-12 17:13:56 +00:00
Gusted
82cb19649d
[GITEA] Fix media description render for orgmode
- Backport of #1224
- In org mode you can specify an description for media via the following
syntax `[[description][media link]]`. The description is then used as
title or alt.
- This patch fixes the rendering of the description by seperating the
description and non-description cases and using `org.String()`.
- Added unit tests.
- Inspired by 6eb20dbda9/org/html_writer.go (L406-L427)
- Resolves https://codeberg.org/Codeberg/Community/issues/848
2023-08-12 16:04:33 +02:00
Gusted
a8f5ad1437 Merge pull request 'Improve profile readme rendering (#25988)' (#1240) from earl-warren/forgejo:forgejo-v1.20-readme into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1240
2023-08-11 08:54:23 +00:00
Earl Warren
84c3b60a4c
Improve profile readme rendering (#25988)
- 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: Gusted <postmaster@gusted.xyz>
Conflicts:
	routers/web/user/profile.go
	inserted Metas:   map[string]string{"mode": "document"}, where
	it was missing
2023-08-11 08:56:04 +02:00
Earl Warren
5d3cfbd2ba
[CI] pin go v1.20 for testing
Refs: https://codeberg.org/forgejo/forgejo/issues/1228
2023-08-09 17:56:06 +02:00
Gusted
b1b90dbb4b Merge pull request 'Fix API leaking Usermail if not logged in' (#1197) from Gusted/forgejo:gitea-backport-25097 into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1197
2023-08-05 19:46:59 +00:00
JakobDev
d89003cc1b Fix API leaking Usermail if not logged in (#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: silverwind <me@silverwind.io>
(cherry picked from commit ea385f5d39)
2023-08-05 11:43:54 +00:00
Loïc Dachary
c9cd5fc65a
[GITEA] golang.org/x/net v0.13.0
Vulnerability #1: GO-2023-1988
    Improper rendering of text nodes in golang.org/x/net/html
  More info: https://pkg.go.dev/vuln/GO-2023-1988
  Module: golang.org/x/net
    Found in: golang.org/x/net@v0.12.0
    Fixed in: golang.org/x/net@v0.13.0
    Example traces found:
      #1: modules/markup/html.go:371:24: markup.postProcess calls html.Render
2023-08-04 23:27:27 +02:00
Gusted
d21b0026c7 Merge pull request '[GITEA] Show manual cron run's last time' (#1167) from Gusted/forgejo:backport-1087 into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1167
2023-07-31 20:29:47 +00:00
Gusted
5f769ef20d [GITEA] Show manual cron run's last time
- Currently in the cron tasks, the 'Previous Time' only displays the
previous time of when the cron library executes the function, but not
any of the manual executions of the task.
- Store the last run's time in memory in the Task struct and use that,
when that time is later than time that the cron library has executed this
task.
- This ensures that if an instance admin manually starts a task, there's
feedback that this task is/has been run, because the task might be run
that quick, that the status icon already has been changed to an
checkmark,
- Tasks that are executed at startup now reflect this as well, as the
time of the execution of that task on startup is now being shown as
'Previous Time'.
- Added integration tests for the API part, which is easier to test
because querying the HTML table of cron tasks is non-trivial.
- Resolves https://codeberg.org/forgejo/forgejo/issues/949
- Backport #1087
2023-07-31 18:34:14 +00:00
Gusted
eaa9b35cf6 Merge pull request '[GITEA] Use join for the deleting issue actions query' (#1165) from Gusted/forgejo:backport-1154 into v1.20/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1165
2023-07-31 18:33:47 +00:00
Gusted
9b71369be9 [GITEA] Use join for the deleting issue actions query
- The action tables can become very large as it's a dumpster for every
action that an user does on an repository.
- The following query: `DELETE FROM action WHERE comment_id IN (SELECT id FROM comment WHERE
issue_id=?)` is not using indexes for `comment_id` and is instead using
an full table scan by MariaDB.
- Rewriting the query to use an JOIN will allow MariaDB to use the
index.
- More information: https://codeberg.org/Codeberg-Infrastructure/techstack-support/issues/9
- Backport https://codeberg.org/forgejo/forgejo/pulls/1154
2023-07-31 10:14:30 +00:00
Gusted
e7d0475e15
[GOLDMARK] html <img /> code cannot be parse in markdown file
- Update goldmark to v1.5.5, which includes
254b9f8f77
- Resolves https://codeberg.org/Codeberg/Community/issues/936
- Backport https://codeberg.org/forgejo/forgejo/pulls/1155
2023-07-30 20:12:49 +02:00
151 changed files with 2382 additions and 413 deletions

View file

@ -0,0 +1,26 @@
RUN_MODE = prod
WORK_PATH = ${WORK_PATH}
[server]
APP_DATA_PATH = ${WORK_PATH}/data
HTTP_PORT = 3000
SSH_LISTEN_PORT = 2222
LFS_START_SERVER = true
[database]
DB_TYPE = sqlite3
PATH = ${WORK_PATH}/forgejo.db
[log]
MODE = file
LEVEL = debug
ROUTER = file
[log.file]
FILE_NAME = forgejo.log
[security]
INSTALL_LOCK = true
[actions]
ENABLED = true

View file

@ -0,0 +1,28 @@
RUN_MODE = prod
WORK_PATH = ${WORK_PATH}
[server]
APP_DATA_PATH = ${WORK_PATH}/data
HTTP_PORT = 3000
SSH_LISTEN_PORT = 2222
LFS_START_SERVER = true
[database]
DB_TYPE = sqlite3
[log]
MODE = file
LEVEL = debug
ROUTER = file
[log.file]
FILE_NAME = forgejo.log
[security]
INSTALL_LOCK = true
[actions]
ENABLED = true
[storage]
PATH = ${WORK_PATH}/merged

View file

@ -0,0 +1,55 @@
RUN_MODE = prod
WORK_PATH = ${WORK_PATH}
[server]
APP_DATA_PATH = ${WORK_PATH}/elsewhere
HTTP_PORT = 3000
SSH_LISTEN_PORT = 2222
LFS_START_SERVER = true
[database]
DB_TYPE = sqlite3
[log]
MODE = file
LEVEL = debug
ROUTER = file
[log.file]
FILE_NAME = forgejo.log
[security]
INSTALL_LOCK = true
[actions]
ENABLED = true
[attachment]
[storage.attachments]
PATH = ${WORK_PATH}/data/attachments
[lfs]
[storage.lfs]
PATH = ${WORK_PATH}/data/lfs
[avatar]
[storage.avatars]
PATH = ${WORK_PATH}/data/avatars
[repo-avatar]
[storage.repo-avatars]
PATH = ${WORK_PATH}/data/repo-avatars
[repo-archive]
[storage.repo-archive]
PATH = ${WORK_PATH}/data/repo-archive
[packages]
[storage.packages]
PATH = ${WORK_PATH}/data/packages

View file

@ -0,0 +1,43 @@
RUN_MODE = prod
WORK_PATH = ${WORK_PATH}
[server]
APP_DATA_PATH = ${WORK_PATH}/elsewhere
HTTP_PORT = 3000
SSH_LISTEN_PORT = 2222
LFS_START_SERVER = true
[database]
DB_TYPE = sqlite3
[log]
MODE = file
LEVEL = debug
ROUTER = file
[log.file]
FILE_NAME = forgejo.log
[security]
INSTALL_LOCK = true
[actions]
ENABLED = true
[attachment]
PATH = ${WORK_PATH}/data/attachments
[lfs]
PATH = ${WORK_PATH}/data/lfs
[avatar]
PATH = ${WORK_PATH}/data/avatars
[repo-avatar]
PATH = ${WORK_PATH}/data/repo-avatars
[repo-archive]
PATH = ${WORK_PATH}/data/repo-archive
[packages]
PATH = ${WORK_PATH}/data/packages

274
.forgejo/upgrades/test-upgrade.sh Executable file
View file

@ -0,0 +1,274 @@
#!/bin/bash
# SPDX-License-Identifier: MIT
set -ex
HOST_PORT=0.0.0.0:3000
STORAGE_PATHS="attachments avatars lfs packages repo-archive repo-avatars"
DIR=/tmp/forgejo-upgrades
SELF_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: '
function maybe_sudo() {
if test $(id -u) != 0 ; then
SUDO=sudo
fi
}
function dependencies() {
if ! which curl daemon > /dev/null ; then
maybe_sudo
$SUDO apt-get install -y -qq curl daemon
fi
}
function build() {
local version=$1
local semver=$2
if ! test -f $DIR/forgejo-$version ; then
mkdir -p $DIR
make VERSION=v$version GITEA_VERSION=v$version FORGEJO_VERSION=$semver TAGS='bindata sqlite sqlite_unlock_notify' generate gitea
mv gitea $DIR/forgejo-$version
fi
}
function build_all() {
test -f Makefile
build 1.20.3-0 5.0.2+0-gitea-1.20.3
build 1.21.0-0 6.0.0+0-gitea-1.21.0
}
function wait_for() {
rm -f $DIR/setup-forgejo.out
success=false
for delay in 1 1 5 5 15 ; do
if "$@" >> $DIR/setup-forgejo.out 2>&1 ; then
success=true
break
fi
cat $DIR/setup-forgejo.out
echo waiting $delay
sleep $delay
done
if test $success = false ; then
cat $DIR/setup-forgejo.out
return 1
fi
}
function download() {
local version=$1
if ! test -f $DIR/forgejo-$version ; then
mkdir -p $DIR
wget -O $DIR/forgejo-$version --quiet https://codeberg.org/forgejo/forgejo/releases/download/v$version/forgejo-$version-linux-amd64
chmod +x $DIR/forgejo-$version
fi
}
function cleanup_logs() {
local work_path=$DIR/forgejo-work-path
rm -f $DIR/*.log
rm -f $work_path/log/*.log
}
function start() {
local version=$1
download $version
local work_path=$DIR/forgejo-work-path
daemon --chdir=$DIR --unsafe --env="TERM=$TERM" --env="HOME=$HOME" --env="PATH=$PATH" --pidfile=$DIR/forgejo-pid --errlog=$DIR/forgejo-err.log --output=$DIR/forgejo-out.log -- $DIR/forgejo-$version --config $work_path/app.ini --work-path $work_path
if ! wait_for grep 'Starting server on' $work_path/log/forgejo.log ; then
cat $DIR/*.log
cat $work_path/log/*.log
return 1
fi
create_user $version
$work_path/forgejo-api http://${HOST_PORT}/api/v1/version
}
function create_user() {
local version=$1
local work_path=$DIR/forgejo-work-path
if test -f $work_path/forgejo-token; then
return
fi
local user=root
local password=admin1234
local cli="$DIR/forgejo-$version --config $work_path/app.ini --work-path $work_path"
$cli admin user create --admin --username "$user" --password "$password" --email "$user@example.com"
local scopes="--scopes all"
if echo $version | grep --quiet 1.18. ; then
scopes=""
fi
$cli admin user generate-access-token -u $user --raw $scopes > $work_path/forgejo-token
( echo -n 'Authorization: token ' ; cat $work_path/forgejo-token ) > $work_path/forgejo-header
( echo "#!/bin/sh" ; echo 'curl -sS -H "Content-Type: application/json" -H @'$work_path/forgejo-header' "$@"' ) > $work_path/forgejo-api && chmod +x $work_path/forgejo-api
}
function stop() {
if test -f $DIR/forgejo-pid ; then
local pid=$(cat $DIR/forgejo-pid)
kill -TERM $pid
pidwait $pid || true
for delay in 1 1 2 2 5 5 ; do
if ! test -f $DIR/forgejo-pid ; then
break
fi
sleep $delay
done
! test -f $DIR/forgejo-pid
fi
cleanup_logs
}
function reset() {
local config=$1
local work_path=$DIR/forgejo-work-path
rm -fr $work_path
mkdir -p $work_path
WORK_PATH=$work_path envsubst < $SELF_DIR/$config-app.ini > $work_path/app.ini
}
function verify_storage() {
local work_path=$DIR/forgejo-work-path
for path in ${STORAGE_PATHS} ; do
test -d $work_path/data/$path
done
}
function cleanup_storage() {
local work_path=$DIR/forgejo-work-path
for path in ${STORAGE_PATHS} ; do
rm -fr $work_path/data/$path
done
}
function test_downgrade_1.20.2_fails() {
local work_path=$DIR/forgejo-work-path
echo "================ See also https://codeberg.org/forgejo/forgejo/pulls/1225"
echo "================ downgrading from 1.20.3-0 to 1.20.2-0 fails"
stop
reset default
start 1.20.3-0
stop
download 1.20.2-0
timeout 60 $DIR/forgejo-1.20.2-0 --config $work_path/app.ini --work-path $work_path || true
if ! grep --fixed-strings --quiet 'use the newer database' $work_path/log/forgejo.log ; then
cat $work_path/log/forgejo.log
return 1
fi
}
function test_bug_storage_merged() {
local work_path=$DIR/forgejo-work-path
echo "================ See also https://codeberg.org/forgejo/forgejo/pulls/1225"
echo "================ using < 1.20.3-0 and [storage].PATH merge all storage"
for version in 1.18.5-0 1.19.4-0 1.20.2-0 ; do
stop
reset merged
start $version
for path in ${STORAGE_PATHS} ; do
! test -d $work_path/data/$path
done
for path in ${STORAGE_PATHS} ; do
! test -d $work_path/merged/$path
done
test -d $work_path/merged
done
stop
echo "================ upgrading from 1.20.2-0 with [storage].PATH fails"
download 1.20.3-0
timeout 60 $DIR/forgejo-1.20.3-0 --config $work_path/app.ini --work-path $work_path || true
if ! grep --fixed-strings --quiet '[storage].PATH is set and may create storage issues' $work_path/log/forgejo.log ; then
cat $work_path/log/forgejo.log
return 1
fi
}
function test_bug_storage_misplace() {
local work_path=$DIR/forgejo-work-path
echo "================ See also https://codeberg.org/forgejo/forgejo/pulls/1225"
echo "================ using < 1.20 and conflicting sections misplace storage"
for version in 1.18.5-0 1.19.4-0 ; do
stop
reset misplace
start $version
#
# some storage are where they should be
#
test -d $work_path/data/packages
test -d $work_path/data/repo-archive
test -d $work_path/data/attachments
#
# others are under APP_DATA_PATH
#
test -d $work_path/elsewhere/lfs
test -d $work_path/elsewhere/avatars
test -d $work_path/elsewhere/repo-avatars
done
echo "================ using < 1.20.[12]-0 and conflicting sections ignores [storage.*]"
for version in 1.20.2-0 ; do
stop
reset misplace
start $version
for path in ${STORAGE_PATHS} ; do
test -d $work_path/elsewhere/$path
done
done
stop
echo "================ upgrading from 1.20.2-0 with conflicting sections fails"
download 1.20.3-0
timeout 60 $DIR/forgejo-1.20.3-0 --config $work_path/app.ini --work-path $work_path || true
for path in ${STORAGE_PATHS} ; do
if ! grep --fixed-strings --quiet "[storage.$path] may conflict" $work_path/log/forgejo.log ; then
cat $work_path/log/forgejo.log
return 1
fi
done
}
function test_successful_upgrades() {
for config in default specific ; do
echo "================ using $config app.ini"
reset $config
for version in 1.18.5-0 1.19.4-0 1.20.2-0 1.20.3-0 1.21.0-0 ; do
echo "================ run $version"
cleanup_storage
start $version
verify_storage
stop
done
done
}
function test_upgrades() {
stop
dependencies
build_all
test_successful_upgrades
test_bug_storage_misplace
test_bug_storage_merged
test_downgrade_1.20.2_fails
}
"$@"

View file

@ -14,7 +14,7 @@ jobs:
- uses: https://code.forgejo.org/actions/checkout@v3 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version: ">=1.20" go-version: "1.20"
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make lint-backend - run: make lint-backend
@ -26,7 +26,7 @@ jobs:
- uses: https://code.forgejo.org/actions/checkout@v3 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version: ">=1.20" go-version: "1.20"
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs - run: make --always-make checks-backend # ensure the "go-licenses" make target runs
@ -39,7 +39,7 @@ jobs:
- uses: https://code.forgejo.org/actions/checkout@v3 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version: ">=1.20.0" go-version: "1.20"
- run: | - run: |
git config --add safe.directory '*' git config --add safe.directory '*'
chown -R gitea:gitea . /go chown -R gitea:gitea . /go
@ -76,7 +76,7 @@ jobs:
- uses: https://code.forgejo.org/actions/checkout@v3 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version: ">=1.20.0" go-version: "1.20"
- run: | - run: |
git config --add safe.directory '*' git config --add safe.directory '*'
chown -R gitea:gitea . /go chown -R gitea:gitea . /go
@ -109,7 +109,7 @@ jobs:
- uses: https://code.forgejo.org/actions/checkout@v3 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version: ">=1.20.0" go-version: "1.20"
- run: | - run: |
git config --add safe.directory '*' git config --add safe.directory '*'
chown -R gitea:gitea . /go chown -R gitea:gitea . /go
@ -136,7 +136,7 @@ jobs:
- uses: https://code.forgejo.org/actions/checkout@v3 - uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4 - uses: https://code.forgejo.org/actions/setup-go@v4
with: with:
go-version: ">=1.20.0" go-version: "1.20"
- run: | - run: |
git config --add safe.directory '*' git config --add safe.directory '*'
chown -R gitea:gitea . /go chown -R gitea:gitea . /go
@ -154,3 +154,33 @@ jobs:
RACE_ENABLED: true RACE_ENABLED: true
TEST_TAGS: gogit sqlite sqlite_unlock_notify TEST_TAGS: gogit sqlite sqlite_unlock_notify
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
upgrade:
needs: [test-sqlite]
runs-on: docker
container:
image: codeberg.org/forgejo/test_env:main
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version: "1.20"
- run: |
git config --add safe.directory '*'
chown -R gitea:gitea . /go
- run: |
su gitea -c 'make deps-backend'
- run: |
su gitea -c 'make backend'
env:
TAGS: bindata sqlite sqlite_unlock_notify
- run: |
su gitea -c 'make gitea'
cp -a gitea /tmp/forgejo-development
timeout-minutes: 50
env:
TAGS: bindata sqlite sqlite_unlock_notify
- run: |
script=$(pwd)/.forgejo/upgrades/test-upgrade.sh
$script dependencies
su gitea -c "$script test_upgrades"

View file

@ -2,9 +2,69 @@
This changelog goes through all the changes that have been made in each release 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 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 * ENHANCEMENTS
* Calculate MAX_WORKERS default value by CPU number (#26177) (#26183) * 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 wrong workflow status when rerun a job in an already finished workflow (#26119) (#26124)
* Fix duplicated url prefix on issue context menu (#26066) (#26067) * 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 * SECURITY
* Disallow dangerous URL schemes (#25960) (#25964) * Disallow dangerous URL schemes (#25960) (#25964)

View file

@ -89,9 +89,9 @@ endif
VERSION = ${GITEA_VERSION} VERSION = ${GITEA_VERSION}
# SemVer # SemVer
FORGEJO_VERSION := 5.0.1+0-gitea-1.20.2 FORGEJO_VERSION := 5.0.2+0-gitea-1.20.3
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64

View file

@ -348,6 +348,10 @@ func runRepoSyncReleases(_ *cli.Context) error {
return err return err
} }
if err := git.InitSimple(ctx); err != nil {
return err
}
log.Trace("Synchronizing repository releases (this may take a while)") log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ { for page := 1; ; page++ {
repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{ repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{

26
cmd/main.go Normal file
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
}

View file

@ -4,9 +4,16 @@
package cmd package cmd
import ( import (
"fmt"
"io"
"strings"
"testing" "testing"
"code.gitea.io/gitea/models/unittest" "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) { func TestMain(m *testing.M) {
@ -14,3 +21,64 @@ func TestMain(m *testing.M) {
GiteaRootPath: "..", 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)
}

View file

@ -46,7 +46,6 @@ PATH = /data/gitea/attachments
[log] [log]
MODE = console MODE = console
LEVEL = info LEVEL = info
ROUTER = console
ROOT_PATH = /data/gitea/log ROOT_PATH = /data/gitea/log
[security] [security]

View file

@ -79,8 +79,9 @@ SMTP_PORT = 465
FROM = example.user@gmail.com FROM = example.user@gmail.com
USER = example.user USER = example.user
PASSWD = `***` PASSWD = `***`
PROTOCOL = smtp PROTOCOL = smtps ; Gitea >= 1.19.0
IS_TLS_ENABLED = true ; 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 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

View file

@ -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 ; by default, the "file" mode will record logs to %(log.ROOT_PATH)/gitea.log, so we don't need to set it
; [log.file] ; [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] [log.file-error]
MODE = file
LEVEL = Error LEVEL = Error
FILE_NAME = file-error.log FILE_NAME = file-error.log
``` ```

View file

@ -40,7 +40,7 @@ apk add gitea
## Arch Linux ## 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 ```sh
pacman -S gitea pacman -S gitea

View file

@ -17,16 +17,20 @@ menu:
# Upgrade from an old Gitea # Upgrade from an old Gitea
To update Gitea, download a newer version, stop the old one, perform a backup, and run the new one. Follow below steps to ensure a smooth upgrade to a new Gitea version.
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.
## Check the Changelog for breaking changes ## Check the Changelog for breaking changes
To make Gitea better, some breaking changes are unavoidable, especially for big milestone releases. 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. 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 ## Backup for downgrade
Gitea keeps compatibility for patch versions whose first two fields are the same (`a.b.x` -> `a.b.y`), 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, 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. 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 ## Upgrade with Docker
* `docker pull` the latest Gitea release. * `docker pull` the latest Gitea release.

View file

@ -15,16 +15,20 @@ menu:
# 从旧版 Gitea 升级 # 从旧版 Gitea 升级
想要升级 Gitea只需要下载新版停止运行旧版进行数据备份然后运行新版就好。 在升级之前,您需要做如下的准备工作。
每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。
如果需要进行数据库迁移Gitea 会花一些时间完成升级然后继续服务。
## 为重大变更检查更新日志 ## 为重大变更检查更新日志
为了让 Gitea 变得更好,进行重大变更是不可避免的,尤其是一些里程碑更新的发布。 为了让 Gitea 变得更好,进行重大变更是不可避免的,尤其是一些里程碑更新的发布。
在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.io/) 在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.com/)
并检查重大变更是否会影响你的 Gitea 实例。 并检查重大变更是否会影响你的 Gitea 实例。
## 在控制面板中检查过期的配置项
一些配置项可能会在后续版本中过期,你需要在控制面板中检查他们。如果不解决过期的配置项,
Gitea也许会在升级后无法重启。你可以访问 https://docs.gitea.com 获得要升级的版本
对应的文档来修改你的配置文件。
## 降级前的备份 ## 降级前的备份
Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`) Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`)
@ -56,6 +60,10 @@ Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`
如果你在使用云服务或拥有快照功能的文件系统, 如果你在使用云服务或拥有快照功能的文件系统,
最好对 Gitea 的数据盘及相关资料存储进行一次快照。 最好对 Gitea 的数据盘及相关资料存储进行一次快照。
在所有上述步骤准备妥当之后,要升级 Gitea只需要下载新版停止运行旧版进行数据备份然后运行新版就好。
每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。
如果需要进行数据库迁移Gitea 会花一些时间完成升级然后继续服务。
## 从 Docker 升级 ## 从 Docker 升级
* `docker pull` 拉取 Gitea 的最新发布版。 * `docker pull` 拉取 Gitea 的最新发布版。

View file

@ -123,7 +123,7 @@ jobs:
请注意,演示文件中包含一些表情符号。 请注意,演示文件中包含一些表情符号。
请确保您的数据库支持它们特别是在使用MySQL时。 请确保您的数据库支持它们特别是在使用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)。 有关更多信息,请参阅[数据库准备工作](installation/database-preparation.md#mysql)。
或者,您可以从演示文件中删除所有表情符号,然后再尝试一次。 或者,您可以从演示文件中删除所有表情符号,然后再尝试一次。

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 ## Creating PRs with Agit
Agit allows to create PRs while pushing code to the remote repo. \ 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). \ 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: The following example illustrates this:
```shell ```shell

4
go.mod
View file

@ -104,12 +104,12 @@ require (
github.com/xanzy/go-gitlab v0.83.0 github.com/xanzy/go-gitlab v0.83.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.5.4 github.com/yuin/goldmark v1.5.5
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.11.0 golang.org/x/crypto v0.11.0
golang.org/x/image v0.7.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/oauth2 v0.8.0
golang.org/x/sys v0.10.0 golang.org/x/sys v0.10.0
golang.org/x/text v0.11.0 golang.org/x/text v0.11.0

8
go.sum
View file

@ -1098,8 +1098,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU=
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 h1:Py16JEzkSdKAtEFJjiaYLYBOWGXc1r/xHj/Q/5lA37k= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 h1:Py16JEzkSdKAtEFJjiaYLYBOWGXc1r/xHj/Q/5lA37k=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
@ -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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

11
main.go
View file

@ -38,8 +38,11 @@ var (
MakeVersion = "" MakeVersion = ""
) )
var ForgejoVersion = "1.0.0"
func init() { func init() {
setting.AppVer = Version setting.AppVer = Version
setting.ForgejoVersion = ForgejoVersion
setting.AppBuiltWith = formatBuiltWith() setting.AppBuiltWith = formatBuiltWith()
setting.AppStartTime = time.Now().UTC() setting.AppStartTime = time.Now().UTC()
} }
@ -192,11 +195,13 @@ argument - which can alternatively be run by running the subcommand web.`
app.Commands = append(app.Commands, subCmdWithIni...) app.Commands = append(app.Commands, subCmdWithIni...)
app.Commands = append(app.Commands, subCmdStandalone...) app.Commands = append(app.Commands, subCmdStandalone...)
err := app.Run(os.Args) cli.OsExiter = func(code int) {
if err != nil { log.GetManager().Close()
_, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err) os.Exit(code)
} }
app.ErrWriter = os.Stderr
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
log.GetManager().Close() log.GetManager().Close()
} }

View file

@ -281,7 +281,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil { if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil {
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err) return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
} else if len(gots) != 1 { } 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 { } else {
_, workflowJob = gots[0].Job() _, workflowJob = gots[0].Job()
} }

View file

@ -685,18 +685,34 @@ func NotifyWatchersActions(acts []*Action) error {
} }
// DeleteIssueActions delete all actions related with issueID // 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 // delete actions assigned to this issue
subQuery := builder.Select("`id`"). e := db.GetEngine(ctx)
From("`comment`").
Where(builder.Eq{"`issue_id`": issueID}) // MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
if _, err := db.GetEngine(ctx).In("comment_id", subQuery).Delete(&Action{}); err != nil { // so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
return err 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). In("op_type", ActionCreateIssue, ActionCreatePullRequest).
Where("content LIKE ?", strconv.FormatInt(issueID, 10)+"|%"). Where("content LIKE ?", strconv.FormatInt(issueIndex, 10)+"|%"). // "IssueIndex|content..."
Delete(&Action{}) Delete(&Action{})
return err return err
} }

View file

@ -4,6 +4,7 @@
package activities_test package activities_test
import ( import (
"fmt"
"path" "path"
"testing" "testing"
@ -284,3 +285,36 @@ func TestConsistencyUpdateAction(t *testing.T) {
assert.NoError(t, db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions)) assert.NoError(t, db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions))
unittest.CheckConsistencyFor(t, &activities_model.Action{}) 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)
}

View file

@ -319,7 +319,7 @@ func createIssueNotification(ctx context.Context, userID int64, issue *issues_mo
} }
func updateIssueNotification(ctx context.Context, userID, issueID, commentID, updatedByID int64) error { 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 { if err != nil {
return err return err
} }
@ -340,7 +340,8 @@ func updateIssueNotification(ctx context.Context, userID, issueID, commentID, up
return err 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) notification := new(Notification)
_, err := db.GetEngine(ctx). _, err := db.GetEngine(ctx).
Where("user_id = ?", userID). Where("user_id = ?", userID).
@ -751,7 +752,7 @@ func GetUIDsAndNotificationCounts(since, until timeutil.TimeStamp) ([]UserIDCoun
// SetIssueReadBy sets issue to be read by given user. // SetIssueReadBy sets issue to be read by given user.
func SetIssueReadBy(ctx context.Context, issueID, userID int64) error { 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 return err
} }
@ -759,7 +760,7 @@ func SetIssueReadBy(ctx context.Context, issueID, userID int64) error {
} }
func setIssueNotificationStatusReadIfUnread(ctx context.Context, userID, issueID 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 // ignore if not exists
if err != nil { if err != nil {
return nil return nil
@ -771,7 +772,7 @@ func setIssueNotificationStatusReadIfUnread(ctx context.Context, userID, issueID
notification.Status = NotificationStatusRead 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 return err
} }

View file

@ -4,6 +4,7 @@
package activities_test package activities_test
import ( import (
"context"
"testing" "testing"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
@ -109,3 +110,16 @@ func TestUpdateNotificationStatuses(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, unittest.AssertExistsAndLoadBean(t,
&activities_model.Notification{ID: notfPinned.ID, Status: activities_model.NotificationStatusPinned}) &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)
}

View file

@ -20,3 +20,19 @@ func BuildCaseInsensitiveLike(key, value string) builder.Cond {
} }
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)} 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 ""
}
}

View file

@ -47,6 +47,7 @@ type Engine interface {
Incr(column string, arg ...any) *xorm.Session Incr(column string, arg ...any) *xorm.Session
Insert(...any) (int64, error) Insert(...any) (int64, error)
Iterate(any, xorm.IterFunc) error Iterate(any, xorm.IterFunc) error
IsTableExist(any) (bool, error)
Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session
SQL(any, ...any) *xorm.Session SQL(any, ...any) *xorm.Session
Where(any, ...any) *xorm.Session Where(any, ...any) *xorm.Session

View file

@ -68,6 +68,7 @@ func TestPrimaryKeys(t *testing.T) {
whitelist := map[string]string{ whitelist := map[string]string{
"the_table_name_to_skip_checking": "Write a note here to explain why", "the_table_name_to_skip_checking": "Write a note here to explain why",
"forgejo_sem_ver": "seriously dude",
} }
for _, bean := range beans { for _, bean := range beans {

View file

@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
package semver
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models/unittest"
_ "code.gitea.io/gitea/models"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", "..", ".."),
})
}

View file

@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
package semver
import (
"context"
"code.gitea.io/gitea/models/db"
"github.com/hashicorp/go-version"
)
func init() {
db.RegisterModel(new(ForgejoSemVer))
}
var DefaultVersionString = "1.0.0"
type ForgejoSemVer struct {
Version string
}
func GetVersion(ctx context.Context) (*version.Version, error) {
return GetVersionWithEngine(db.GetEngine(ctx))
}
func GetVersionWithEngine(e db.Engine) (*version.Version, error) {
versionString := DefaultVersionString
exists, err := e.IsTableExist("forgejo_sem_ver")
if err != nil {
return nil, err
}
if exists {
var semver ForgejoSemVer
has, err := e.Get(&semver)
if err != nil {
return nil, err
} else if has {
versionString = semver.Version
}
}
v, err := version.NewVersion(versionString)
if err != nil {
return nil, err
}
return v, nil
}
func SetVersionString(ctx context.Context, versionString string) error {
return SetVersionStringWithEngine(db.GetEngine(ctx), versionString)
}
func SetVersionStringWithEngine(e db.Engine, versionString string) error {
v, err := version.NewVersion(versionString)
if err != nil {
return err
}
return SetVersionWithEngine(e, v)
}
func SetVersion(ctx context.Context, v *version.Version) error {
return SetVersionWithEngine(db.GetEngine(ctx), v)
}
func SetVersionWithEngine(e db.Engine, v *version.Version) error {
var semver ForgejoSemVer
has, err := e.Get(&semver)
if err != nil {
return err
}
if !has {
_, err = e.Exec("insert into forgejo_sem_ver values (?)", v.String())
} else {
_, err = e.Exec("update forgejo_sem_ver set version = ?", v.String())
}
return err
}

View file

@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
package semver
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"github.com/hashicorp/go-version"
"github.com/stretchr/testify/assert"
)
func TestForgejoSemVerSetGet(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx := db.DefaultContext
newVersion, err := version.NewVersion("v1.2.3")
assert.NoError(t, err)
assert.NoError(t, SetVersionString(ctx, newVersion.String()))
databaseVersion, err := GetVersion(ctx)
assert.NoError(t, err)
assert.EqualValues(t, newVersion.String(), databaseVersion.String())
assert.True(t, newVersion.Equal(databaseVersion))
}
func TestForgejoSemVerMissing(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx := db.DefaultContext
e := db.GetEngine(ctx)
_, err := e.Exec("delete from forgejo_sem_ver")
assert.NoError(t, err)
v, err := GetVersion(ctx)
assert.NoError(t, err)
assert.EqualValues(t, "1.0.0", v.String())
_, err = e.Exec("drop table forgejo_sem_ver")
assert.NoError(t, err)
v, err = GetVersion(ctx)
assert.NoError(t, err)
assert.EqualValues(t, "1.0.0", v.String())
}

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"code.gitea.io/gitea/models/forgejo/semver"
forgejo_v1_20 "code.gitea.io/gitea/models/forgejo_migrations/v1_20" forgejo_v1_20 "code.gitea.io/gitea/models/forgejo_migrations/v1_20"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -37,6 +38,8 @@ func NewMigration(desc string, fn func(*xorm.Engine) error) *Migration {
// Add new migrations to the bottom of the list. // Add new migrations to the bottom of the list.
var migrations = []*Migration{ var migrations = []*Migration{
NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser), NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser),
// v2 -> v3
NewMigration("create the forgejo_sem_ver table", forgejo_v1_20.CreateSemVerTable),
} }
// GetCurrentDBVersion returns the current Forgejo database version. // GetCurrentDBVersion returns the current Forgejo database version.
@ -138,5 +141,10 @@ func Migrate(x *xorm.Engine) error {
return err return err
} }
} }
return nil
if err := x.Sync(new(semver.ForgejoSemVer)); err != nil {
return fmt.Errorf("sync: %w", err)
}
return semver.SetVersionStringWithEngine(x, setting.ForgejoVersion)
} }

View file

@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
package forgejo_v1_20 //nolint:revive
import (
"xorm.io/xorm"
)
func CreateSemVerTable(x *xorm.Engine) error {
type ForgejoSemVer struct {
Version string
}
return x.Sync(new(ForgejoSemVer))
}

View file

@ -283,9 +283,9 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
Where("repo_id = ?", repoID).And("sha = ?", sha). Where("repo_id = ?", repoID).And("sha = ?", sha).
Select("max( id ) as id"). Select("max( id ) as id").
GroupBy("context_hash").OrderBy("max( id ) desc") GroupBy("context_hash").OrderBy("max( id ) desc")
if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions) sess = db.SetSessionPagination(sess, &listOptions)
}
count, err := sess.FindAndCount(&ids) count, err := sess.FindAndCount(&ids)
if err != nil { if err != nil {
return nil, count, err return nil, count, err

View file

@ -155,6 +155,18 @@ func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sess
return 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 { func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
if len(opts.RepoIDs) == 1 { if len(opts.RepoIDs) == 1 {
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]} 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}) sess.And(builder.Lte{"issue.updated_unix": opts.UpdatedBeforeUnix})
} }
if opts.ProjectID > 0 { applyProjectCondition(sess, opts)
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")))
}
if opts.ProjectBoardID != 0 { if opts.ProjectBoardID != 0 {
if opts.ProjectBoardID > 0 { if opts.ProjectBoardID > 0 {

View file

@ -133,10 +133,7 @@ func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, err
applyMilestoneCondition(sess, opts) applyMilestoneCondition(sess, opts)
if opts.ProjectID > 0 { applyProjectCondition(sess, opts)
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
And("project_issue.project_id=?", opts.ProjectID)
}
if opts.AssigneeID > 0 { if opts.AssigneeID > 0 {
applyAssigneeCondition(sess, opts.AssigneeID) applyAssigneeCondition(sess, opts.AssigneeID)

View file

@ -55,8 +55,8 @@ func NewIssueUsers(ctx context.Context, repo *repo_model.Repository, issue *Issu
} }
// UpdateIssueUserByRead updates issue-user relation for reading. // UpdateIssueUserByRead updates issue-user relation for reading.
func UpdateIssueUserByRead(uid, issueID int64) error { func UpdateIssueUserByRead(ctx context.Context, uid, issueID int64) error {
_, err := db.GetEngine(db.DefaultContext).Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID) _, err := db.GetEngine(ctx).Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
return err return err
} }

View file

@ -40,13 +40,13 @@ func TestUpdateIssueUserByRead(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) 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") 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") 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) { func TestUpdateIssueUsersByMentions(t *testing.T) {

View file

@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
forgejo_services "code.gitea.io/gitea/services/forgejo"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/names" "xorm.io/xorm/names"
@ -554,6 +555,7 @@ func Migrate(x *xorm.Engine) error {
return fmt.Errorf("sync: %w", err) return fmt.Errorf("sync: %w", err)
} }
var previousVersion int64
currentVersion := &Version{ID: 1} currentVersion := &Version{ID: 1}
has, err := x.Get(currentVersion) has, err := x.Get(currentVersion)
if err != nil { if err != nil {
@ -567,6 +569,8 @@ func Migrate(x *xorm.Engine) error {
if _, err = x.InsertOne(currentVersion); err != nil { if _, err = x.InsertOne(currentVersion); err != nil {
return fmt.Errorf("insert: %w", err) return fmt.Errorf("insert: %w", err)
} }
} else {
previousVersion = currentVersion.Version
} }
v := currentVersion.Version v := currentVersion.Version
@ -595,6 +599,10 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
} }
} }
if err := forgejo_services.PreMigrationSanityChecks(x, previousVersion, setting.CfgProvider); err != nil {
return err
}
// Migrate // Migrate
for i, m := range migrations[v-minDBVersion:] { for i, m := range migrations[v-minDBVersion:] {
log.Info("Migration[%d]: %s", v+int64(i), m.Description()) log.Info("Migration[%d]: %s", v+int64(i), m.Description())

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
}

View file

@ -189,7 +189,7 @@ type PackageSearchOptions struct {
db.Paginator db.Paginator
} }
func (opts *PackageSearchOptions) toConds() builder.Cond { func (opts *PackageSearchOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if !opts.IsInternal.IsNone() { if !opts.IsInternal.IsNone() {
cond = builder.Eq{ cond = builder.Eq{
@ -283,7 +283,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
// SearchVersions gets all versions of packages matching the search options // SearchVersions gets all versions of packages matching the search options
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Where(opts.toConds()). Where(opts.ToConds()).
Table("package_version"). Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id") 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 // SearchLatestVersions gets the latest version of every package matching the search options
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) { func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
cond := opts.toConds(). cond := opts.ToConds().
And(builder.Expr("pv2.id IS NULL")) 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))") 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 // ExistVersion checks if a version matching the search options exist
func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) { func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
return db.GetEngine(ctx). return db.GetEngine(ctx).
Where(opts.toConds()). Where(opts.ToConds()).
Table("package_version"). Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id"). Join("INNER", "package", "package.id = package_version.package_id").
Exist(new(PackageVersion)) 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 // CountVersions counts all versions of packages matching the search options
func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) { func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) {
return db.GetEngine(ctx). return db.GetEngine(ctx).
Where(opts.toConds()). Where(opts.ToConds()).
Table("package_version"). Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id"). Join("INNER", "package", "package.id = package_version.package_id").
Count(new(PackageVersion)) Count(new(PackageVersion))

View file

@ -22,7 +22,7 @@ func init() {
db.RegisterModel(new(RepoTopic)) 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 // Topic represents a topic of repositories
type Topic struct { type Topic struct {

View file

@ -69,6 +69,7 @@ func TestAddTopic(t *testing.T) {
func TestTopicValidator(t *testing.T) { func TestTopicValidator(t *testing.T) {
assert.True(t, repo_model.ValidateTopic("12345")) assert.True(t, repo_model.ValidateTopic("12345"))
assert.True(t, repo_model.ValidateTopic("2-test")) 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("test-3"))
assert.True(t, repo_model.ValidateTopic("first")) assert.True(t, repo_model.ValidateTopic("first"))
assert.True(t, repo_model.ValidateTopic("second-test-topic")) 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("$fourth-test,topic"))
assert.False(t, repo_model.ValidateTopic("-fifth-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("sixth-go-project-topic-with-excess-length"))
assert.False(t, repo_model.ValidateTopic(".foo"))
} }

View file

@ -203,11 +203,16 @@ func UpdateUserTheme(u *User, themeName string) error {
return UpdateUserCols(db.DefaultContext, u, "theme") 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 // GetEmail returns an noreply email, if the user has set to keep his
// email address private, otherwise the primary email address. // email address private, otherwise the primary email address.
func (u *User) GetEmail() string { func (u *User) GetEmail() string {
if u.KeepEmailPrivate { if u.KeepEmailPrivate {
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress) return u.GetPlaceholderEmail()
} }
return u.Email return u.Email
} }

View file

@ -143,6 +143,10 @@ func (b *Base) Params(p string) string {
return s 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 // ParamsInt64 returns the param on route as int64
func (b *Base) ParamsInt64(p string) int64 { func (b *Base) ParamsInt64(p string) int64 {
v, _ := strconv.ParseInt(b.Params(p), 10, 64) v, _ := strconv.ParseInt(b.Params(p), 10, 64)

View file

@ -74,6 +74,8 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
Stdin: batchStdinReader, Stdin: batchStdinReader,
Stdout: batchStdoutWriter, Stdout: batchStdoutWriter,
Stderr: &stderr, Stderr: &stderr,
UseContextTimeout: true,
}) })
if err != nil { if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
@ -124,6 +126,8 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
Stdin: batchStdinReader, Stdin: batchStdinReader,
Stdout: batchStdoutWriter, Stdout: batchStdoutWriter,
Stderr: &stderr, Stderr: &stderr,
UseContextTimeout: true,
}) })
if err != nil { if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))

View file

@ -5,11 +5,14 @@ package git
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"fmt" "fmt"
"io" "io"
"os" "os"
"regexp" "regexp"
"code.gitea.io/gitea/modules/log"
) )
// BlamePart represents block of blame - continuous lines with one sha // 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) done := make(chan error, 1)
go func(cmd *Command, dir string, stdout io.WriteCloser, done chan error) { 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, UseContextTimeout: true,
Dir: dir, Dir: dir,
Stdout: stdout, Stdout: stdout,
Stderr: os.Stderr, Stderr: &stderr,
}); err == nil { })
stdout.Close()
}
done <- err 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) }(cmd, repoPath, stdout, done)
bufferedReader := bufio.NewReader(reader) bufferedReader := bufio.NewReader(reader)

View file

@ -80,7 +80,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error {
// IsEmpty Check if repository is empty. // IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) { func (repo *Repository) IsEmpty() (bool, error) {
var errbuf, output strings.Builder 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{ Run(&RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: &output, Stdout: &output,

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 // 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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -119,3 +119,42 @@ func TestReadWritePullHead(t *testing.T) {
err = repo.RemoveReference(PullPrefix + "1/head") err = repo.RemoveReference(PullPrefix + "1/head")
assert.NoError(t, err) 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)
}
}

View file

@ -11,10 +11,10 @@ import (
"strings" "strings"
) )
// EmptySHA defines empty git SHA // EmptySHA defines empty git SHA (undefined, non-existent)
const EmptySHA = "0000000000000000000000000000000000000000" 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" const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
// SHAFullLength is the full length of a git SHA // SHAFullLength is the full length of a git SHA

View file

@ -153,18 +153,30 @@ func (r *Writer) WriteRegularLink(l org.RegularLink) {
link = []byte(util.URLJoin(r.URLPrefix, lnk)) link = []byte(util.URLJoin(r.URLPrefix, lnk))
} }
description := string(link)
if l.Description != nil {
description = r.WriteNodesAsString(l.Description...)
}
switch l.Kind() { switch l.Kind() {
case "image": case "image":
imageSrc := getMediaURL(link) if l.Description == nil {
fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, description, description) imageSrc := getMediaURL(link)
fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, link, link)
} else {
description := strings.TrimPrefix(org.String(l.Description), "file:")
imageSrc := getMediaURL([]byte(description))
fmt.Fprintf(r, `<a href="%s"><img src="%s" alt="%s" /></a>`, link, imageSrc, imageSrc)
}
case "video": case "video":
videoSrc := getMediaURL(link) if l.Description == nil {
fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, videoSrc, description, description) imageSrc := getMediaURL(link)
fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, imageSrc, link, link)
} else {
description := strings.TrimPrefix(org.String(l.Description), "file:")
videoSrc := getMediaURL([]byte(description))
fmt.Fprintf(r, `<a href="%s"><video src="%s" title="%s"></video></a>`, link, videoSrc, videoSrc)
}
default: default:
description := string(link)
if l.Description != nil {
description = r.WriteNodesAsString(l.Description...)
}
fmt.Fprintf(r, `<a href="%s" title="%s">%s</a>`, link, description, description) fmt.Fprintf(r, `<a href="%s" title="%s">%s</a>`, link, description, description)
} }
} }

View file

@ -42,7 +42,7 @@ func TestRender_StandardLinks(t *testing.T) {
"<p><a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a></p>") "<p><a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a></p>")
} }
func TestRender_Images(t *testing.T) { func TestRender_Media(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
setting.AppSubURL = AppSubURL setting.AppSubURL = AppSubURL
@ -60,6 +60,18 @@ func TestRender_Images(t *testing.T) {
test("[[file:"+url+"]]", test("[[file:"+url+"]]",
"<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>") "<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>")
// With description.
test("[[https://example.com][https://example.com/example.svg]]",
`<p><a href="https://example.com"><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></a></p>`)
test("[[https://example.com][https://example.com/example.mp4]]",
`<p><a href="https://example.com"><video src="https://example.com/example.mp4" title="https://example.com/example.mp4"></video></a></p>`)
// Without description.
test("[[https://example.com/example.svg]]",
`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" title="https://example.com/example.svg" /></p>`)
test("[[https://example.com/example.mp4]]",
`<p><video src="https://example.com/example.mp4" title="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`)
} }
func TestRender_Source(t *testing.T) { func TestRender_Source(t *testing.T) {

View file

@ -172,19 +172,10 @@ func ParseControlFile(r io.Reader) (*Package, error) {
value := strings.TrimSpace(parts[1]) value := strings.TrimSpace(parts[1])
switch key { switch key {
case "Package": case "Package":
if !namePattern.MatchString(value) {
return nil, ErrInvalidName
}
p.Name = value p.Name = value
case "Version": case "Version":
if !versionPattern.MatchString(value) {
return nil, ErrInvalidVersion
}
p.Version = value p.Version = value
case "Architecture": case "Architecture":
if value == "" {
return nil, ErrInvalidArchitecture
}
p.Architecture = value p.Architecture = value
case "Maintainer": case "Maintainer":
a, err := mail.ParseAddress(value) a, err := mail.ParseAddress(value)
@ -208,13 +199,23 @@ func ParseControlFile(r io.Reader) (*Package, error) {
return nil, err 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(), ",") dependencies := strings.Split(depends.String(), ",")
for i := range dependencies { for i := range dependencies {
dependencies[i] = strings.TrimSpace(dependencies[i]) dependencies[i] = strings.TrimSpace(dependencies[i])
} }
p.Metadata.Dependencies = dependencies p.Metadata.Dependencies = dependencies
p.Control = control.String() p.Control = strings.TrimSpace(control.String())
return p, nil return p, nil
} }

View file

@ -4,6 +4,7 @@
package setting package setting
import ( import (
"bytes"
"os" "os"
"regexp" "regexp"
"strconv" "strconv"
@ -133,6 +134,11 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
log.Error("Error reading file for %s : %v", envKey, envValue, err) log.Error("Error reading file for %s : %v", envKey, envValue, err)
continue 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) keyValue = string(fileContent)
} }

View file

@ -106,4 +106,19 @@ key = old
changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile}) changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
assert.True(t, changed) assert.True(t, changed)
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String()) 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())
} }

View file

@ -53,6 +53,7 @@ type ConfigProvider interface {
Save() error Save() error
SaveTo(filename string) error SaveTo(filename string) error
GetFile() string
DisableSaving() DisableSaving()
PrepareSaving() (ConfigProvider, error) PrepareSaving() (ConfigProvider, error)
IsLoadedFromEmpty() bool IsLoadedFromEmpty() bool
@ -251,6 +252,10 @@ func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) {
var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save") var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save")
func (p *iniConfigProvider) GetFile() string {
return p.file
}
// Save saves the content into file // Save saves the content into file
func (p *iniConfigProvider) Save() error { func (p *iniConfigProvider) Save() error {
if p.disableSaving { if p.disableSaving {

View file

@ -63,7 +63,7 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
LFS.JWTSecretBytes = make([]byte, 32) LFS.JWTSecretBytes = make([]byte, 32)
n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64)) n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
if err != nil || n != 32 { if (err != nil || n != 32) && InstallLock {
LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64() LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
if err != nil { if err != nil {
return fmt.Errorf("error generating JWT Secret for custom config: %v", err) return fmt.Errorf("error generating JWT Secret for custom config: %v", err)

View file

@ -165,7 +165,7 @@ func loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (wri
writerMode.WriterOption = writerOption writerMode.WriterOption = writerOption
default: default:
if !log.HasEventWriter(writerType) { 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)
} }
} }

View file

@ -15,6 +15,8 @@ import (
"code.gitea.io/gitea/modules/user" "code.gitea.io/gitea/modules/user"
) )
var ForgejoVersion = "1.0.0"
// settings // settings
var ( var (
// AppVer is the version of the current build of Gitea. It is set in main.go from main.Version. // AppVer is the version of the current build of Gitea. It is set in main.go from main.Version.

View file

@ -84,102 +84,179 @@ func getDefaultStorageSection(rootCfg ConfigProvider) ConfigSection {
return storageSec 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) { func getStorage(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (*Storage, error) {
if name == "" { if name == "" {
return nil, errors.New("no name for storage") return nil, errors.New("no name for storage")
} }
var targetSec ConfigSection targetSec, tp, err := getStorageTargetSection(rootCfg, name, typ, sec)
if typ != "" { if err != nil {
var err error return nil, err
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)
}
}
} }
storageNameSec, _ := rootCfg.GetSection(storageSectionName + "." + name) overrideSec := getStorageOverrideSection(rootCfg, targetSec, sec, tp, name)
if targetSec == nil { targetType := targetSec.Key("STORAGE_TYPE").String()
targetSec = sec 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
} type targetSecType int
if targetSec == nil {
targetSec = getDefaultStorageSection(rootCfg) const (
} else { targetSecIsTyp targetSecType = iota // target section is [storage.type] which the type from parameter
targetType := targetSec.Key("STORAGE_TYPE").String() targetSecIsStorage // target section is [storage]
switch { targetSecIsDefault // target section is the default value
case targetType == "": targetSecIsStorageWithName // target section is [storage.name]
if targetSec.Key("PATH").String() == "" { targetSecIsSec // target section is from the name seciont [name]
targetSec = getDefaultStorageSection(rootCfg) )
} else {
targetSec.Key("STORAGE_TYPE").SetValue("local") func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) {
} targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
default: if err != nil {
newTargetSec, _ := rootCfg.GetSection(storageSectionName + "." + targetType) if !IsValidStorageType(StorageType(typ)) {
if newTargetSec == nil { return nil, 0, fmt.Errorf("get section via storage type %q failed: %v", typ, err)
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)
}
}
}
} }
// 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() targetType := targetSec.Key("STORAGE_TYPE").String()
if !IsValidStorageType(StorageType(targetType)) { if targetType == "" {
return nil, fmt.Errorf("invalid storage type %q", 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 return targetSec, targetSecIsTyp, nil
storage.Type = StorageType(targetType) }
switch targetType { func getStorageTargetSection(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (ConfigSection, targetSecType, error) {
case string(LocalStorageType): // check typ first
storage.Path = ConfigSectionKeyString(targetSec, "PATH", filepath.Join(AppDataPath, name)) if typ == "" {
if !filepath.IsAbs(storage.Path) { if sec != nil { // check sec's type secondly
storage.Path = filepath.Join(AppWorkPath, storage.Path) 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 { if typ != "" {
return nil, fmt.Errorf("map minio config failed: %v", err) 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 { // check stoarge name thirdly
extraConfigSec = storageNameSec 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 { return targetSec, targetSecIsStorageWithName, 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 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 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
}

View file

@ -4,6 +4,7 @@
package setting package setting
import ( import (
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -26,12 +27,15 @@ MINIO_BUCKET = gitea-storage
assert.NoError(t, loadAttachmentFrom(cfg)) assert.NoError(t, loadAttachmentFrom(cfg))
assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
assert.NoError(t, loadLFSFrom(cfg)) assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket) assert.EqualValues(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
assert.NoError(t, loadAvatarsFrom(cfg)) assert.NoError(t, loadAvatarsFrom(cfg))
assert.EqualValues(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket) assert.EqualValues(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "avatars/", Avatar.Storage.MinioConfig.BasePath)
} }
func Test_getStorageUseOtherNameAsType(t *testing.T) { func Test_getStorageUseOtherNameAsType(t *testing.T) {
@ -48,9 +52,11 @@ MINIO_BUCKET = gitea-storage
assert.NoError(t, loadAttachmentFrom(cfg)) assert.NoError(t, loadAttachmentFrom(cfg))
assert.EqualValues(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket) assert.EqualValues(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath)
assert.NoError(t, loadLFSFrom(cfg)) assert.NoError(t, loadLFSFrom(cfg))
assert.EqualValues(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket) assert.EqualValues(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath)
} }
func Test_getStorageInheritStorageType(t *testing.T) { func Test_getStorageInheritStorageType(t *testing.T) {
@ -90,3 +96,319 @@ STORAGE_TYPE = minio
assert.EqualValues(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket) assert.EqualValues(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket)
assert.EqualValues(t, "repo-avatars/", RepoAvatar.Storage.MinioConfig.BasePath) 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)
}

View file

@ -17,6 +17,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -164,6 +165,10 @@ func sessionHandler(session ssh.Session) {
} }
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { 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 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()) 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 // look for the exact principal
principalLoop: principalLoop:
for _, principal := range cert.ValidPrincipals { for _, principal := range cert.ValidPrincipals {
pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal) pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal)
if err != nil { if err != nil {
if asymkey_model.IsErrKeyNotExist(err) { if asymkey_model.IsErrKeyNotExist(err) {
log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal) 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)) 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 err != nil {
if asymkey_model.IsErrKeyNotExist(err) { if asymkey_model.IsErrKeyNotExist(err) {
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())

View file

@ -84,17 +84,22 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""), Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
Secure: config.UseSSL, Secure: config.UseSSL,
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}}, Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
Region: config.Location,
}) })
if err != nil { if err != nil {
return nil, convertMinioErr(err) return nil, convertMinioErr(err)
} }
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{ // Check to see if we already own this bucket
Region: config.Location, exists, errBucketExists := minioClient.BucketExists(ctx, config.Bucket)
}); err != nil { if errBucketExists != nil {
// Check to see if we already own this bucket (which happens if you run this twice) return nil, convertMinioErr(err)
exists, errBucketExists := minioClient.BucketExists(ctx, config.Bucket) }
if !exists || errBucketExists != nil {
if !exists {
if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{
Region: config.Location,
}); err != nil {
return nil, convertMinioErr(err) return nil, convertMinioErr(err)
} }
} }

View file

@ -17,6 +17,12 @@ func IsNormalPageCompleted(s string) bool {
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`) 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 }
}
func MockVariable[T any](variable *T, mock T) func() { func MockVariable[T any](variable *T, mock T) func() {
original := *variable original := *variable
*variable = mock *variable = mock

View file

@ -71,7 +71,7 @@ func (ct SniffedType) IsRepresentableAsText() bool {
return ct.IsText() || ct.IsSvgImage() 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 { func (ct SniffedType) IsBrowsableBinaryType() bool {
return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio() 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} return SniffedType{ct}
} }

View file

@ -6,6 +6,7 @@ package typesniffer
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/hex"
"strings" "strings"
"testing" "testing"
@ -121,3 +122,15 @@ func TestDetectContentTypeFromReader(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, st.IsAudio()) 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())
}

View file

@ -2300,6 +2300,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.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.bot_token = Bot Token
settings.chat_id = Chat ID settings.chat_id = Chat ID
settings.thread_id = Thread ID
settings.matrix.homeserver_url = Homeserver URL settings.matrix.homeserver_url = Homeserver URL
settings.matrix.room_id = Room ID settings.matrix.room_id = Room ID
settings.matrix.message_type = Message Type settings.matrix.message_type = Message Type
@ -2500,7 +2501,7 @@ tag.create_success = Tag "%s" has been created.
topic.manage_topics = Manage Topics topic.manage_topics = Manage Topics
topic.done = Done topic.done = Done
topic.count_prompt = You cannot select more than 25 topics 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.go_to_file = Go to file
find_file.no_matching = No matching file found find_file.no_matching = No matching file found
@ -3491,3 +3492,12 @@ need_approval_desc = Need approval to run workflows for fork pull request.
type-1.display_name = Individual Project type-1.display_name = Individual Project
type-2.display_name = Repository Project type-2.display_name = Repository Project
type-3.display_name = Organization 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

View file

@ -9,6 +9,9 @@ import (
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget" 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 // 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) 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)) data := make([]*SearchResult, 0, len(pds))
for _, group := range grouped { for _, key := range keys {
data = append(data, createSearchResult(l, group)) data = append(data, createSearchResult(l, grouped[key]))
} }
return &SearchResultResponse{ return &SearchResultResponse{

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" 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/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" 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") skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
paginator := db.NewAbsoluteListOptions(skip, take) 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, OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet, Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{ 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 // 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) { 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, OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{ Name: packages_model.SearchValue{
Value: getSearchTerm(ctx), 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 // https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
func SearchServiceV3(ctx *context.Context) { 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, OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")}, Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
IsInternal: util.OptionalBoolFalse, IsInternal: util.OptionalBoolFalse,
Paginator: db.NewAbsoluteListOptions( Paginator: db.NewAbsoluteListOptions(

View file

@ -127,7 +127,7 @@ func EditWikiPage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateWikiPageOptions) 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) newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(newWikiName) == 0 { if len(newWikiName) == 0 {
@ -231,7 +231,7 @@ func DeleteWikiPage(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$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 := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
if err.Error() == "file does not exist" { if err.Error() == "file does not exist" {
@ -359,7 +359,7 @@ func GetWikiPage(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
// get requested pagename // get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params(":pageName")) pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
wikiPage := getWikiPage(ctx, pageName) wikiPage := getWikiPage(ctx, pageName)
if !ctx.Written() { if !ctx.Written() {
@ -409,7 +409,7 @@ func ListPageRevisions(ctx *context.APIContext) {
} }
// get requested pagename // get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params(":pageName")) pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
if len(pageName) == 0 { if len(pageName) == 0 {
pageName = "Home" pageName = "Home"
} }

View file

@ -16,7 +16,7 @@ func Queues(ctx *context.Context) {
if !setting.IsProd { if !setting.IsProd {
initTestQueueOnce() initTestQueueOnce()
} }
ctx.Data["Title"] = ctx.Tr("admin.monitor.queue") ctx.Data["Title"] = ctx.Tr("admin.monitor.queues")
ctx.Data["PageIsAdminMonitorQueue"] = true ctx.Data["PageIsAdminMonitorQueue"] = true
ctx.Data["Queues"] = queue.GetManager().ManagedQueues() ctx.Data["Queues"] = queue.GetManager().ManagedQueues()
ctx.HTML(http.StatusOK, tplQueue) ctx.HTML(http.StatusOK, tplQueue)

View file

@ -339,7 +339,7 @@ func Diff(ctx *context.Context) {
ctx.Data["Commit"] = commit ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff 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 { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)
} }

View file

@ -370,7 +370,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
} }
if ctx.Repo.Repository.IsEmpty { 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) redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
@ -763,7 +765,9 @@ func UploadFilePost(ctx *context.Context) {
} }
if ctx.Repo.Repository.IsEmpty { 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) redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)

View file

@ -421,7 +421,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
if len(compareInfo.Commits) != 0 { if len(compareInfo.Commits) != 0 {
sha := compareInfo.Commits[0].ID.String() 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 { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil 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) ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
return nil 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 { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil
@ -575,7 +575,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return nil 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 { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil

View file

@ -834,7 +834,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
ctx.Data["LatestCommitVerification"] = verification ctx.Data["LatestCommitVerification"] = verification
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit) 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 { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)
} }

View file

@ -453,12 +453,13 @@ func telegramHookParams(ctx *context.Context) webhookParams {
return webhookParams{ return webhookParams{
Type: webhook_module.TELEGRAM, 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, ContentType: webhook.ContentTypeJSON,
WebhookForm: form.WebhookForm, WebhookForm: form.WebhookForm,
Meta: &webhook_service.TelegramMeta{ Meta: &webhook_service.TelegramMeta{
BotToken: form.BotToken, BotToken: form.BotToken,
ChatID: form.ChatID, ChatID: form.ChatID,
ThreadID: form.ThreadID,
}, },
} }
} }

View file

@ -186,7 +186,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
ctx.Data["Pages"] = pages ctx.Data["Pages"] = pages
// get requested page name // get requested page name
pageName := wiki_service.WebPathFromRequest(ctx.Params("*")) pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 { if len(pageName) == 0 {
pageName = "Home" pageName = "Home"
} }
@ -333,7 +333,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
} }
// get requested pagename // get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params("*")) pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 { if len(pageName) == 0 {
pageName = "Home" pageName = "Home"
} }
@ -416,7 +416,7 @@ func renderEditPage(ctx *context.Context) {
}() }()
// get requested pagename // get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.Params("*")) pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 { if len(pageName) == 0 {
pageName = "Home" pageName = "Home"
} }
@ -648,7 +648,7 @@ func WikiRaw(ctx *context.Context) {
return return
} }
providedWebPath := wiki_service.WebPathFromRequest(ctx.Params("*")) providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
providedGitPath := wiki_service.WebPathToGitPath(providedWebPath) providedGitPath := wiki_service.WebPathToGitPath(providedWebPath)
var entry *git.TreeEntry var entry *git.TreeEntry
if commit != nil { if commit != nil {
@ -760,7 +760,7 @@ func EditWikiPost(ctx *context.Context) {
return return
} }
oldWikiName := wiki_service.WebPathFromRequest(ctx.Params("*")) oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
newWikiName := wiki_service.UserTitleToWebPath("", form.Title) newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(form.Message) == 0 { if len(form.Message) == 0 {
@ -779,7 +779,7 @@ func EditWikiPost(ctx *context.Context) {
// DeleteWikiPagePost delete wiki page // DeleteWikiPagePost delete wiki page
func DeleteWikiPagePost(ctx *context.Context) { func DeleteWikiPagePost(ctx *context.Context) {
wikiName := wiki_service.WebPathFromRequest(ctx.Params("*")) wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(wikiName) == 0 { if len(wikiName) == 0 {
wikiName = "Home" wikiName = "Home"
} }

View file

@ -53,7 +53,7 @@ func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) {
ctx.Data["Runners"] = runners ctx.Data["Runners"] = runners
ctx.Data["Total"] = count ctx.Data["Total"] = count
ctx.Data["RegistrationToken"] = token.Token ctx.Data["RegistrationToken"] = token.Token
ctx.Data["RunnerOnwerID"] = opts.OwnerID ctx.Data["RunnerOwnerID"] = opts.OwnerID
ctx.Data["RunnerRepoID"] = opts.RepoID ctx.Data["RunnerRepoID"] = opts.RepoID
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)

View file

@ -695,7 +695,7 @@ func getRepoIDs(reposQuery string) []int64 {
return []int64{} return []int64{}
} }
if !issueReposQueryPattern.MatchString(reposQuery) { if !issueReposQueryPattern.MatchString(reposQuery) {
log.Warn("issueReposQueryPattern does not match query") log.Warn("issueReposQueryPattern does not match query: %q", reposQuery)
return []int64{} return []int64{}
} }

View file

@ -120,6 +120,7 @@ func Profile(ctx *context.Context) {
profileContent, err := markdown.RenderString(&markup.RenderContext{ profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx, Ctx: ctx,
GitRepo: gitRepo, GitRepo: gitRepo,
Metas: map[string]string{"mode": "document"},
}, bytes) }, bytes)
if err != nil { if err != nil {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)

View file

@ -302,8 +302,8 @@ func registerRoutes(m *web.Route) {
} }
addWebhookEditRoutes := func() { addWebhookEditRoutes := func() {
m.Post("/forgejo/{id}", web.Bind(forms.NewWebhookForm{}), repo.ForgejoHooksEditPost)
m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost)
m.Post("/forgejo/{id}", web.Bind(forms.NewWebhookForm{}), repo.ForgejoHooksEditPost)
m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
@ -1133,7 +1133,7 @@ func registerRoutes(m *web.Route) {
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom) m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed), }, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true)) 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.Group("/releases", func() {
m.Get("/new", repo.NewRelease) m.Get("/new", repo.NewRelease)
m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost) m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)

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) ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
state := toCommitStatus(job.Status) 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 { for _, v := range statuses {
if v.Context == ctxname { if v.Context == ctxname {
if v.State == state { if v.State == state {

View file

@ -56,7 +56,7 @@ func (p *AuthSourceProvider) DisplayName() string {
func (p *AuthSourceProvider) IconHTML() template.HTML { func (p *AuthSourceProvider) IconHTML() template.HTML {
if p.iconURL != "" { 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()), html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
) )
return template.HTML(img) return template.HTML(img)

View file

@ -51,7 +51,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
ID: user.ID, ID: user.ID,
UserName: user.Name, UserName: user.Name,
FullName: user.FullName, FullName: user.FullName,
Email: user.GetEmail(), Email: user.GetPlaceholderEmail(),
AvatarURL: user.AvatarLink(ctx), AvatarURL: user.AvatarLink(ctx),
Created: user.CreatedUnix.AsTime(), Created: user.CreatedUnix.AsTime(),
Restricted: user.IsRestricted, Restricted: user.IsRestricted,

View file

@ -96,6 +96,12 @@ func ListTasks() TaskTable {
next = e.Next next = e.Next
prev = e.Prev prev = e.Prev
} }
// If the manual run is after the cron run, use that instead.
if prev.Before(task.LastRun) {
prev = task.LastRun
}
task.lock.Lock() task.lock.Lock()
tTable = append(tTable, &TaskTableRow{ tTable = append(tTable, &TaskTableRow{
Name: task.Name, Name: task.Name,

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"sync" "sync"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
system_model "code.gitea.io/gitea/models/system" system_model "code.gitea.io/gitea/models/system"
@ -36,6 +37,8 @@ type Task struct {
LastMessage string LastMessage string
LastDoer string LastDoer string
ExecTimes int64 ExecTimes int64
// This stores the time of the last manual run of this task.
LastRun time.Time
} }
// DoRunAtStart returns if this task should run at the start // DoRunAtStart returns if this task should run at the start
@ -87,6 +90,12 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
} }
}() }()
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) { graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
// Store the time of this run, before the function is executed, so it
// matches the behavior of what the cron library does.
t.lock.Lock()
t.LastRun = time.Now()
t.lock.Unlock()
pm := process.GetManager() pm := process.GetManager()
doerName := "" doerName := ""
if doer != nil && doer.ID != -1 { if doer != nil && doer.ID != -1 {

View file

@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
package forgejo
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models/unittest"
_ "code.gitea.io/gitea/models"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", ".."),
})
}

View file

@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
package forgejo
import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
var (
ForgejoV6DatabaseVersion = int64(261) // must be updated once v6 / Gitea v1.21 is out
ForgejoV5DatabaseVersion = int64(260)
ForgejoV4DatabaseVersion = int64(244)
)
var logFatal = log.Fatal
func fatal(err error) error {
logFatal("%v", err)
return err
}
func PreMigrationSanityChecks(e db.Engine, dbVersion int64, cfg setting.ConfigProvider) error {
return v1TOv5_0_1Included(e, dbVersion, cfg)
}

View file

@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
package forgejo
import (
"os"
"path/filepath"
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func TestForgejo_PreMigrationSanityChecks(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx := db.DefaultContext
e := db.GetEngine(ctx)
assert.NoError(t, PreMigrationSanityChecks(e, ForgejoV4DatabaseVersion, configFixture(t, "")))
}
func configFixture(t *testing.T, content string) setting.ConfigProvider {
config := filepath.Join(t.TempDir(), "app.ini")
assert.NoError(t, os.WriteFile(config, []byte(content), 0o777))
cfg, err := setting.NewConfigProviderFromFile(config)
assert.NoError(t, err)
return cfg
}

View file

@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT
package forgejo
import (
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/forgejo/semver"
"code.gitea.io/gitea/modules/setting"
"github.com/hashicorp/go-version"
)
var v1TOv5_0_1IncludedStorageSections = []struct {
section string
storageSection string
}{
{"attachment", "storage.attachments"},
{"lfs", "storage.lfs"},
{"avatar", "storage.avatars"},
{"repo-avatar", "storage.repo-avatars"},
{"repo-archive", "storage.repo-archive"},
{"packages", "storage.packages"},
// the actions sections are not included here because they were experimental at the time
}
func v1TOv5_0_1Included(e db.Engine, dbVersion int64, cfg setting.ConfigProvider) error {
//
// When upgrading from Forgejo > v5 or Gitea > v1.20, no sanity check is necessary
//
if dbVersion > ForgejoV5DatabaseVersion {
return nil
}
//
// When upgrading from a Forgejo point version >= v5.0.1, no sanity
// check is necessary
//
// When upgrading from a Gitea >= v1.20 the sanitiy checks will
// always be done They are necessary for Gitea [v1.20.0..v1.20.2]
// but not for [v1.20.3..] but there is no way to know which point
// release was running prior to the upgrade. This may require the
// Gitea admin to update their app.ini although it is not necessary
// but will have no other consequence.
//
previousServerVersion, err := semver.GetVersionWithEngine(e)
if err != nil {
return err
}
upper, err := version.NewVersion("v5.0.1")
if err != nil {
return err
}
if previousServerVersion.GreaterThan(upper) {
return nil
}
//
// Sanity checks
//
originalCfg, err := cfg.PrepareSaving()
if err != nil {
return err
}
messages := make([]string, 0, 10)
for _, c := range v1TOv5_0_1IncludedStorageSections {
section, _ := originalCfg.GetSection(c.section)
if section == nil {
continue
}
storageSection, _ := originalCfg.GetSection(c.storageSection)
if storageSection == nil {
continue
}
messages = append(messages, fmt.Sprintf("[%s] and [%s] may conflict with each other", c.section, c.storageSection))
}
if originalCfg.Section("storage").HasKey("PATH") {
messages = append(messages, "[storage].PATH is set and may create storage issues")
}
if len(messages) > 0 {
return fatal(fmt.Errorf("%s\nThese issues need to be manually fixed in the app.ini file at %s. Please read https://forgejo.org/2023-08-release-v1-20-3-0/ for instructions", strings.Join(messages, "\n"), cfg.GetFile()))
}
return nil
}

View file

@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
package forgejo
import (
"fmt"
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/forgejo/semver"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
)
func TestForgejo_v1TOv5_0_1Included(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
logFatal = func(string, ...any) {}
defer func() {
logFatal = log.Fatal
}()
configWithSoragePath := `
[storage]
PATH = /something
`
verifyForgejoV1TOv5_0_1Included(t, configWithSoragePath, "[storage].PATH is set")
for _, c := range v1TOv5_0_1IncludedStorageSections {
config := fmt.Sprintf("[%s]\n[%s]\n", c.section, c.storageSection)
verifyForgejoV1TOv5_0_1Included(t, config, fmt.Sprintf("[%s] and [%s]", c.section, c.storageSection))
}
}
func verifyForgejoV1TOv5_0_1Included(t *testing.T, config, message string) {
ctx := db.DefaultContext
e := db.GetEngine(ctx)
for _, testCase := range []struct {
name string
dbVersion int64
semver string
config string
}{
{
name: "5.0.0 with no " + message,
dbVersion: ForgejoV5DatabaseVersion,
semver: "5.0.0+0-gitea-1.20.1",
config: "",
},
{
name: "5.0.1 with no " + message,
dbVersion: ForgejoV5DatabaseVersion,
semver: "5.0.1+0-gitea-1.20.2",
config: "",
},
{
name: "5.0.2 with " + message,
dbVersion: ForgejoV5DatabaseVersion,
semver: "5.0.2+0-gitea-1.20.3",
config: config,
},
{
name: "6.0.0 with " + message,
dbVersion: ForgejoV6DatabaseVersion,
semver: "6.0.0+0-gitea-1.21.0",
config: config,
},
} {
cfg := configFixture(t, testCase.config)
semver.SetVersionString(ctx, testCase.semver)
assert.NoError(t, v1TOv5_0_1Included(e, testCase.dbVersion, cfg))
}
for _, testCase := range []struct {
name string
dbVersion int64
semver string
config string
}{
{
name: "5.0.0 with " + message,
dbVersion: ForgejoV5DatabaseVersion,
semver: "5.0.0+0-gitea-1.20.1",
config: config,
},
{
name: "5.0.1 with " + message,
dbVersion: ForgejoV5DatabaseVersion,
semver: "5.0.1+0-gitea-1.20.2",
config: config,
},
{
//
// When upgrading from
//
// Forgejo >= 5.0.1+0-gitea-1.20.2
// Gitea > v1.21
//
// The version that the server was running prior to the upgrade
// is not available.
//
name: semver.DefaultVersionString + " with " + message,
dbVersion: ForgejoV4DatabaseVersion,
semver: semver.DefaultVersionString,
config: config,
},
} {
cfg := configFixture(t, testCase.config)
semver.SetVersionString(ctx, testCase.semver)
assert.ErrorContains(t, v1TOv5_0_1Included(e, testCase.dbVersion, cfg), message)
}
}

View file

@ -352,6 +352,7 @@ func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) b
type NewTelegramHookForm struct { type NewTelegramHookForm struct {
BotToken string `binding:"Required"` BotToken string `binding:"Required"`
ChatID string `binding:"Required"` ChatID string `binding:"Required"`
ThreadID string
WebhookForm WebhookForm
} }

View file

@ -427,6 +427,23 @@ func (diffFile *DiffFile) ShouldBeHidden() bool {
return diffFile.IsGenerated || diffFile.IsViewed 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 { func getCommitFileLineCount(commit *git.Commit, filePath string) int {
blob, err := commit.GetBlobByPath(filePath) blob, err := commit.GetBlobByPath(filePath)
if err != nil { if err != nil {

View file

@ -247,7 +247,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
issue.MilestoneID, err) 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 return err
} }

View file

@ -212,7 +212,7 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa
} }
addSeparator = true 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, "Filename: pool/%s/%s/%s\n", distribution, component, pfd.File.Name)
fmt.Fprintf(w, "Size: %d\n", pfd.Blob.Size) fmt.Fprintf(w, "Size: %d\n", pfd.Blob.Size)

View file

@ -143,7 +143,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
return "", errors.Wrap(err, "LoadBaseRepo") 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 { if err != nil {
return "", errors.Wrap(err, "GetLatestCommitStatus") return "", errors.Wrap(err, "GetLatestCommitStatus")
} }

View file

@ -799,7 +799,7 @@ func getAllCommitStatus(gitRepo *git.Repository, pr *issues_model.PullRequest) (
return nil, nil, shaErr 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) lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err return statuses, lastStatus, err
} }

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