Skip to content

Commit 8f9b8c2

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: (29 commits) [skip ci] Updated translations via Crowdin Support localized README (go-gitea#20508) Clean up and fix clone button script (go-gitea#20415) Add disable download source configuration (go-gitea#20548) Fix default merge style (go-gitea#20564) Update login methods in package docs (go-gitea#20561) Add missing Tabs on organisation/package view (Frontport go-gitea#20539) (go-gitea#20540) [skip ci] Updated licenses and gitignores Add setting `SQLITE_JOURNAL_MODE` to enable WAL (go-gitea#20535) Rework file highlight rendering and fix yaml copy-paste (go-gitea#19967) Add new API endpoints for push mirrors management (go-gitea#19841) WebAuthn CredentialID field needs to be increased in size (go-gitea#20530) Add latest commit's SHA to content response (go-gitea#20398) Improve token and secret key generation docs (go-gitea#20387) [skip ci] Updated translations via Crowdin Rework raw file http header logic (go-gitea#20484) Update lunny/levelqueue to prevent NPE when reads are performed after close (go-gitea#20534) Added guidance on file to choose to download (go-gitea#20474) [skip ci] Updated translations via Crowdin Ensure that all unmerged files are merged when conflict checking (go-gitea#20528) ...
2 parents bddab98 + 1a8d7d0 commit 8f9b8c2

File tree

139 files changed

+3690
-797
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+3690
-797
lines changed

custom/conf/app.example.ini

+4
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ USER = root
313313
;DB_TYPE = sqlite3
314314
;PATH= ; defaults to data/gitea.db
315315
;SQLITE_TIMEOUT = ; Query timeout defaults to: 500
316+
;SQLITE_JOURNAL_MODE = ; defaults to sqlite database default (often DELETE), can be used to enable WAL mode. https://www.sqlite.org/pragma.html#pragma_journal_mode
316317
;;
317318
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
318319
;;
@@ -878,6 +879,9 @@ ROUTER = console
878879
;; Allow deletion of unadopted repositories
879880
;ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES = false
880881

882+
;; Don't allow download source archive files from UI
883+
;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false
884+
881885
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
882886
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
883887
;[repository.editor]

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
7878
- `DEFAULT_BRANCH`: **main**: Default branch name of all repositories.
7979
- `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories
8080
- `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories
81+
- `DISABLE_DOWNLOAD_SOURCE_ARCHIVES`: **false**: Don't allow download source archive files from UI
8182

8283
### Repository - Editor (`repository.editor`)
8384

@@ -382,6 +383,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
382383
- `verify-ca`: Enable TLS with verification of the database server certificate against its root certificate.
383384
- `verify-full`: Enable TLS and verify the database server name matches the given certificate in either the `Common Name` or `Subject Alternative Name` fields.
384385
- `SQLITE_TIMEOUT`: **500**: Query timeout for SQLite3 only.
386+
- `SQLITE_JOURNAL_MODE`: **""**: Change journal mode for SQlite3. Can be used to enable [WAL mode](https://www.sqlite.org/wal.html) when high load causes write congestion. See [SQlite3 docs](https://www.sqlite.org/pragma.html#pragma_journal_mode) for possible values. Defaults to the default for the database file, often DELETE.
385387
- `ITERATE_BUFFER_SIZE`: **50**: Internal buffer size for iterating.
386388
- `CHARSET`: **utf8mb4**: For MySQL only, either "utf8" or "utf8mb4". NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this.
387389
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.

docs/content/doc/installation/from-binary.en-us.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,29 @@ embedded assets. This can be different for older releases.
2424

2525
## Download
2626

27-
Choose the file matching your platform from the [downloads page](https://dl.gitea.io/gitea/), copy the URL and replace the URL within the commands below:
27+
You can find the file matching your platform from the [downloads page](https://dl.gitea.io/gitea/) after navigating to the version you want to download.
28+
29+
### Choosing the right file
30+
31+
**For Linux**, you will likely want `linux-amd64`. It's for 64-bit Intel/AMD platforms, but there are other platforms available, including `arm64` (e.g. Raspberry PI 4), `386` (i.e. 32-bit), `arm-5`, and `arm-6`.
32+
33+
**For Windows**, you will likely want `windows-4.0-amd64`. It's for all modern versions of Windows, but there is also a `386` platform available designed for older, 32-bit versions of Windows.
34+
35+
*Note: there is also a `gogit-windows` file available that was created to help with some [performance problems](https://github.com/go-gitea/gitea/pull/15482) reported by some Windows users on older systems/versions. You should consider using this file if you're experiencing performance issues, and let us know if it improves performance.*
36+
37+
**For macOS**, you should choose `darwin-arm64` if your hardware uses Apple Silicon, or `darwin-amd64` for Intel.
38+
39+
### Downloading with wget
40+
41+
Copy the commands below and replace the URL within the one you wish to download.
2842

2943
```sh
3044
wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
3145
chmod +x gitea
3246
```
3347

48+
Note that the above command will download Gitea {{< version >}} for 64-bit Linux.
49+
3450
## Verify GPG signature
3551

3652
Gitea signs all binaries with a [GPG key](https://keys.openpgp.org/search?q=teabot%40gitea.io) to prevent against unwanted modification of binaries.

docs/content/doc/installation/with-docker.en-us.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,23 @@ services:
303303
- GITEA__mailer__PASSWD="""${GITEA__mailer__PASSWD:?GITEA__mailer__PASSWD not set}"""
304304
```
305305

306-
To set required TOKEN and SECRET values, consider using Gitea's built-in [generate utility functions](https://docs.gitea.io/en-us/command-line/#generate).
306+
Gitea will generate new secrets/tokens for every new installation automatically and write them into the app.ini. If you want to set the secrets/tokens manually, you can use the following docker commands to use of Gitea's built-in [generate utility functions](https://docs.gitea.io/en-us/command-line/#generate). Do not lose/change your SECRET_KEY after the installation, otherwise the encrypted data can not be decrypted anymore.
307+
308+
The following commands will output a new `SECRET_KEY` and `INTERNAL_TOKEN` to `stdout`, which you can then place in your environment variables.
309+
310+
```bash
311+
docker run -it --rm gitea/gitea:1 gitea generate secret SECRET_KEY
312+
docker run -it --rm gitea/gitea:1 gitea generate secret INTERNAL_TOKEN
313+
```
314+
315+
```yaml
316+
...
317+
services:
318+
server:
319+
environment:
320+
- GITEA__security__SECRET_KEY=[value returned by generate secret SECRET_KEY]
321+
- GITEA__security__INTERNAL_TOKEN=[value returned by generate secret INTERNAL_TOKEN]
322+
```
307323
308324
## SSH Container Passthrough
309325

docs/content/doc/packages/composer.en-us.md

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ curl --user your_username:your_password_or_token \
6060
https://gitea.example.com/api/packages/testuser/composer?version=1.0.3
6161
```
6262

63+
If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password.
64+
6365
The server responds with the following HTTP Status codes.
6466

6567
| HTTP Status Code | Meaning |

docs/content/doc/packages/conan.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ conan user --remote {remote} --password {password} {username}
3737
| -----------| ----------- |
3838
| `remote` | The remote name. |
3939
| `username` | Your Gitea username. |
40-
| `password` | Your Gitea password or a personal access token. |
40+
| `password` | Your Gitea password. If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. |
4141
| `owner` | The owner of the package. |
4242

4343
For example:

docs/content/doc/packages/container.en-us.md

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ To push an image or if the image is in a private registry, you have to authentic
3434
docker login gitea.example.com
3535
```
3636

37+
If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password.
38+
3739
## Image naming convention
3840

3941
Images must follow this naming convention:

docs/content/doc/packages/generic.en-us.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{packa
3737
| ----------------- | ----------- |
3838
| `owner` | The owner of the package. |
3939
| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). |
40-
| `package_version` | The package version as described in the [SemVer](https://semver.org/) spec. |
40+
| `package_version` | The package version, a non-empty string. |
4141
| `file_name` | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). |
4242

4343
Example request using HTTP Basic authentication:
@@ -48,6 +48,8 @@ curl --user your_username:your_password_or_token \
4848
https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0/file.bin
4949
```
5050

51+
If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password.
52+
5153
The server reponds with the following HTTP Status codes.
5254

5355
| HTTP Status Code | Meaning |

docs/content/doc/packages/helm.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ helm cm-push ./{chart_file}.tgz {repo}
4242
| Parameter | Description |
4343
| ------------ | ----------- |
4444
| `username` | Your Gitea username. |
45-
| `password` | Your Gitea password or a personal access token. |
45+
| `password` | Your Gitea password. If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. |
4646
| `repo` | The name for the repository. |
4747
| `chart_file` | The Helm Chart archive. |
4848
| `owner` | The owner of the package. |

docs/content/doc/packages/nuget.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dotnet nuget add source --name {source_name} --username {username} --password {p
3838
| ------------- | ----------- |
3939
| `source_name` | The desired source name. |
4040
| `username` | Your Gitea username. |
41-
| `password` | Your Gitea password or a personal access token. |
41+
| `password` | Your Gitea password. If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. |
4242
| `owner` | The owner of the package. |
4343

4444
For example:

docs/content/doc/packages/pypi.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ password = {password}
4242
| ------------ | ----------- |
4343
| `owner` | The owner of the package. |
4444
| `username` | Your Gitea username. |
45-
| `password` | Your Gitea password or a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}). |
45+
| `password` | Your Gitea password. If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. |
4646

4747
## Publish a package
4848

docs/content/doc/packages/rubygems.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ https://gitea.example.com/api/packages/{owner}/rubygems: Bearer {token}
3636
| Parameter | Description |
3737
| ------------- | ----------- |
3838
| `owner` | The owner of the package. |
39-
| `token` | Your personal access token. |
39+
| `token` | Your [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}). |
4040

4141
For example:
4242

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
gitea.com/go-chi/cache v0.2.0
1010
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
1111
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8
12-
gitea.com/lunny/levelqueue v0.4.1
12+
gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7
1313
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
1414
github.com/NYTimes/gziphandler v1.1.1
1515
github.com/PuerkitoBio/goquery v1.8.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 h1:J/1i8u40TbcLP/w2w
8080
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5/go.mod h1:hQ9SYHKdOX968wJglb/NMQ+UqpOKwW4L+EYdvkWjHSo=
8181
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8 h1:tJQRXgZigkLeeW9LPlps9G9aMoE6LAmqigLA+wxmd1Q=
8282
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8/go.mod h1:fc/pjt5EqNKgqQXYzcas1Z5L5whkZHyOvTA7OzWVJck=
83-
gitea.com/lunny/levelqueue v0.4.1 h1:RZ+AFx5gBsZuyqCvofhAkPQ9uaVDPJnsULoJZIYaJNw=
84-
gitea.com/lunny/levelqueue v0.4.1/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
83+
gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7 h1:Zc3RQWC2xOVglLciQH/ZIC5IqSk3Jn96LflGQLv18Rg=
84+
gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
8585
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
8686
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
8787
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=

integrations/api_packages_container_test.go

+70-14
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
func TestPackageContainer(t *testing.T) {
2929
defer prepareTestEnv(t)()
30+
3031
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
3132

3233
has := func(l packages_model.PackagePropertyList, name string) bool {
@@ -37,6 +38,15 @@ func TestPackageContainer(t *testing.T) {
3738
}
3839
return false
3940
}
41+
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
42+
values := make([]string, 0, len(l))
43+
for _, pp := range l {
44+
if pp.Name == name {
45+
values = append(values, pp.Value)
46+
}
47+
}
48+
return values
49+
}
4050

4151
images := []string{"test", "te/st"}
4252
tags := []string{"latest", "main"}
@@ -67,7 +77,7 @@ func TestPackageContainer(t *testing.T) {
6777
Token string `json:"token"`
6878
}
6979

70-
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token"`}
80+
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`}
7181

7282
t.Run("Anonymous", func(t *testing.T) {
7383
defer PrintCurrentTest(t)()
@@ -237,7 +247,8 @@ func TestPackageContainer(t *testing.T) {
237247
assert.Nil(t, pd.SemVer)
238248
assert.Equal(t, image, pd.Package.Name)
239249
assert.Equal(t, tag, pd.Version.Version)
240-
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
250+
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
251+
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
241252

242253
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
243254
metadata := pd.Metadata.(*container_module.Metadata)
@@ -331,7 +342,8 @@ func TestPackageContainer(t *testing.T) {
331342
assert.Nil(t, pd.SemVer)
332343
assert.Equal(t, image, pd.Package.Name)
333344
assert.Equal(t, untaggedManifestDigest, pd.Version.Version)
334-
assert.False(t, has(pd.Properties, container_module.PropertyManifestTagged))
345+
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
346+
assert.False(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
335347

336348
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
337349

@@ -363,18 +375,10 @@ func TestPackageContainer(t *testing.T) {
363375
assert.Nil(t, pd.SemVer)
364376
assert.Equal(t, image, pd.Package.Name)
365377
assert.Equal(t, multiTag, pd.Version.Version)
366-
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
378+
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
379+
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
367380

368-
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
369-
values := make([]string, 0, len(l))
370-
for _, pp := range l {
371-
if pp.Name == name {
372-
values = append(values, pp.Value)
373-
}
374-
}
375-
return values
376-
}
377-
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.Properties, container_module.PropertyManifestReference))
381+
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference))
378382

379383
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
380384
metadata := pd.Metadata.(*container_module.Metadata)
@@ -536,4 +540,56 @@ func TestPackageContainer(t *testing.T) {
536540
})
537541
})
538542
}
543+
544+
t.Run("OwnerNameChange", func(t *testing.T) {
545+
defer PrintCurrentTest(t)()
546+
547+
checkCatalog := func(owner string) func(t *testing.T) {
548+
return func(t *testing.T) {
549+
defer PrintCurrentTest(t)()
550+
551+
req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL))
552+
addTokenAuthHeader(req, userToken)
553+
resp := MakeRequest(t, req, http.StatusOK)
554+
555+
type RepositoryList struct {
556+
Repositories []string `json:"repositories"`
557+
}
558+
559+
repoList := &RepositoryList{}
560+
DecodeJSON(t, resp, &repoList)
561+
562+
assert.Len(t, repoList.Repositories, len(images))
563+
names := make([]string, 0, len(images))
564+
for _, image := range images {
565+
names = append(names, strings.ToLower(owner+"/"+image))
566+
}
567+
assert.ElementsMatch(t, names, repoList.Repositories)
568+
}
569+
}
570+
571+
t.Run(fmt.Sprintf("Catalog[%s]", user.LowerName), checkCatalog(user.LowerName))
572+
573+
session := loginUser(t, user.Name)
574+
575+
newOwnerName := "newUsername"
576+
577+
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
578+
"_csrf": GetCSRF(t, session, "/user/settings"),
579+
"name": newOwnerName,
580+
"email": "user2@example.com",
581+
"language": "en-US",
582+
})
583+
session.MakeRequest(t, req, http.StatusSeeOther)
584+
585+
t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName))
586+
587+
req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
588+
"_csrf": GetCSRF(t, session, "/user/settings"),
589+
"name": user.Name,
590+
"email": "user2@example.com",
591+
"language": "en-US",
592+
})
593+
session.MakeRequest(t, req, http.StatusSeeOther)
594+
})
539595
}

integrations/api_packages_generic_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ func TestPackageGeneric(t *testing.T) {
4242

4343
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
4444
assert.NoError(t, err)
45-
assert.NotNil(t, pd.SemVer)
4645
assert.Nil(t, pd.Metadata)
4746
assert.Equal(t, packageName, pd.Package.Name)
4847
assert.Equal(t, packageVersion, pd.Version.Version)

integrations/api_packages_npm_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ func TestPackageNpm(t *testing.T) {
8585
assert.IsType(t, &npm.Metadata{}, pd.Metadata)
8686
assert.Equal(t, packageName, pd.Package.Name)
8787
assert.Equal(t, packageVersion, pd.Version.Version)
88-
assert.Len(t, pd.Properties, 1)
89-
assert.Equal(t, npm.TagProperty, pd.Properties[0].Name)
90-
assert.Equal(t, packageTag, pd.Properties[0].Value)
88+
assert.Len(t, pd.VersionProperties, 1)
89+
assert.Equal(t, npm.TagProperty, pd.VersionProperties[0].Name)
90+
assert.Equal(t, packageTag, pd.VersionProperties[0].Value)
9191

9292
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
9393
assert.NoError(t, err)

0 commit comments

Comments
 (0)