From cdc9e91750036fc370db65a44618f3139db11ae1 Mon Sep 17 00:00:00 2001 From: FuXiaoHei Date: Mon, 13 Mar 2023 18:23:51 +0800 Subject: [PATCH 01/94] add path prefix to ObjectStorage.Iterator (#23332) Support to iterator subdirectory in ObjectStorage for ObjectStorage.Iterator method. It's required for https://github.com/go-gitea/gitea/pull/22738 to make artifact files cleanable. --------- Co-authored-by: Jason Song Co-authored-by: Lunny Xiao --- cmd/dump.go | 6 ++--- modules/doctor/storage.go | 2 +- modules/storage/helper.go | 2 +- modules/storage/helper_test.go | 2 +- modules/storage/local.go | 8 +++++-- modules/storage/local_test.go | 42 ++++++++++++++++++++++++++++++++++ modules/storage/minio.go | 12 +++++++--- modules/storage/storage.go | 4 ++-- tests/test_utils.go | 4 ++-- 9 files changed, 67 insertions(+), 15 deletions(-) diff --git a/cmd/dump.go b/cmd/dump.go index c802849f8e..00d279b991 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -250,7 +250,7 @@ func runDump(ctx *cli.Context) error { if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { log.Info("Skip dumping LFS data") - } else if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error { + } else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error { info, err := object.Stat() if err != nil { return err @@ -351,7 +351,7 @@ func runDump(ctx *cli.Context) error { if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { log.Info("Skip dumping attachment data") - } else if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error { + } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { info, err := object.Stat() if err != nil { return err @@ -364,7 +364,7 @@ func runDump(ctx *cli.Context) error { if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { log.Info("Skip dumping package data") - } else if err := storage.Packages.IterateObjects(func(objPath string, object storage.Object) error { + } else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error { info, err := object.Stat() if err != nil { return err diff --git a/modules/doctor/storage.go b/modules/doctor/storage.go index aa987de447..c20566d675 100644 --- a/modules/doctor/storage.go +++ b/modules/doctor/storage.go @@ -31,7 +31,7 @@ func commonCheckStorage(ctx context.Context, logger log.Logger, autofix bool, op totalSize, orphanedSize := int64(0), int64(0) var pathsToDelete []string - if err := opts.storer.IterateObjects(func(p string, obj storage.Object) error { + if err := opts.storer.IterateObjects("", func(p string, obj storage.Object) error { defer obj.Close() totalCount++ diff --git a/modules/storage/helper.go b/modules/storage/helper.go index 1ab99d98b3..d1959830b9 100644 --- a/modules/storage/helper.go +++ b/modules/storage/helper.go @@ -90,6 +90,6 @@ func (s discardStorage) URL(_, _ string) (*url.URL, error) { return nil, fmt.Errorf("%s", s) } -func (s discardStorage) IterateObjects(_ func(string, Object) error) error { +func (s discardStorage) IterateObjects(_ string, _ func(string, Object) error) error { return fmt.Errorf("%s", s) } diff --git a/modules/storage/helper_test.go b/modules/storage/helper_test.go index 7d74671c54..f4c2d0467f 100644 --- a/modules/storage/helper_test.go +++ b/modules/storage/helper_test.go @@ -42,7 +42,7 @@ func Test_discardStorage(t *testing.T) { assert.Errorf(t, err, string(tt)) } { - err := tt.IterateObjects(func(_ string, _ Object) error { return nil }) + err := tt.IterateObjects("", func(_ string, _ Object) error { return nil }) assert.Error(t, err, string(tt)) } }) diff --git a/modules/storage/local.go b/modules/storage/local.go index 05bf1fb28a..15f5761e8f 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -127,8 +127,12 @@ func (l *LocalStorage) URL(path, name string) (*url.URL, error) { } // IterateObjects iterates across the objects in the local storage -func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) error { - return filepath.WalkDir(l.dir, func(path string, d os.DirEntry, err error) error { +func (l *LocalStorage) IterateObjects(prefix string, fn func(path string, obj Object) error) error { + dir := l.dir + if prefix != "" { + dir = filepath.Join(l.dir, util.CleanPath(prefix)) + } + return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil { return err } diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go index 994c54e859..2b112df8f1 100644 --- a/modules/storage/local_test.go +++ b/modules/storage/local_test.go @@ -4,6 +4,10 @@ package storage import ( + "bytes" + "context" + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -50,3 +54,41 @@ func TestBuildLocalPath(t *testing.T) { }) } } + +func TestLocalStorageIterator(t *testing.T) { + dir := filepath.Join(os.TempDir(), "TestLocalStorageIteratorTestDir") + l, err := NewLocalStorage(context.Background(), LocalStorageConfig{Path: dir}) + assert.NoError(t, err) + + testFiles := [][]string{ + {"a/1.txt", "a1"}, + {"/a/1.txt", "aa1"}, // same as above, but with leading slash that will be trim + {"b/1.txt", "b1"}, + {"b/2.txt", "b2"}, + {"b/3.txt", "b3"}, + {"b/x 4.txt", "bx4"}, + } + for _, f := range testFiles { + _, err = l.Save(f[0], bytes.NewBufferString(f[1]), -1) + assert.NoError(t, err) + } + + expectedList := map[string][]string{ + "a": {"a/1.txt"}, + "b": {"b/1.txt", "b/2.txt", "b/3.txt", "b/x 4.txt"}, + "": {"a/1.txt", "b/1.txt", "b/2.txt", "b/3.txt", "b/x 4.txt"}, + "/": {"a/1.txt", "b/1.txt", "b/2.txt", "b/3.txt", "b/x 4.txt"}, + "a/b/../../a": {"a/1.txt"}, + } + for dir, expected := range expectedList { + count := 0 + err = l.IterateObjects(dir, func(path string, f Object) error { + defer f.Close() + assert.Contains(t, expected, path) + count++ + return nil + }) + assert.NoError(t, err) + assert.Equal(t, count, len(expected)) + } +} diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 24da14b634..8cc06bcdd3 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -209,12 +209,18 @@ func (m *MinioStorage) URL(path, name string) (*url.URL, error) { } // IterateObjects iterates across the objects in the miniostorage -func (m *MinioStorage) IterateObjects(fn func(path string, obj Object) error) error { +func (m *MinioStorage) IterateObjects(prefix string, fn func(path string, obj Object) error) error { opts := minio.GetObjectOptions{} lobjectCtx, cancel := context.WithCancel(m.ctx) defer cancel() + + basePath := m.basePath + if prefix != "" { + basePath = m.buildMinioPath(prefix) + } + for mObjInfo := range m.client.ListObjects(lobjectCtx, m.bucket, minio.ListObjectsOptions{ - Prefix: m.basePath, + Prefix: basePath, Recursive: true, }) { object, err := m.client.GetObject(lobjectCtx, m.bucket, mObjInfo.Key, opts) @@ -223,7 +229,7 @@ func (m *MinioStorage) IterateObjects(fn func(path string, obj Object) error) er } if err := func(object *minio.Object, fn func(path string, obj Object) error) error { defer object.Close() - return fn(strings.TrimPrefix(mObjInfo.Key, m.basePath), &minioObject{object}) + return fn(strings.TrimPrefix(mObjInfo.Key, basePath), &minioObject{object}) }(object, fn); err != nil { return convertMinioErr(err) } diff --git a/modules/storage/storage.go b/modules/storage/storage.go index d8998b1922..caecab306e 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -65,7 +65,7 @@ type ObjectStorage interface { Stat(path string) (os.FileInfo, error) Delete(path string) error URL(path, name string) (*url.URL, error) - IterateObjects(func(path string, obj Object) error) error + IterateObjects(path string, iterator func(path string, obj Object) error) error } // Copy copies a file from source ObjectStorage to dest ObjectStorage @@ -87,7 +87,7 @@ func Copy(dstStorage ObjectStorage, dstPath string, srcStorage ObjectStorage, sr // Clean delete all the objects in this storage func Clean(storage ObjectStorage) error { - return storage.IterateObjects(func(path string, obj Object) error { + return storage.IterateObjects("", func(path string, obj Object) error { _ = obj.Close() return storage.Delete(path) }) diff --git a/tests/test_utils.go b/tests/test_utils.go index e3e5becfbe..102dd3d298 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -201,7 +201,7 @@ func PrepareTestEnv(t testing.TB, skip ...int) func() { lfsFixtures, err := storage.NewStorage("", storage.LocalStorageConfig{Path: path.Join(filepath.Dir(setting.AppPath), "tests/gitea-lfs-meta")}) assert.NoError(t, err) assert.NoError(t, storage.Clean(storage.LFS)) - assert.NoError(t, lfsFixtures.IterateObjects(func(path string, _ storage.Object) error { + assert.NoError(t, lfsFixtures.IterateObjects("", func(path string, _ storage.Object) error { _, err := storage.Copy(storage.LFS, path, lfsFixtures, path) return err })) @@ -258,7 +258,7 @@ func ResetFixtures(t *testing.T) { lfsFixtures, err := storage.NewStorage("", storage.LocalStorageConfig{Path: path.Join(filepath.Dir(setting.AppPath), "tests/gitea-lfs-meta")}) assert.NoError(t, err) assert.NoError(t, storage.Clean(storage.LFS)) - assert.NoError(t, lfsFixtures.IterateObjects(func(path string, _ storage.Object) error { + assert.NoError(t, lfsFixtures.IterateObjects("", func(path string, _ storage.Object) error { _, err := storage.Copy(storage.LFS, path, lfsFixtures, path) return err })) From d74a7efb60f94a4b8e6e5f65332f94f1be31b761 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Mon, 13 Mar 2023 20:31:41 +0900 Subject: [PATCH 02/94] Use context for `RepositoryList.LoadAttributes` (#23435) --- models/repo/repo_list.go | 10 +++------- routers/api/v1/user/repo.go | 2 +- routers/web/user/notification.go | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index d9cd905a19..92b9c15b4b 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -62,7 +62,8 @@ func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList { return RepositoryList(ValuesRepository(repoMap)) } -func (repos RepositoryList) loadAttributes(ctx context.Context) error { +// LoadAttributes loads the attributes for the given RepositoryList +func (repos RepositoryList) LoadAttributes(ctx context.Context) error { if len(repos) == 0 { return nil } @@ -107,11 +108,6 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error { return nil } -// LoadAttributes loads the attributes for the given RepositoryList -func (repos RepositoryList) LoadAttributes() error { - return repos.loadAttributes(db.DefaultContext) -} - // SearchRepoOptions holds the search options type SearchRepoOptions struct { db.ListOptions @@ -547,7 +543,7 @@ func SearchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c } if loadAttributes { - if err := repos.loadAttributes(ctx); err != nil { + if err := repos.LoadAttributes(ctx); err != nil { return nil, 0, fmt.Errorf("LoadAttributes: %w", err) } } diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go index dcb14780a7..7a8978cc4e 100644 --- a/routers/api/v1/user/repo.go +++ b/routers/api/v1/user/repo.go @@ -31,7 +31,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) { return } - if err := repos.LoadAttributes(); err != nil { + if err := repos.LoadAttributes(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "RepositoryList.LoadAttributes", err) return } diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index e96b3dd27a..e12b41e649 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -117,7 +117,7 @@ func getNotifications(ctx *context.Context) { return } notifications = notifications.Without(failures) - if err := repos.LoadAttributes(); err != nil { // TODO + if err := repos.LoadAttributes(ctx); err != nil { ctx.ServerError("LoadAttributes", err) return } From 0a6f6354bbc03d4a3ad382a432113301e9ea9d86 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 13 Mar 2023 14:41:38 -0500 Subject: [PATCH 03/94] Purge API comment (#23451) This PR just adds the `purge` query parameter to the swagger docs for admin user delete. Signed-off-by: jolheiser --- routers/api/v1/admin/user.go | 4 ++++ templates/swagger/v1_json.tmpl | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 1fbdab3e55..4192d8654d 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -305,6 +305,10 @@ func DeleteUser(ctx *context.APIContext) { // description: username of user to delete // type: string // required: true + // - name: purge + // in: query + // description: purge the user from the system completely + // type: boolean // responses: // "204": // "$ref": "#/responses/empty" diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index cb88e175ea..b64f6dcd87 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -493,6 +493,12 @@ "name": "username", "in": "path", "required": true + }, + { + "type": "boolean", + "description": "purge the user from the system completely", + "name": "purge", + "in": "query" } ], "responses": { From c709fa17a77eae391cafbe72d6b2594f74d86a60 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 13 Mar 2023 21:28:39 +0100 Subject: [PATCH 04/94] Add Swift package registry (#22404) This PR adds a [Swift](https://www.swift.org/) package registry. ![grafik](https://user-images.githubusercontent.com/1666336/211842523-07521cbd-8fb6-400f-820c-ee8048b05ae8.png) --- custom/conf/app.example.ini | 2 + .../doc/advanced/config-cheat-sheet.en-us.md | 1 + docs/content/doc/packages/overview.en-us.md | 1 + docs/content/doc/packages/swift.en-us.md | 93 ++++ models/packages/descriptor.go | 3 + models/packages/package.go | 6 + modules/packages/swift/metadata.go | 214 ++++++++ modules/packages/swift/metadata_test.go | 144 ++++++ modules/setting/packages.go | 2 + options/locale/locale_en-US.ini | 4 + public/img/svg/gitea-swift.svg | 1 + routers/api/packages/api.go | 36 ++ routers/api/packages/swift/swift.go | 464 ++++++++++++++++++ routers/api/v1/packages/package.go | 2 +- services/forms/package_form.go | 2 +- services/packages/packages.go | 2 + templates/package/content/swift.tmpl | 40 ++ templates/package/metadata/swift.tmpl | 4 + templates/package/view.tmpl | 2 + templates/swagger/v1_json.tmpl | 1 + tests/integration/api_packages_swift_test.go | 326 ++++++++++++ web_src/svg/gitea-swift.svg | 5 + 22 files changed, 1353 insertions(+), 2 deletions(-) create mode 100644 docs/content/doc/packages/swift.en-us.md create mode 100644 modules/packages/swift/metadata.go create mode 100644 modules/packages/swift/metadata_test.go create mode 100644 public/img/svg/gitea-swift.svg create mode 100644 routers/api/packages/swift/swift.go create mode 100644 templates/package/content/swift.tmpl create mode 100644 templates/package/metadata/swift.tmpl create mode 100644 tests/integration/api_packages_swift_test.go create mode 100644 web_src/svg/gitea-swift.svg diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b2b5af0af8..e53ed7ad9f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2516,6 +2516,8 @@ ROUTER = console ;LIMIT_SIZE_PYPI = -1 ;; Maximum size of a RubyGems upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_RUBYGEMS = -1 +;; Maximum size of a Swift upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) +;LIMIT_SIZE_SWIFT = -1 ;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_VAGRANT = -1 diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index b67d6cdf5f..4b9c519cd8 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -1254,6 +1254,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `LIMIT_SIZE_PUB`: **-1**: Maximum size of a Pub upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_PYPI`: **-1**: Maximum size of a PyPI upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_RUBYGEMS`: **-1**: Maximum size of a RubyGems upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) +- `LIMIT_SIZE_SWIFT`: **-1**: Maximum size of a Swift upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_VAGRANT`: **-1**: Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ## Mirror (`mirror`) diff --git a/docs/content/doc/packages/overview.en-us.md b/docs/content/doc/packages/overview.en-us.md index f93fec6393..08da8ced48 100644 --- a/docs/content/doc/packages/overview.en-us.md +++ b/docs/content/doc/packages/overview.en-us.md @@ -40,6 +40,7 @@ The following package managers are currently supported: | [Pub]({{< relref "doc/packages/pub.en-us.md" >}}) | Dart | `dart`, `flutter` | | [PyPI]({{< relref "doc/packages/pypi.en-us.md" >}}) | Python | `pip`, `twine` | | [RubyGems]({{< relref "doc/packages/rubygems.en-us.md" >}}) | Ruby | `gem`, `Bundler` | +| [Swift]({{< relref "doc/packages/rubygems.en-us.md" >}}) | Swift | `swift` | | [Vagrant]({{< relref "doc/packages/vagrant.en-us.md" >}}) | - | `vagrant` | **The following paragraphs only apply if Packages are not globally disabled!** diff --git a/docs/content/doc/packages/swift.en-us.md b/docs/content/doc/packages/swift.en-us.md new file mode 100644 index 0000000000..61a4c9a55d --- /dev/null +++ b/docs/content/doc/packages/swift.en-us.md @@ -0,0 +1,93 @@ +--- +date: "2023-01-10T00:00:00+00:00" +title: "Swift Packages Repository" +slug: "packages/swift" +draft: false +toc: false +menu: + sidebar: + parent: "packages" + name: "Swift" + weight: 95 + identifier: "swift" +--- + +# Swift Packages Repository + +Publish [Swift](hhttps://www.swift.org/) packages for your user or organization. + +**Table of Contents** + +{{< toc >}} + +## Requirements + +To work with the Swift package registry, you need to use [swift](https://www.swift.org/getting-started/) to consume and a HTTP client (like `curl`) to publish packages. + +## Configuring the package registry + +To register the package registry and provide credentials, execute: + +```shell +swift package-registry set https://gitea.example.com/api/packages/{owner}/swift -login {username} -password {password} +``` + +| Placeholder | Description | +| ------------ | ----------- | +| `owner` | The owner of the package. | +| `username` | Your Gitea username. | +| `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. | + +The login is optional and only needed if the package registry is private. + +## Publish a package + +First you have to pack the contents of your package: + +```shell +swift package archive-source +``` + +To publish the package perform a HTTP PUT request with the package content in the request body. + +```shell --user your_username:your_password_or_token \ +curl -X PUT --user {username}:{password} \ + -H "Accept: application/vnd.swift.registry.v1+json" \ + -F source-archive=@/path/to/package.zip \ + -F metadata={metadata} \ + https://gitea.example.com/api/packages/{owner}/swift/{scope}/{name}/{version} +``` + +| Placeholder | Description | +| ----------- | ----------- | +| `username` | Your Gitea username. | +| `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. | +| `owner` | The owner of the package. | +| `scope` | The package scope. | +| `name` | The package name. | +| `version` | The package version. | +| `metadata` | (Optional) The metadata of the package. JSON encoded subset of https://schema.org/SoftwareSourceCode | + +You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. + +## Install a package + +To install a Swift package from the package registry, add it in the `Package.swift` file dependencies list: + +``` +dependencies: [ + .package(id: "{scope}.{name}", from:"{version}") +] +``` + +| Parameter | Description | +| ----------- | ----------- | +| `scope` | The package scope. | +| `name` | The package name. | +| `version` | The package version. | + +Afterwards execute the following command to install it: + +```shell +swift package resolve +``` diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index f4be21e74e..06699b5d57 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/packages/pub" "code.gitea.io/gitea/modules/packages/pypi" "code.gitea.io/gitea/modules/packages/rubygems" + "code.gitea.io/gitea/modules/packages/swift" "code.gitea.io/gitea/modules/packages/vagrant" "github.com/hashicorp/go-version" @@ -159,6 +160,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc metadata = &pypi.Metadata{} case TypeRubyGems: metadata = &rubygems.Metadata{} + case TypeSwift: + metadata = &swift.Metadata{} case TypeVagrant: metadata = &vagrant.Metadata{} default: diff --git a/models/packages/package.go b/models/packages/package.go index 32f30fab9b..ccc9257c31 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -44,6 +44,7 @@ const ( TypePub Type = "pub" TypePyPI Type = "pypi" TypeRubyGems Type = "rubygems" + TypeSwift Type = "swift" TypeVagrant Type = "vagrant" ) @@ -62,6 +63,7 @@ var TypeList = []Type{ TypePub, TypePyPI, TypeRubyGems, + TypeSwift, TypeVagrant, } @@ -96,6 +98,8 @@ func (pt Type) Name() string { return "PyPI" case TypeRubyGems: return "RubyGems" + case TypeSwift: + return "Swift" case TypeVagrant: return "Vagrant" } @@ -133,6 +137,8 @@ func (pt Type) SVGName() string { return "gitea-python" case TypeRubyGems: return "gitea-rubygems" + case TypeSwift: + return "gitea-swift" case TypeVagrant: return "gitea-vagrant" } diff --git a/modules/packages/swift/metadata.go b/modules/packages/swift/metadata.go new file mode 100644 index 0000000000..24c4262ab7 --- /dev/null +++ b/modules/packages/swift/metadata.go @@ -0,0 +1,214 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package swift + +import ( + "archive/zip" + "fmt" + "io" + "path" + "regexp" + "strings" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/validation" + + "github.com/hashicorp/go-version" +) + +var ( + ErrMissingManifestFile = util.NewInvalidArgumentErrorf("Package.swift file is missing") + ErrManifestFileTooLarge = util.NewInvalidArgumentErrorf("Package.swift file is too large") + ErrInvalidManifestVersion = util.NewInvalidArgumentErrorf("manifest version is invalid") + + manifestPattern = regexp.MustCompile(`\APackage(?:@swift-(\d+(?:\.\d+)?(?:\.\d+)?))?\.swift\z`) + toolsVersionPattern = regexp.MustCompile(`\A// swift-tools-version:(\d+(?:\.\d+)?(?:\.\d+)?)`) +) + +const ( + maxManifestFileSize = 128 * 1024 + + PropertyScope = "swift.scope" + PropertyName = "swift.name" + PropertyRepositoryURL = "swift.repository_url" +) + +// Package represents a Swift package +type Package struct { + RepositoryURLs []string + Metadata *Metadata +} + +// Metadata represents the metadata of a Swift package +type Metadata struct { + Description string `json:"description,omitempty"` + Keywords []string `json:"keywords,omitempty"` + RepositoryURL string `json:"repository_url,omitempty"` + License string `json:"license,omitempty"` + Author Person `json:"author,omitempty"` + Manifests map[string]*Manifest `json:"manifests,omitempty"` +} + +// Manifest represents a Package.swift file +type Manifest struct { + Content string `json:"content"` + ToolsVersion string `json:"tools_version,omitempty"` +} + +// https://schema.org/SoftwareSourceCode +type SoftwareSourceCode struct { + Context []string `json:"@context"` + Type string `json:"@type"` + Name string `json:"name"` + Version string `json:"version"` + Description string `json:"description,omitempty"` + Keywords []string `json:"keywords,omitempty"` + CodeRepository string `json:"codeRepository,omitempty"` + License string `json:"license,omitempty"` + Author Person `json:"author"` + ProgrammingLanguage ProgrammingLanguage `json:"programmingLanguage"` + RepositoryURLs []string `json:"repositoryURLs,omitempty"` +} + +// https://schema.org/ProgrammingLanguage +type ProgrammingLanguage struct { + Type string `json:"@type"` + Name string `json:"name"` + URL string `json:"url"` +} + +// https://schema.org/Person +type Person struct { + Type string `json:"@type,omitempty"` + GivenName string `json:"givenName,omitempty"` + MiddleName string `json:"middleName,omitempty"` + FamilyName string `json:"familyName,omitempty"` +} + +func (p Person) String() string { + var sb strings.Builder + if p.GivenName != "" { + sb.WriteString(p.GivenName) + } + if p.MiddleName != "" { + if sb.Len() > 0 { + sb.WriteRune(' ') + } + sb.WriteString(p.MiddleName) + } + if p.FamilyName != "" { + if sb.Len() > 0 { + sb.WriteRune(' ') + } + sb.WriteString(p.FamilyName) + } + return sb.String() +} + +// ParsePackage parses the Swift package upload +func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) { + zr, err := zip.NewReader(sr, size) + if err != nil { + return nil, err + } + + p := &Package{ + Metadata: &Metadata{ + Manifests: make(map[string]*Manifest), + }, + } + + for _, file := range zr.File { + manifestMatch := manifestPattern.FindStringSubmatch(path.Base(file.Name)) + if len(manifestMatch) == 0 { + continue + } + + if file.UncompressedSize64 > maxManifestFileSize { + return nil, ErrManifestFileTooLarge + } + + f, err := zr.Open(file.Name) + if err != nil { + return nil, err + } + + content, err := io.ReadAll(f) + + if err := f.Close(); err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + + swiftVersion := "" + if len(manifestMatch) == 2 && manifestMatch[1] != "" { + v, err := version.NewSemver(manifestMatch[1]) + if err != nil { + return nil, ErrInvalidManifestVersion + } + swiftVersion = TrimmedVersionString(v) + } + + manifest := &Manifest{ + Content: string(content), + } + + toolsMatch := toolsVersionPattern.FindStringSubmatch(manifest.Content) + if len(toolsMatch) == 2 { + v, err := version.NewSemver(toolsMatch[1]) + if err != nil { + return nil, ErrInvalidManifestVersion + } + + manifest.ToolsVersion = TrimmedVersionString(v) + } + + p.Metadata.Manifests[swiftVersion] = manifest + } + + if _, found := p.Metadata.Manifests[""]; !found { + return nil, ErrMissingManifestFile + } + + if mr != nil { + var ssc *SoftwareSourceCode + if err := json.NewDecoder(mr).Decode(&ssc); err != nil { + return nil, err + } + + p.Metadata.Description = ssc.Description + p.Metadata.Keywords = ssc.Keywords + p.Metadata.License = ssc.License + p.Metadata.Author = Person{ + GivenName: ssc.Author.GivenName, + MiddleName: ssc.Author.MiddleName, + FamilyName: ssc.Author.FamilyName, + } + + p.Metadata.RepositoryURL = ssc.CodeRepository + if !validation.IsValidURL(p.Metadata.RepositoryURL) { + p.Metadata.RepositoryURL = "" + } + + p.RepositoryURLs = ssc.RepositoryURLs + } + + return p, nil +} + +// TrimmedVersionString returns the version string without the patch segment if it is zero +func TrimmedVersionString(v *version.Version) string { + segments := v.Segments64() + + var b strings.Builder + fmt.Fprintf(&b, "%d.%d", segments[0], segments[1]) + if segments[2] != 0 { + fmt.Fprintf(&b, ".%d", segments[2]) + } + return b.String() +} diff --git a/modules/packages/swift/metadata_test.go b/modules/packages/swift/metadata_test.go new file mode 100644 index 0000000000..3913c2355b --- /dev/null +++ b/modules/packages/swift/metadata_test.go @@ -0,0 +1,144 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package swift + +import ( + "archive/zip" + "bytes" + "strings" + "testing" + + "github.com/hashicorp/go-version" + "github.com/stretchr/testify/assert" +) + +const ( + packageName = "gitea" + packageVersion = "1.0.1" + packageDescription = "Package Description" + packageRepositoryURL = "https://gitea.io/gitea/gitea" + packageAuthor = "KN4CK3R" + packageLicense = "MIT" +) + +func TestParsePackage(t *testing.T) { + createArchive := func(files map[string][]byte) *bytes.Reader { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + for filename, content := range files { + w, _ := zw.Create(filename) + w.Write(content) + } + zw.Close() + return bytes.NewReader(buf.Bytes()) + } + + t.Run("MissingManifestFile", func(t *testing.T) { + data := createArchive(map[string][]byte{"dummy.txt": {}}) + + p, err := ParsePackage(data, data.Size(), nil) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrMissingManifestFile) + }) + + t.Run("ManifestFileTooLarge", func(t *testing.T) { + data := createArchive(map[string][]byte{ + "Package.swift": make([]byte, maxManifestFileSize+1), + }) + + p, err := ParsePackage(data, data.Size(), nil) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrManifestFileTooLarge) + }) + + t.Run("WithoutMetadata", func(t *testing.T) { + content1 := "// swift-tools-version:5.7\n//\n// Package.swift" + content2 := "// swift-tools-version:5.6\n//\n// Package@swift-5.6.swift" + + data := createArchive(map[string][]byte{ + "Package.swift": []byte(content1), + "Package@swift-5.5.swift": []byte(content2), + }) + + p, err := ParsePackage(data, data.Size(), nil) + assert.NotNil(t, p) + assert.NoError(t, err) + + assert.NotNil(t, p.Metadata) + assert.Empty(t, p.RepositoryURLs) + assert.Len(t, p.Metadata.Manifests, 2) + m := p.Metadata.Manifests[""] + assert.Equal(t, "5.7", m.ToolsVersion) + assert.Equal(t, content1, m.Content) + m = p.Metadata.Manifests["5.5"] + assert.Equal(t, "5.6", m.ToolsVersion) + assert.Equal(t, content2, m.Content) + }) + + t.Run("WithMetadata", func(t *testing.T) { + data := createArchive(map[string][]byte{ + "Package.swift": []byte("// swift-tools-version:5.7\n//\n// Package.swift"), + }) + + p, err := ParsePackage( + data, + data.Size(), + strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","keywords":["swift","package"],"license":"`+packageLicense+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`), + ) + assert.NotNil(t, p) + assert.NoError(t, err) + + assert.NotNil(t, p.Metadata) + assert.Len(t, p.Metadata.Manifests, 1) + m := p.Metadata.Manifests[""] + assert.Equal(t, "5.7", m.ToolsVersion) + + assert.Equal(t, packageDescription, p.Metadata.Description) + assert.ElementsMatch(t, []string{"swift", "package"}, p.Metadata.Keywords) + assert.Equal(t, packageLicense, p.Metadata.License) + assert.Equal(t, packageAuthor, p.Metadata.Author.GivenName) + assert.Equal(t, packageRepositoryURL, p.Metadata.RepositoryURL) + assert.ElementsMatch(t, []string{packageRepositoryURL}, p.RepositoryURLs) + }) +} + +func TestTrimmedVersionString(t *testing.T) { + cases := []struct { + Version *version.Version + Expected string + }{ + { + Version: version.Must(version.NewVersion("1")), + Expected: "1.0", + }, + { + Version: version.Must(version.NewVersion("1.0")), + Expected: "1.0", + }, + { + Version: version.Must(version.NewVersion("1.0.0")), + Expected: "1.0", + }, + { + Version: version.Must(version.NewVersion("1.0.1")), + Expected: "1.0.1", + }, + { + Version: version.Must(version.NewVersion("1.0+meta")), + Expected: "1.0", + }, + { + Version: version.Must(version.NewVersion("1.0.0+meta")), + Expected: "1.0", + }, + { + Version: version.Must(version.NewVersion("1.0.1+meta")), + Expected: "1.0.1", + }, + } + + for _, c := range cases { + assert.Equal(t, c.Expected, TrimmedVersionString(c.Version)) + } +} diff --git a/modules/setting/packages.go b/modules/setting/packages.go index 13599e5a63..ac0ad62bca 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -39,6 +39,7 @@ var ( LimitSizePub int64 LimitSizePyPI int64 LimitSizeRubyGems int64 + LimitSizeSwift int64 LimitSizeVagrant int64 }{ Enabled: true, @@ -81,6 +82,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) { Packages.LimitSizePub = mustBytes(sec, "LIMIT_SIZE_PUB") Packages.LimitSizePyPI = mustBytes(sec, "LIMIT_SIZE_PYPI") Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS") + Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 677af1397d..e793c3ef03 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3239,6 +3239,10 @@ rubygems.dependencies.development = Development Dependencies rubygems.required.ruby = Requires Ruby version rubygems.required.rubygems = Requires RubyGem version rubygems.documentation = For more information on the RubyGems registry, see the documentation. +swift.registry = Setup this registry from the command line: +swift.install = Add the package in your Package.swift file: +swift.install2 = and run the following command: +swift.documentation = For more information on the Swift registry, see the documentation. vagrant.install = To add a Vagrant box, run the following command: vagrant.documentation = For more information on the Vagrant registry, see the documentation. settings.link = Link this package to a repository diff --git a/public/img/svg/gitea-swift.svg b/public/img/svg/gitea-swift.svg new file mode 100644 index 0000000000..ebfea951da --- /dev/null +++ b/public/img/svg/gitea-swift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 0e3d8b7a02..c0c7b117f6 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -28,6 +28,7 @@ import ( "code.gitea.io/gitea/routers/api/packages/pub" "code.gitea.io/gitea/routers/api/packages/pypi" "code.gitea.io/gitea/routers/api/packages/rubygems" + "code.gitea.io/gitea/routers/api/packages/swift" "code.gitea.io/gitea/routers/api/packages/vagrant" "code.gitea.io/gitea/services/auth" context_service "code.gitea.io/gitea/services/context" @@ -375,6 +376,41 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { r.Delete("/yank", rubygems.DeletePackage) }, reqPackageAccess(perm.AccessModeWrite)) }, reqPackageAccess(perm.AccessModeRead)) + r.Group("/swift", func() { + r.Group("/{scope}/{name}", func() { + r.Group("", func() { + r.Get("", swift.EnumeratePackageVersions) + r.Get(".json", swift.EnumeratePackageVersions) + }, swift.CheckAcceptMediaType(swift.AcceptJSON)) + r.Group("/{version}", func() { + r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest) + r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile) + r.Get("", func(ctx *context.Context) { + // Can't use normal routes here: https://github.com/go-chi/chi/issues/781 + + version := ctx.Params("version") + if strings.HasSuffix(version, ".zip") { + swift.CheckAcceptMediaType(swift.AcceptZip)(ctx) + if ctx.Written() { + return + } + ctx.SetParams("version", version[:len(version)-4]) + swift.DownloadPackageFile(ctx) + } else { + swift.CheckAcceptMediaType(swift.AcceptJSON)(ctx) + if ctx.Written() { + return + } + if strings.HasSuffix(version, ".json") { + ctx.SetParams("version", version[:len(version)-5]) + } + swift.PackageVersionMetadata(ctx) + } + }) + }) + }) + r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/vagrant", func() { r.Group("/authenticate", func() { r.Get("", vagrant.CheckAuthenticate) diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go new file mode 100644 index 0000000000..f78f703778 --- /dev/null +++ b/routers/api/packages/swift/swift.go @@ -0,0 +1,464 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package swift + +import ( + "errors" + "fmt" + "io" + "net/http" + "regexp" + "sort" + "strings" + + packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + packages_module "code.gitea.io/gitea/modules/packages" + swift_module "code.gitea.io/gitea/modules/packages/swift" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/api/packages/helper" + packages_service "code.gitea.io/gitea/services/packages" + + "github.com/hashicorp/go-version" +) + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#35-api-versioning +const ( + AcceptJSON = "application/vnd.swift.registry.v1+json" + AcceptSwift = "application/vnd.swift.registry.v1+swift" + AcceptZip = "application/vnd.swift.registry.v1+zip" +) + +var ( + // https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#361-package-scope + scopePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-]{0,38}\z`) + // https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#362-package-name + namePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-_]{0,99}\z`) +) + +type headers struct { + Status int + ContentType string + Digest string + Location string + Link string +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#35-api-versioning +func setResponseHeaders(resp http.ResponseWriter, h *headers) { + if h.ContentType != "" { + resp.Header().Set("Content-Type", h.ContentType) + } + if h.Digest != "" { + resp.Header().Set("Digest", "sha256="+h.Digest) + } + if h.Location != "" { + resp.Header().Set("Location", h.Location) + } + if h.Link != "" { + resp.Header().Set("Link", h.Link) + } + resp.Header().Set("Content-Version", "1") + if h.Status != 0 { + resp.WriteHeader(h.Status) + } +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#33-error-handling +func apiError(ctx *context.Context, status int, obj interface{}) { + // https://www.rfc-editor.org/rfc/rfc7807 + type Problem struct { + Status int `json:"status"` + Detail string `json:"detail"` + } + + helper.LogAndProcessError(ctx, status, obj, func(message string) { + setResponseHeaders(ctx.Resp, &headers{ + Status: status, + ContentType: "application/problem+json", + }) + if err := json.NewEncoder(ctx.Resp).Encode(Problem{ + Status: status, + Detail: message, + }); err != nil { + log.Error("JSON encode: %v", err) + } + }) +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#35-api-versioning +func CheckAcceptMediaType(requiredAcceptHeader string) func(ctx *context.Context) { + return func(ctx *context.Context) { + accept := ctx.Req.Header.Get("Accept") + if accept != "" && accept != requiredAcceptHeader { + apiError(ctx, http.StatusBadRequest, fmt.Sprintf("Unexpected accept header. Should be '%s'.", requiredAcceptHeader)) + } + } +} + +func buildPackageID(scope, name string) string { + return scope + "." + name +} + +type Release struct { + URL string `json:"url"` +} + +type EnumeratePackageVersionsResponse struct { + Releases map[string]Release `json:"releases"` +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#41-list-package-releases +func EnumeratePackageVersions(ctx *context.Context) { + packageScope := ctx.Params("scope") + packageName := ctx.Params("name") + + pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(packageScope, packageName)) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if len(pvs) == 0 { + apiError(ctx, http.StatusNotFound, nil) + return + } + + pds, err := packages_model.GetPackageDescriptors(ctx, pvs) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + sort.Slice(pds, func(i, j int) bool { + return pds[i].SemVer.LessThan(pds[j].SemVer) + }) + + baseURL := fmt.Sprintf("%sapi/packages/%s/swift/%s/%s/", setting.AppURL, ctx.Package.Owner.LowerName, packageScope, packageName) + + releases := make(map[string]Release) + for _, pd := range pds { + version := pd.SemVer.String() + releases[version] = Release{ + URL: baseURL + version, + } + } + + setResponseHeaders(ctx.Resp, &headers{ + Link: fmt.Sprintf(`<%s%s>; rel="latest-version"`, baseURL, pds[len(pds)-1].Version.Version), + }) + + ctx.JSON(http.StatusOK, EnumeratePackageVersionsResponse{ + Releases: releases, + }) +} + +type Resource struct { + Name string `json:"id"` + Type string `json:"type"` + Checksum string `json:"checksum"` +} + +type PackageVersionMetadataResponse struct { + ID string `json:"id"` + Version string `json:"version"` + Resources []Resource `json:"resources"` + Metadata *swift_module.SoftwareSourceCode `json:"metadata"` +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-2 +func PackageVersionMetadata(ctx *context.Context) { + id := buildPackageID(ctx.Params("scope"), ctx.Params("name")) + + pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, id, ctx.Params("version")) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + pd, err := packages_model.GetPackageDescriptor(ctx, pv) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + metadata := pd.Metadata.(*swift_module.Metadata) + + setResponseHeaders(ctx.Resp, &headers{}) + + ctx.JSON(http.StatusOK, PackageVersionMetadataResponse{ + ID: id, + Version: pd.Version.Version, + Resources: []Resource{ + { + Name: "source-archive", + Type: "application/zip", + Checksum: pd.Files[0].Blob.HashSHA256, + }, + }, + Metadata: &swift_module.SoftwareSourceCode{ + Context: []string{"http://schema.org/"}, + Type: "SoftwareSourceCode", + Name: pd.PackageProperties.GetByName(swift_module.PropertyName), + Version: pd.Version.Version, + Description: metadata.Description, + Keywords: metadata.Keywords, + CodeRepository: metadata.RepositoryURL, + License: metadata.License, + ProgrammingLanguage: swift_module.ProgrammingLanguage{ + Type: "ComputerLanguage", + Name: "Swift", + URL: "https://swift.org", + }, + Author: swift_module.Person{ + Type: "Person", + GivenName: metadata.Author.GivenName, + MiddleName: metadata.Author.MiddleName, + FamilyName: metadata.Author.FamilyName, + }, + }, + }) +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#43-fetch-manifest-for-a-package-release +func DownloadManifest(ctx *context.Context) { + packageScope := ctx.Params("scope") + packageName := ctx.Params("name") + packageVersion := ctx.Params("version") + + pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(packageScope, packageName), packageVersion) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + pd, err := packages_model.GetPackageDescriptor(ctx, pv) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + swiftVersion := ctx.FormTrim("swift-version") + if swiftVersion != "" { + v, err := version.NewVersion(swiftVersion) + if err == nil { + swiftVersion = swift_module.TrimmedVersionString(v) + } + } + m, ok := pd.Metadata.(*swift_module.Metadata).Manifests[swiftVersion] + if !ok { + setResponseHeaders(ctx.Resp, &headers{ + Status: http.StatusSeeOther, + Location: fmt.Sprintf("%sapi/packages/%s/swift/%s/%s/%s/Package.swift", setting.AppURL, ctx.Package.Owner.LowerName, packageScope, packageName, packageVersion), + }) + return + } + + setResponseHeaders(ctx.Resp, &headers{}) + + filename := "Package.swift" + if swiftVersion != "" { + filename = fmt.Sprintf("Package@swift-%s.swift", swiftVersion) + } + + ctx.ServeContent(strings.NewReader(m.Content), &context.ServeHeaderOptions{ + ContentType: "text/x-swift", + Filename: filename, + LastModified: pv.CreatedUnix.AsLocalTime(), + }) +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-6 +func UploadPackageFile(ctx *context.Context) { + packageScope := ctx.Params("scope") + packageName := ctx.Params("name") + + v, err := version.NewVersion(ctx.Params("version")) + + if !scopePattern.MatchString(packageScope) || !namePattern.MatchString(packageName) || err != nil { + apiError(ctx, http.StatusBadRequest, err) + return + } + + packageVersion := v.Core().String() + + file, _, err := ctx.Req.FormFile("source-archive") + if err != nil { + apiError(ctx, http.StatusBadRequest, err) + return + } + defer file.Close() + + buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer buf.Close() + + var mr io.Reader + metadata := ctx.Req.FormValue("metadata") + if metadata != "" { + mr = strings.NewReader(metadata) + } + + pck, err := swift_module.ParsePackage(buf, buf.Size(), mr) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + apiError(ctx, http.StatusBadRequest, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + if _, err := buf.Seek(0, io.SeekStart); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + pv, _, err := packages_service.CreatePackageAndAddFile( + &packages_service.PackageCreationInfo{ + PackageInfo: packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeSwift, + Name: buildPackageID(packageScope, packageName), + Version: packageVersion, + }, + SemverCompatible: true, + Creator: ctx.Doer, + Metadata: pck.Metadata, + PackageProperties: map[string]string{ + swift_module.PropertyScope: packageScope, + swift_module.PropertyName: packageName, + }, + }, + &packages_service.PackageFileCreationInfo{ + PackageFileInfo: packages_service.PackageFileInfo{ + Filename: fmt.Sprintf("%s-%s.zip", packageName, packageVersion), + }, + Creator: ctx.Doer, + Data: buf, + IsLead: true, + }, + ) + if err != nil { + switch err { + case packages_model.ErrDuplicatePackageVersion: + apiError(ctx, http.StatusConflict, err) + case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: + apiError(ctx, http.StatusForbidden, err) + default: + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + for _, url := range pck.RepositoryURLs { + _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, swift_module.PropertyRepositoryURL, url) + if err != nil { + log.Error("InsertProperty failed: %v", err) + } + } + + setResponseHeaders(ctx.Resp, &headers{}) + + ctx.Status(http.StatusCreated) +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-4 +func DownloadPackageFile(ctx *context.Context) { + pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(ctx.Params("scope"), ctx.Params("name")), ctx.Params("version")) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + pd, err := packages_model.GetPackageDescriptor(ctx, pv) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + pf := pd.Files[0].File + + s, _, err := packages_service.GetPackageFileStream(ctx, pf) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer s.Close() + + setResponseHeaders(ctx.Resp, &headers{ + Digest: pd.Files[0].Blob.HashSHA256, + }) + + ctx.ServeContent(s, &context.ServeHeaderOptions{ + Filename: pf.Name, + ContentType: "application/zip", + LastModified: pf.CreatedUnix.AsLocalTime(), + }) +} + +type LookupPackageIdentifiersResponse struct { + Identifiers []string `json:"identifiers"` +} + +// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-5 +func LookupPackageIdentifiers(ctx *context.Context) { + url := ctx.FormTrim("url") + if url == "" { + apiError(ctx, http.StatusBadRequest, nil) + return + } + + pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeSwift, + Properties: map[string]string{ + swift_module.PropertyRepositoryURL: url, + }, + IsInternal: util.OptionalBoolFalse, + }) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + if len(pvs) == 0 { + apiError(ctx, http.StatusNotFound, nil) + return + } + + pds, err := packages_model.GetPackageDescriptors(ctx, pvs) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + identifiers := make([]string, 0, len(pds)) + for _, pd := range pds { + identifiers = append(identifiers, pd.Package.Name) + } + + setResponseHeaders(ctx.Resp, &headers{}) + + ctx.JSON(http.StatusOK, LookupPackageIdentifiersResponse{ + Identifiers: identifiers, + }) +} diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index ab077090d1..200dc5aaf1 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -40,7 +40,7 @@ func ListPackages(ctx *context.APIContext) { // in: query // description: package type filter // type: string - // enum: [cargo, chef, composer, conan, conda, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, vagrant] + // enum: [cargo, chef, composer, conan, conda, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, swift, vagrant] // - name: q // in: query // description: name filter diff --git a/services/forms/package_form.go b/services/forms/package_form.go index b22ed47c77..699d0fe44f 100644 --- a/services/forms/package_form.go +++ b/services/forms/package_form.go @@ -15,7 +15,7 @@ import ( type PackageCleanupRuleForm struct { ID int64 Enabled bool - Type string `binding:"Required;In(cargo,chef,composer,conan,conda,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,vagrant)"` + Type string `binding:"Required;In(cargo,chef,composer,conan,conda,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,swift,vagrant)"` KeepCount int `binding:"In(0,1,5,10,25,50,100)"` KeepPattern string `binding:"RegexPattern"` RemoveDays int `binding:"In(0,7,14,30,60,90,180)"` diff --git a/services/packages/packages.go b/services/packages/packages.go index 3abca7337c..dd5c63470b 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -361,6 +361,8 @@ func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, p typeSpecificSize = setting.Packages.LimitSizePyPI case packages_model.TypeRubyGems: typeSpecificSize = setting.Packages.LimitSizeRubyGems + case packages_model.TypeSwift: + typeSpecificSize = setting.Packages.LimitSizeSwift case packages_model.TypeVagrant: typeSpecificSize = setting.Packages.LimitSizeVagrant } diff --git a/templates/package/content/swift.tmpl b/templates/package/content/swift.tmpl new file mode 100644 index 0000000000..3ff06483b8 --- /dev/null +++ b/templates/package/content/swift.tmpl @@ -0,0 +1,40 @@ +{{if eq .PackageDescriptor.Package.Type "swift"}} +

{{.locale.Tr "packages.installation"}}

+
+
+
+ +
swift package-registry set 
+
+
+ +
dependencies: [
+	.package(id: "{{.PackageDescriptor.Package.Name}}", from:"{{.PackageDescriptor.Version.Version}}")
+]
+
+
+ +
swift package resolve
+
+
+ +
+
+
+ + {{if .PackageDescriptor.Metadata.Description}} +

{{.locale.Tr "packages.about"}}

+
+ {{if .PackageDescriptor.Metadata.Description}}{{.PackageDescriptor.Metadata.Description}}{{end}} +
+ {{end}} + + {{if .PackageDescriptor.Metadata.Keywords}} +

{{.locale.Tr "packages.keywords"}}

+
+ {{range .PackageDescriptor.Metadata.Keywords}} + {{.}} + {{end}} +
+ {{end}} +{{end}} diff --git a/templates/package/metadata/swift.tmpl b/templates/package/metadata/swift.tmpl new file mode 100644 index 0000000000..8a9ab071fc --- /dev/null +++ b/templates/package/metadata/swift.tmpl @@ -0,0 +1,4 @@ +{{if eq .PackageDescriptor.Package.Type "swift"}} + {{if .PackageDescriptor.Metadata.Author.String}}
{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Author}}
{{end}} + {{if .PackageDescriptor.Metadata.RepositoryURL}}
{{svg "octicon-link-external" 16 "mr-3"}} {{.locale.Tr "packages.details.project_site"}}
{{end}} +{{end}} diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index 839d9cf21a..b2a2fb1e5d 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -33,6 +33,7 @@ {{template "package/content/pub" .}} {{template "package/content/pypi" .}} {{template "package/content/rubygems" .}} + {{template "package/content/swift" .}} {{template "package/content/vagrant" .}}
@@ -59,6 +60,7 @@ {{template "package/metadata/pub" .}} {{template "package/metadata/pypi" .}} {{template "package/metadata/rubygems" .}} + {{template "package/metadata/swift" .}} {{template "package/metadata/vagrant" .}}
{{svg "octicon-database" 16 "gt-mr-3"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index b64f6dcd87..9c46b25eaf 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -2120,6 +2120,7 @@ "pub", "pypi", "rubygems", + "swift", "vagrant" ], "type": "string", diff --git a/tests/integration/api_packages_swift_test.go b/tests/integration/api_packages_swift_test.go new file mode 100644 index 0000000000..a3035ea604 --- /dev/null +++ b/tests/integration/api_packages_swift_test.go @@ -0,0 +1,326 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "archive/zip" + "bytes" + "fmt" + "io" + "mime/multipart" + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + swift_module "code.gitea.io/gitea/modules/packages/swift" + "code.gitea.io/gitea/modules/setting" + swift_router "code.gitea.io/gitea/routers/api/packages/swift" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageSwift(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + packageScope := "test-scope" + packageName := "test_package" + packageID := packageScope + "." + packageName + packageVersion := "1.0.3" + packageAuthor := "KN4CK3R" + packageDescription := "Gitea Test Package" + packageRepositoryURL := "https://gitea.io/gitea/gitea" + contentManifest1 := "// swift-tools-version:5.7\n//\n// Package.swift" + contentManifest2 := "// swift-tools-version:5.6\n//\n// Package@swift-5.6.swift" + + url := fmt.Sprintf("/api/packages/%s/swift", user.Name) + + t.Run("CheckAcceptMediaType", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + for _, sub := range []string{ + "/scope/package", + "/scope/package.json", + "/scope/package/1.0.0", + "/scope/package/1.0.0.json", + "/scope/package/1.0.0.zip", + "/scope/package/1.0.0/Package.swift", + "/identifiers", + } { + req := NewRequest(t, "GET", url+sub) + req.Header.Add("Accept", "application/unknown") + resp := MakeRequest(t, req, http.StatusBadRequest) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) + } + + req := NewRequestWithBody(t, "PUT", url+"/scope/package/1.0.0", strings.NewReader("")) + req = AddBasicAuthHeader(req, user.Name) + req.Header.Add("Accept", "application/unknown") + resp := MakeRequest(t, req, http.StatusBadRequest) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { + var body bytes.Buffer + mpw := multipart.NewWriter(&body) + + part, _ := mpw.CreateFormFile("source-archive", "source-archive.zip") + io.Copy(part, sr) + + if metadata != "" { + mpw.WriteField("metadata", metadata) + } + + mpw.Close() + + req := NewRequestWithBody(t, "PUT", url, &body) + req.Header.Add("Content-Type", mpw.FormDataContentType()) + req.Header.Add("Accept", swift_router.AcceptJSON) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, expectedStatus) + } + + createArchive := func(files map[string]string) *bytes.Buffer { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + for filename, content := range files { + w, _ := zw.Create(filename) + w.Write([]byte(content)) + } + zw.Close() + return &buf + } + + for _, triple := range []string{"/sc_ope/package/1.0.0", "/scope/pack~age/1.0.0", "/scope/package/1_0.0"} { + req := NewRequestWithBody(t, "PUT", url+triple, bytes.NewReader([]byte{})) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusBadRequest) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) + } + + uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion) + + req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) + MakeRequest(t, req, http.StatusUnauthorized) + + uploadPackage( + t, + uploadURL, + http.StatusCreated, + createArchive(map[string]string{ + "Package.swift": contentManifest1, + "Package@swift-5.6.swift": contentManifest2, + }), + `{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`, + ) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.Equal(t, packageID, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + assert.IsType(t, &swift_module.Metadata{}, pd.Metadata) + metadata := pd.Metadata.(*swift_module.Metadata) + assert.Equal(t, packageDescription, metadata.Description) + assert.Len(t, metadata.Manifests, 2) + assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) + assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) + assert.Len(t, pd.VersionProperties, 1) + assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion), pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + uploadPackage( + t, + uploadURL, + http.StatusConflict, + createArchive(map[string]string{ + "Package.swift": contentManifest1, + }), + "", + ) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s.zip", url, packageScope, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + req.Header.Add("Accept", swift_router.AcceptZip) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "application/zip", resp.Header().Get("Content-Type")) + + pv, err := packages.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages.TypeSwift, packageID, packageVersion) + assert.NotNil(t, pv) + assert.NoError(t, err) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv) + assert.NoError(t, err) + assert.Equal(t, "sha256="+pd.Files[0].Blob.HashSHA256, resp.Header().Get("Digest")) + }) + + t.Run("EnumeratePackageVersions", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", url, packageScope, packageName)) + req = AddBasicAuthHeader(req, user.Name) + req.Header.Add("Accept", swift_router.AcceptJSON) + resp := MakeRequest(t, req, http.StatusOK) + + versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link")) + + body := resp.Body.String() + + var result *swift_router.EnumeratePackageVersionsResponse + DecodeJSON(t, resp, &result) + + assert.Len(t, result.Releases, 1) + assert.Contains(t, result.Releases, packageVersion) + assert.Equal(t, versionURL, result.Releases[packageVersion].URL) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)) + req = AddBasicAuthHeader(req, user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, body, resp.Body.String()) + }) + + t.Run("PackageVersionMetadata", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + req.Header.Add("Accept", swift_router.AcceptJSON) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + + body := resp.Body.String() + + var result *swift_router.PackageVersionMetadataResponse + DecodeJSON(t, resp, &result) + + pv, err := packages.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages.TypeSwift, packageID, packageVersion) + assert.NotNil(t, pv) + assert.NoError(t, err) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv) + assert.NoError(t, err) + + assert.Equal(t, packageID, result.ID) + assert.Equal(t, packageVersion, result.Version) + assert.Len(t, result.Resources, 1) + assert.Equal(t, "source-archive", result.Resources[0].Name) + assert.Equal(t, "application/zip", result.Resources[0].Type) + assert.Equal(t, pd.Files[0].Blob.HashSHA256, result.Resources[0].Checksum) + assert.Equal(t, "SoftwareSourceCode", result.Metadata.Type) + assert.Equal(t, packageName, result.Metadata.Name) + assert.Equal(t, packageVersion, result.Metadata.Version) + assert.Equal(t, packageDescription, result.Metadata.Description) + assert.Equal(t, "Swift", result.Metadata.ProgrammingLanguage.Name) + assert.Equal(t, packageAuthor, result.Metadata.Author.GivenName) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s.json", url, packageScope, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, body, resp.Body.String()) + }) + + t.Run("DownloadManifest", func(t *testing.T) { + manifestURL := fmt.Sprintf("%s/%s/%s/%s/Package.swift", url, packageScope, packageName, packageVersion) + + t.Run("Default", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", manifestURL) + req = AddBasicAuthHeader(req, user.Name) + req.Header.Add("Accept", swift_router.AcceptSwift) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "text/x-swift", resp.Header().Get("Content-Type")) + assert.Equal(t, contentManifest1, resp.Body.String()) + }) + + t.Run("DifferentVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", manifestURL+"?swift-version=5.6") + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "text/x-swift", resp.Header().Get("Content-Type")) + assert.Equal(t, contentManifest2, resp.Body.String()) + + req = NewRequest(t, "GET", manifestURL+"?swift-version=5.6.0") + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusOK) + }) + + t.Run("Redirect", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", manifestURL+"?swift-version=1.0") + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusSeeOther) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, setting.AppURL+url[1:]+fmt.Sprintf("/%s/%s/%s/Package.swift", packageScope, packageName, packageVersion), resp.Header().Get("Location")) + }) + }) + + t.Run("LookupPackageIdentifiers", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", url+"/identifiers") + req.Header.Add("Accept", swift_router.AcceptJSON) + resp := MakeRequest(t, req, http.StatusBadRequest) + + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) + + req = NewRequest(t, "GET", url+"/identifiers?url=https://unknown.host/") + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", url+"/identifiers?url="+packageRepositoryURL) + req.Header.Add("Accept", swift_router.AcceptJSON) + resp = MakeRequest(t, req, http.StatusOK) + + var result *swift_router.LookupPackageIdentifiersResponse + DecodeJSON(t, resp, &result) + + assert.Len(t, result.Identifiers, 1) + assert.Equal(t, packageID, result.Identifiers[0]) + }) +} diff --git a/web_src/svg/gitea-swift.svg b/web_src/svg/gitea-swift.svg new file mode 100644 index 0000000000..8af43d32e4 --- /dev/null +++ b/web_src/svg/gitea-swift.svg @@ -0,0 +1,5 @@ + + + + + From 5eea61dbc8f8e82e0dd05addf76751ee517459a0 Mon Sep 17 00:00:00 2001 From: sillyguodong <33891828+sillyguodong@users.noreply.github.com> Date: Tue, 14 Mar 2023 05:05:19 +0800 Subject: [PATCH 05/94] Fix missing commit status in PR which from forked repo (#23351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close: #23347 ### Reference and Inference According to Github REST API [doc](https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#list-commit-statuses-for-a-reference): 1. The `Drone CI` that can create some commit status by [API](https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#create-a-commit-status) is enabled in `go-gitea/gitea`. So I tried to call the API to get a commit status list of a PR which is commited to upstream repo(`go-gitea/gitea`). As a result, the API returned a array of commit status. ![image](https://user-images.githubusercontent.com/33891828/223913371-313d047a-5e2e-484c-b13e-dcd38748703e.png) 2. Then I tried to call the API to get commit status list of the reference which of the `SHA` is the same as step 1 in the repo which is forked from `go-gitea/gitea`. But I got a empty array. ![image](https://user-images.githubusercontent.com/33891828/223930827-17a64d3c-f466-4980-897c-77fe386c4d3b.png) So, I believe it that: 1. The commit status is not shared between upstream repo and forked repo. 2. The coomit status is bound to a repo that performs actions. (Gitea's logic is the same) ### Cause During debugging, I found it that commit status are not stored in the DB as expected. So, I located the following code: https://github.com/go-gitea/gitea/blob/8cadd51bf295e6ff36ac36efed68cc5de34c9382/services/actions/commit_status.go#L18-L26 When I create a PR, the type of `event` is `pull request`, not `push`. So the code return function directly. ### Screenshot ![image](https://user-images.githubusercontent.com/33891828/223939339-dadf539c-1fdd-40c4-96e9-2e4fa733f531.png) ![image](https://user-images.githubusercontent.com/33891828/223939519-edb02bf0-2478-4ea5-9366-be85468f02db.png) ![image](https://user-images.githubusercontent.com/33891828/223939557-ec6f1375-5536-400e-8987-fb7d2fd452fa.png) ### Other In this PR, I also fix the problem of missing icon which represents running in PRs list. ![image](https://user-images.githubusercontent.com/33891828/223939898-2a0339e4-713f-4c7b-9d99-2250a43f3457.png) ![image](https://user-images.githubusercontent.com/33891828/223939979-037a975f-5ced-480c-bac7-0ee00ebfff4b.png) --- models/actions/run.go | 11 +++++ services/actions/commit_status.go | 75 ++++++++++++++++++++----------- templates/repo/commit_status.tmpl | 3 ++ 3 files changed, 64 insertions(+), 25 deletions(-) diff --git a/models/actions/run.go b/models/actions/run.go index d5ab45a519..a711cfee2e 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -128,6 +128,17 @@ func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) { return nil, fmt.Errorf("event %s is not a push event", run.Event) } +func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) { + if run.Event == webhook_module.HookEventPullRequest { + var payload api.PullRequestPayload + if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { + return nil, err + } + return &payload, nil + } + return nil, fmt.Errorf("event %s is not a pull request event", run.Event) +} + func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { _, err := db.GetEngine(ctx).ID(repo.ID). SetExpr("num_action_runs", diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 4f31349352..84de106eec 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -21,35 +21,60 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er } run := job.Run - if run.Event != webhook_module.HookEventPush { + var ( + sha string + creatorID int64 + ) + + switch run.Event { + case webhook_module.HookEventPush: + payload, err := run.GetPushEventPayload() + if err != nil { + return fmt.Errorf("GetPushEventPayload: %w", err) + } + + // Since the payload comes from json data, we should check if it's broken, or it will cause panic + switch { + case payload.Repo == nil: + return fmt.Errorf("repo is missing in event payload") + case payload.Pusher == nil: + return fmt.Errorf("pusher is missing in event payload") + case payload.HeadCommit == nil: + return fmt.Errorf("head commit is missing in event payload") + } + + sha = payload.HeadCommit.ID + creatorID = payload.Pusher.ID + case webhook_module.HookEventPullRequest: + payload, err := run.GetPullRequestEventPayload() + if err != nil { + return fmt.Errorf("GetPullRequestEventPayload: %w", err) + } + + switch { + case payload.PullRequest == nil: + return fmt.Errorf("pull request is missing in event payload") + case payload.PullRequest.Head == nil: + return fmt.Errorf("head of pull request is missing in event payload") + case payload.PullRequest.Head.Repository == nil: + return fmt.Errorf("head repository of pull request is missing in event payload") + case payload.PullRequest.Head.Repository.Owner == nil: + return fmt.Errorf("owner of head repository of pull request is missing in evnt payload") + } + + sha = payload.PullRequest.Head.Sha + creatorID = payload.PullRequest.Head.Repository.Owner.ID + default: return nil } - payload, err := run.GetPushEventPayload() - if err != nil { - return fmt.Errorf("GetPushEventPayload: %w", err) - } - - // Since the payload comes from json data, we should check if it's broken, or it will cause panic - switch { - case payload.Repo == nil: - return fmt.Errorf("repo is missing in event payload") - case payload.Pusher == nil: - return fmt.Errorf("pusher is missing in event payload") - case payload.HeadCommit == nil: - return fmt.Errorf("head commit is missing in event payload") - } - - creator, err := user_model.GetUserByID(ctx, payload.Pusher.ID) + repo := run.Repo + ctxname := job.Name + state := toCommitStatus(job.Status) + creator, err := user_model.GetUserByID(ctx, creatorID) if err != nil { return fmt.Errorf("GetUserByID: %w", err) } - - repo := run.Repo - sha := payload.HeadCommit.ID - ctxname := job.Name - state := toCommitStatus(job.Status) - if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil { for _, v := range statuses { if v.Context == ctxname { @@ -65,14 +90,14 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ Repo: repo, - SHA: payload.HeadCommit.ID, + SHA: sha, Creator: creator, CommitStatus: &git_model.CommitStatus{ SHA: sha, TargetURL: run.Link(), Description: "", Context: ctxname, - CreatorID: payload.Pusher.ID, + CreatorID: creatorID, State: state, }, }); err != nil { diff --git a/templates/repo/commit_status.tmpl b/templates/repo/commit_status.tmpl index fbf064527d..470869b381 100644 --- a/templates/repo/commit_status.tmpl +++ b/templates/repo/commit_status.tmpl @@ -1,6 +1,9 @@ {{if eq .State "pending"}} {{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}} {{end}} +{{if eq .State "running"}} + {{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}} +{{end}} {{if eq .State "success"}} {{svg "octicon-check" 18 "commit-status icon text green"}} {{end}} From 8421b8264fd7715ec93b13f37be31a945faec556 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Tue, 14 Mar 2023 05:55:30 +0800 Subject: [PATCH 06/94] Handle missing `README` in create repos API (#23387) Close #22934 In `/user/repos` API (and other APIs related to creating repos), user can specify a readme template for auto init. At present, if the specified template does not exist, a `500` will be returned . This PR improved the logic and will return a `400` instead of `500`. --- routers/api/v1/admin/repo.go | 2 ++ routers/api/v1/repo/repo.go | 11 +++++++++++ templates/swagger/v1_json.tmpl | 9 +++++++++ 3 files changed, 22 insertions(+) diff --git a/routers/api/v1/admin/repo.go b/routers/api/v1/admin/repo.go index 83ed06e49b..a4895f260b 100644 --- a/routers/api/v1/admin/repo.go +++ b/routers/api/v1/admin/repo.go @@ -32,6 +32,8 @@ func CreateRepo(ctx *context.APIContext) { // responses: // "201": // "$ref": "#/responses/Repository" + // "400": + // "$ref": "#/responses/error" // "403": // "$ref": "#/responses/forbidden" // "404": diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 397600dc50..16608e5bbb 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -231,6 +231,13 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre if opt.AutoInit && opt.Readme == "" { opt.Readme = "Default" } + + // If the readme template does not exist, a 400 will be returned. + if opt.AutoInit && len(opt.Readme) > 0 && !util.SliceContains(repo_module.Readmes, opt.Readme) { + ctx.Error(http.StatusBadRequest, "", fmt.Errorf("readme template does not exist, available templates: %v", repo_module.Readmes)) + return + } + repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_module.CreateRepoOptions{ Name: opt.Name, Description: opt.Description, @@ -283,6 +290,8 @@ func Create(ctx *context.APIContext) { // responses: // "201": // "$ref": "#/responses/Repository" + // "400": + // "$ref": "#/responses/error" // "409": // description: The repository with the same name already exists. // "422": @@ -464,6 +473,8 @@ func CreateOrgRepo(ctx *context.APIContext) { // responses: // "201": // "$ref": "#/responses/Repository" + // "400": + // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" // "403": diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 9c46b25eaf..c304a7a497 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -713,6 +713,9 @@ "201": { "$ref": "#/responses/Repository" }, + "400": { + "$ref": "#/responses/error" + }, "403": { "$ref": "#/responses/forbidden" }, @@ -1926,6 +1929,9 @@ "201": { "$ref": "#/responses/Repository" }, + "400": { + "$ref": "#/responses/error" + }, "403": { "$ref": "#/responses/forbidden" }, @@ -13382,6 +13388,9 @@ "201": { "$ref": "#/responses/Repository" }, + "400": { + "$ref": "#/responses/error" + }, "409": { "description": "The repository with the same name already exists." }, From 8570593d5526812ee9adc95485a11f51d55575d6 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 13 Mar 2023 23:15:09 +0100 Subject: [PATCH 07/94] Add package registry architecture overview (#23445) As announced in #22810 I added a readme file to help understanding how the package registry archictecture works and how the go packages are related. --- routers/api/packages/README.md | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 routers/api/packages/README.md diff --git a/routers/api/packages/README.md b/routers/api/packages/README.md new file mode 100644 index 0000000000..533a0d32f0 --- /dev/null +++ b/routers/api/packages/README.md @@ -0,0 +1,50 @@ +# Gitea Package Registry + +This document gives a brief overview how the package registry is organized in code. + +## Structure + +The package registry code is divided into multiple modules to split the functionality and make code reuse possible. + +| Module | Description | +| - | - | +| `models/packages` | Common methods and models used by all registry types | +| `models/packages/` | Methods used by specific registry type. There should be no need to use type specific models. | +| `modules/packages` | Common methods and types used by multiple registry types | +| `modules/packages/` | Registry type specific methods and types (e.g. metadata extraction of package files) | +| `routers/api/packages` | Route definitions for all registry types | +| `routers/api/packages/` | Route implementation for a specific registry type | +| `services/packages` | Helper methods used by registry types to handle common tasks like package creation and deletion in `routers` | +| `services/packages/` | Registry type specific methods used by `routers` and `services` | + +## Models + +Every package registry implementation uses the same underlaying models: + +| Model | Description | +| - | - | +| `Package` | The root of a package providing values fixed for every version (e.g. the package name) | +| `PackageVersion` | A version of a package containing metadata (e.g. the package description) | +| `PackageFile` | A file of a package describing its content (e.g. file name) | +| `PackageBlob` | The content of a file (may be shared by multiple files) | +| `PackageProperty` | Additional properties attached to `Package`, `PackageVersion` or `PackageFile` (e.g. used if metadata is needed for routing) | + +The following diagram shows the relationship between the models: +``` +Package <1---*> PackageVersion <1---*> PackageFile <*---1> PackageBlob +``` + +## Adding a new package registry type + +Before adding a new package registry type have a look at the existing implementation to get an impression of how it could work. +Most registry types offer endpoints to retrieve the metadata, upload and download package files. +The upload endpoint is often the heavy part because it must validate the uploaded blob, extract metadata and create the models. +The methods to validate and extract the metadata should be added in the `modules/packages/` package. +If the upload is valid the methods in `services/packages` allow to store the upload and create the corresponding models. +It depends if the registry type allows multiple files per package version which method should be called: +- `CreatePackageAndAddFile`: error if package version already exists +- `CreatePackageOrAddFileToExisting`: error if file already exists +- `AddFileToExistingPackage`: error if package version does not exist or file already exists + +`services/packages` also contains helper methods to download a file or to remove a package version. +There are no helper methods for metadata endpoints because they are very type specific. From 605fd15ad6eda19dba8f5e8a8f2e595e34e6c6ee Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 14 Mar 2023 00:16:09 +0000 Subject: [PATCH 08/94] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-BR.ini | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 55529df882..b34b5642dc 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -247,6 +247,7 @@ default_enable_timetracking_popup=Habilitar o cronômetro para novos repositóri no_reply_address=Domínio de e-mail oculto no_reply_address_helper=Nome de domínio para usuários com um endereço de e-mail oculto. Por exemplo, o nome de usuário 'joe' será registrado no Git como 'joe@noreply.example.org' se o domínio de e-mail oculto estiver definido como 'noreply.example.org'. password_algorithm=Algoritmo Hash de Senha +invalid_password_algorithm=Algoritmo de hash de senha inválido password_algorithm_helper=Escolha o algoritmo de hash para as senhas. Diferentes algoritmos têm requerimentos e forças diversos. O `Argon2` possui boa qualidade, porém usa muita memória e pode ser inapropriado para sistemas com menos recursos. enable_update_checker=Habilitar Verificador de Atualizações enable_update_checker_helper=Procura por novas versões periodicamente conectando-se ao gitea.io. @@ -284,6 +285,7 @@ users=Usuários organizations=Organizações search=Pesquisar code=Código +search.type.tooltip=Tipo de pesquisa search.fuzzy=Similar search.fuzzy.tooltip=Incluir resultados que sejam próximos ao termo de busca search.match=Correspondência @@ -819,6 +821,7 @@ remove_account_link=Remover conta vinculada remove_account_link_desc=A exclusão da chave SSH revogará o acesso à sua conta. Continuar? remove_account_link_success=A conta vinculada foi removida. +hooks.desc=Adicionar webhooks que serão acionados para todos os repositórios pertencentes a este usuário. orgs_none=Você não é membro de nenhuma organização. repos_none=Você não possui nenhum repositório @@ -1231,6 +1234,7 @@ projects.column.color=Colorido projects.open=Abrir projects.close=Fechar projects.column.assigned_to=Atribuído a +projects.card_type.desc=Pré-visualizações de Cards projects.card_type.images_and_text=Imagens e Texto projects.card_type.text_only=Somente texto @@ -1399,6 +1403,7 @@ issues.label_title=Nome da etiqueta issues.label_description=Descrição da etiqueta issues.label_color=Cor da etiqueta issues.label_exclusive=Exclusivo +issues.label_exclusive_desc=Nomeie o rótulo escopo/item para torná-lo mutuamente exclusivo com outros rótulos do escopo/. issues.label_exclusive_warning=Quaisquer rótulos com escopo conflitantes serão removidos ao editar os rótulos de uma issue ou pull request. issues.label_count=%d etiquetas issues.label_open_issues=%d issues abertas @@ -1655,6 +1660,7 @@ pulls.merge_instruction_hint=`Você também pode ver as *, eventos para todos os branches serão relatados. Veja github.com/gobwas/glob documentação da sintaxe. Exemplos: master, {master,release*}. settings.authorization_header=Header de Autorização +settings.authorization_header_desc=Será incluído como header de autorização para solicitações quando estiver presente. Exemplos: %s. settings.active=Ativo settings.active_helper=Informações sobre eventos disparados serão enviadas para esta URL do webhook. settings.add_hook_success=O webhook foi adicionado. @@ -2124,6 +2132,7 @@ settings.dismiss_stale_approvals=Descartar aprovações obsoletas settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas. settings.require_signed_commits=Exibir commits assinados settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis. +settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida settings.protect_protected_file_patterns=Padrões de arquivos protegidos (separados usando ponto e vírgula '\;'): settings.protect_protected_file_patterns_desc=Arquivos protegidos que não têm permissão para serem alterados diretamente, mesmo se o usuário tiver permissão para adicionar, editar ou apagar arquivos neste branch. Vários padrões podem ser separados usando ponto e vírgula ('\;'). Veja github.com/gobwas/glob documentação para sintaxe de padrões. Exemplos: .drone.yml, /docs/**/*.txt. settings.protect_unprotected_file_patterns=Padrões de arquivos desprotegidos (separados usando ponto e vírgula '\;'): @@ -2132,6 +2141,7 @@ settings.add_protected_branch=Habilitar proteção settings.delete_protected_branch=Desabilitar proteção settings.update_protect_branch_success=Proteção do branch '%s' foi atualizada. settings.remove_protected_branch_success=Proteção do branch '%s' foi desabilitada. +settings.remove_protected_branch_failed=Removendo regra de proteção de branch '%s' falhou. settings.protected_branch_deletion=Desabilitar proteção de branch settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar? settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas @@ -2146,6 +2156,8 @@ settings.default_merge_style_desc=Estilo de merge padrão para pull requests: settings.choose_branch=Escolha um branch... settings.no_protected_branch=Não há branches protegidos. settings.edit_protected_branch=Editar +settings.protected_branch_required_rule_name=Nome da regra é obrigatório +settings.protected_branch_duplicate_rule_name=Regra com nome duplicado settings.protected_branch_required_approvals_min=Aprovações necessárias não podem ser negativas. settings.tags=Tags settings.tags.protection=Proteção das Tags @@ -2278,6 +2290,8 @@ release.edit_subheader=Lançamentos organizam versões do projeto. release.tag_name=Nome da tag release.target=Destino release.tag_helper=Escolha uma tag existente, ou crie uma nova tag. +release.tag_helper_new=Nova tag. Esta tag será criada a partir do alvo. +release.tag_helper_existing=Tag existente. release.title=Título release.content=Conteúdo release.prerelease_desc=Marcar como pré-lançamento @@ -2570,6 +2584,10 @@ dashboard.delete_old_actions=Excluir todas as ações antigas do banco de dados dashboard.delete_old_actions.started=A exclusão de todas as ações antigas do banco de dados foi iniciada. dashboard.update_checker=Verificador de atualização dashboard.delete_old_system_notices=Excluir todos os avisos de sistema antigos do banco de dados +dashboard.gc_lfs=Coletar lixos dos meta-objetos LFS +dashboard.stop_zombie_tasks=Parar tarefas zumbi +dashboard.stop_endless_tasks=Parar tarefas infinitas +dashboard.cancel_abandoned_jobs=Cancelar trabalhos abandonados users.user_manage_panel=Gerenciamento de conta de usuário users.new_account=Criar conta de usuário @@ -2658,6 +2676,7 @@ repos.size=Tamanho packages.package_manage_panel=Gerenciamento de Pacotes packages.total_size=Tamanho Total: %s +packages.unreferenced_size=Tamanho Não Referenciado: %s packages.owner=Proprietário packages.creator=Criador packages.name=Nome @@ -2751,6 +2770,8 @@ auths.oauth2_required_claim_value_helper=Defina este valor para permitir o login auths.oauth2_group_claim_name=Nome do claim que fornece os nomes dos grupos para esta fonte. (Opcional) auths.oauth2_admin_group=Valor do Claim de Grupo para os usuários administradores. (Opcional - requer nome do claim acima) auths.oauth2_restricted_group=Valor do Claim de Grupo para os usuários restritos. (Opcional - requer nome do claim acima) +auths.oauth2_map_group_to_team=Mapear grupos para Organizações. (Opcional - requer nome do claim acima) +auths.oauth2_map_group_to_team_removal=Remover usuários de equipes sincronizadas se o usuário não pertence ao grupo correspondente. auths.enable_auto_register=Habilitar cadastro automático auths.sspi_auto_create_users=Criar usuários automaticamente auths.sspi_auto_create_users_helper=Permitir que o método de autenticação SSPI crie automaticamente novas contas para usuários que fazem o login pela primeira vez @@ -2791,6 +2812,8 @@ auths.still_in_used=A fonte de autenticação ainda está em uso. Converta ou ex auths.deletion_success=A fonte de autenticação foi excluída. auths.login_source_exist=A fonte de autenticação '%s' já existe. auths.login_source_of_type_exist=Uma fonte de autenticação deste tipo já existe. +auths.unable_to_initialize_openid=Não é possível inicializar o Provedor OpenID Connect: %s +auths.invalid_openIdConnectAutoDiscoveryURL=URL do Auto Discovery inválida (deve ser uma URL válida, começando com http:// ou https://) config.server_config=Configuração do servidor config.app_name=Nome do servidor @@ -3039,6 +3062,7 @@ reopen_pull_request=`reabriu o pull request %[3]s#%[2]s` comment_issue=`comentou na issue %[3]s#%[2]s` comment_pull=`comentou no pull request %[3]s#%[2]s` merge_pull_request=`fez merge do pull request %[3]s#%[2]s` +auto_merge_pull_request=`fez merge automático do pull request %[3]s#%[2]s` transfer_repo=transferiu repositório de %s para %s push_tag=fez push da tag %[3]s to %[4]s delete_tag=excluiu tag %[2]s de %[3]s @@ -3148,10 +3172,12 @@ dependency.id=ID dependency.version=Versão cargo.registry=Configurar este registro no arquivo de configuração de Cargo (por exemplo ~/.cargo/config.toml): cargo.install=Para instalar o pacote usando Cargo, execute o seguinte comando: +cargo.documentation=Para obter mais informações sobre o registro Cargo, consulte a documentação. cargo.details.repository_site=Site do Repositório cargo.details.documentation_site=Site da Documentação chef.registry=Configure este registro em seu arquivo ~/.chef/config.rb: chef.install=Para instalar o pacote, execute o seguinte comando: +chef.documentation=Para obter mais informações sobre o registro Chef, consulte a documentação. composer.registry=Configure este registro em seu arquivo ~/.composer/config.json: composer.install=Para instalar o pacote usando o Composer, execute o seguinte comando: composer.documentation=Para obter mais informações sobre o registro do Composer, consulte a documentação. @@ -3224,6 +3250,15 @@ settings.delete.description=A exclusão de um pacote é permanente e não pode s settings.delete.notice=Você está prestes a excluir %s (%s). Esta operação é irreversível, tem certeza? settings.delete.success=O pacote foi excluído. settings.delete.error=Falha ao excluir o pacote. +owner.settings.cargo.title=Índice do Registro Cargo +owner.settings.cargo.initialize=Iniciar Índice +owner.settings.cargo.initialize.description=Para usar o registro Cargo é necessário um repositório git especial. Aqui você pode (re)criá-lo com a configuração necessária. +owner.settings.cargo.initialize.error=Falha ao inicializar índice Cargo: %v +owner.settings.cargo.initialize.success=O índice Cargo foi criado com sucesso. +owner.settings.cargo.rebuild=Reconstruir Índice +owner.settings.cargo.rebuild.description=Se o índice está fora de sincronia com os pacotes Cargo, você pode reconstruí-lo aqui. +owner.settings.cargo.rebuild.error=Falha ao reconstruir índice Cargo: %v +owner.settings.cargo.rebuild.success=O índice Cargo foi reconstruído com sucesso. owner.settings.cleanuprules.title=Gerenciar Regras de Limpeza owner.settings.cleanuprules.add=Adicionar Regra de Limpeza owner.settings.cleanuprules.edit=Editar Regra de Limpeza @@ -3232,6 +3267,7 @@ owner.settings.cleanuprules.preview=Pré-visualizar Regra de Limpeza owner.settings.cleanuprules.preview.overview=%d pacotes agendados para serem removidos. owner.settings.cleanuprules.preview.none=A regra de limpeza não corresponde a nenhum pacote. owner.settings.cleanuprules.enabled=Habilitado +owner.settings.cleanuprules.pattern_full_match=Aplicar padrão ao nome completo do pacote owner.settings.cleanuprules.keep.title=Versões que correspondem a estas regras são mantidas, mesmo se corresponderem a uma regra de remoção abaixo. owner.settings.cleanuprules.keep.count=Manter o mais recente owner.settings.cleanuprules.keep.count.1=1 versão por pacote @@ -3245,6 +3281,7 @@ owner.settings.cleanuprules.success.update=Regra de limpeza foi atualizada. owner.settings.cleanuprules.success.delete=Regra de limpeza foi excluída. owner.settings.chef.title=Registro Chef owner.settings.chef.keypair=Gerar par de chaves +owner.settings.chef.keypair.description=Gerar um par de chaves usado para autenticar no registro Chef. A chave anterior não pode ser usada depois. [secrets] secrets=Segredos @@ -3253,6 +3290,8 @@ none=Não há segredos ainda. value=Valor name=Nome creation=Adicionar Segredo +creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_ +creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos. creation.success=O segredo '%s' foi adicionado. creation.failed=Falha ao adicionar segredo. deletion=Excluir segredo @@ -3274,6 +3313,10 @@ status.cancelled=Cancelado status.skipped=Ignorado status.blocked=Bloqueado +runners=Runners +runners.runner_manage_panel=Gerenciamento de Runners +runners.new=Criar novo Runner +runners.new_notice=Como iniciar um runner runners.status=Status runners.id=ID runners.name=Nome @@ -3281,21 +3324,36 @@ runners.owner_type=Tipo runners.description=Descrição runners.labels=Rótulos runners.last_online=Última Vez Online +runners.agent_labels=Etiquetas do Agente runners.custom_labels=Etiquetas Personalizadas runners.custom_labels_helper=Etiquetas personalizadas são etiquetas que são adicionadas manualmente por um administrador. Separe as etiquetas com vírgula. Espaço em branco no começo ou no final de cada etiqueta é ignorado. +runners.runner_title=Runner +runners.task_list=Tarefas recentes neste runner runners.task_list.run=Executar runners.task_list.status=Status runners.task_list.repository=Repositório runners.task_list.commit=Commit +runners.task_list.done_at=Feito em +runners.edit_runner=Editar Runner runners.update_runner=Atualizar as Alterações +runners.update_runner_success=Runner atualizado com sucesso +runners.update_runner_failed=Falha ao atualizar runner +runners.delete_runner=Deletar esse runner +runners.delete_runner_success=Runner excluído com sucesso +runners.delete_runner_failed=Falha ao excluir runner +runners.delete_runner_header=Confirme para excluir este runner +runners.delete_runner_notice=Se uma tarefa estiver sendo executada neste runner, ela será encerrada e marcada como falha. Pode quebrar o workflow de construção. +runners.none=Nenhum runner disponível runners.status.unspecified=Desconhecido runners.status.idle=Inativo runners.status.active=Ativo runners.status.offline=Offiline +runs.all_workflows=Todos os Workflows runs.open_tab=%d Aberto runs.closed_tab=%d Fechado runs.commit=Commit runs.pushed_by=Push realizado por +need_approval_desc=Precisa de aprovação para executar workflowa para pull request do fork. From 81fe5d61851c0e586af7d32c29171ceff9a571bb Mon Sep 17 00:00:00 2001 From: delvh Date: Tue, 14 Mar 2023 04:34:09 +0100 Subject: [PATCH 09/94] Convert `
` to ` -
{{.locale.Tr "admin.auths.delete"}}
+
diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index 091f5011f9..d8fa986cff 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -78,7 +78,7 @@ {{.locale.Tr "admin.emails.change_email_header"}}
-

{{.locale.Tr "admin.emails.change_email_text"}}

+

{{.locale.Tr "admin.emails.change_email_text"}}

{{$.CsrfTokenHtml}} @@ -93,11 +93,9 @@ -
-
{{$.locale.Tr "settings.cancel"}}
- +
+ {{template "base/delete_modal_actions" .}}
-
diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index a2c7ca2f6a..34bd83a214 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -23,7 +23,7 @@
- +
{{.ID}} @@ -39,13 +39,11 @@ -
-
- {{.CsrfTokenHtml}} - -
-
- @@ -70,16 +61,7 @@ -
-
- {{svg "octicon-trash" 16 "gt-mr-2"}} - {{$.locale.Tr "modal.no"}} -
- -
+ {{template "base/delete_modal_actions" .}} diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 5dd1f531fd..73017e1b13 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -151,7 +151,7 @@
-
{{.locale.Tr "admin.users.delete_account"}}
+
@@ -189,7 +189,7 @@
@@ -213,16 +213,7 @@

{{.locale.Tr "admin.users.purge_help"}}

-
-
- {{svg "octicon-x"}} - {{.locale.Tr "modal.no"}} -
- -
+ {{template "base/delete_modal_actions" .}} {{template "base/footer" .}} diff --git a/templates/base/delete_modal_actions.tmpl b/templates/base/delete_modal_actions.tmpl index fb4d31270a..29bf5f92fd 100644 --- a/templates/base/delete_modal_actions.tmpl +++ b/templates/base/delete_modal_actions.tmpl @@ -1,10 +1,10 @@
-
+
-
+ +
+
diff --git a/templates/org/settings/delete.tmpl b/templates/org/settings/delete.tmpl index 669e393e1d..69e226f410 100644 --- a/templates/org/settings/delete.tmpl +++ b/templates/org/settings/delete.tmpl @@ -19,9 +19,9 @@ -
+
+ diff --git a/templates/org/settings/labels.tmpl b/templates/org/settings/labels.tmpl index 5436bcba05..e04b391271 100644 --- a/templates/org/settings/labels.tmpl +++ b/templates/org/settings/labels.tmpl @@ -11,7 +11,7 @@
-
{{.locale.Tr "repo.issues.new_label"}}
+
diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index dc12fb8207..875bf852bb 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -57,10 +57,7 @@
{{.CsrfTokenHtml}} -
-
{{.locale.Tr "cancel"}}
- -
+ {{template "base/delete_modal_actions" .}}
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 4a21c0fd28..89c52dee68 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -84,15 +84,6 @@

{{.locale.Tr "repo.projects.deletion_desc"}}

-
-
- - {{.locale.Tr "modal.no"}} -
-
- - {{.locale.Tr "modal.yes"}} -
-
+ {{template "base/delete_modal_actions" .}} {{end}} diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 6867309510..b776f89efa 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -29,7 +29,7 @@
-
{{$.locale.Tr "settings.cancel"}}
+
@@ -127,7 +127,7 @@
-
{{$.locale.Tr "settings.cancel"}}
+
@@ -144,7 +144,7 @@
-
{{$.locale.Tr "settings.cancel"}}
+
@@ -158,8 +158,8 @@ {{$.locale.Tr "repo.projects.column.deletion_desc"}} -
-
{{$.locale.Tr "settings.cancel"}}
+
{{/* TODO: convert to base/delete_modal_actions.tmpl */}} +
@@ -265,15 +265,6 @@

{{.locale.Tr "repo.projects.deletion_desc"}}

-
-
- - {{.locale.Tr "modal.no"}} -
-
- - {{.locale.Tr "modal.yes"}} -
-
+ {{template "base/delete_modal_actions" .}} {{end}} diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index a093c19deb..7e8bf348a4 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -176,7 +176,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
diff --git a/templates/repo/cite/cite_modal.tmpl b/templates/repo/cite/cite_modal.tmpl index 185b34173d..f00bab8859 100644 --- a/templates/repo/cite/cite_modal.tmpl +++ b/templates/repo/cite/cite_modal.tmpl @@ -15,8 +15,8 @@
-
+
+
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index f19a4d4223..ace5a41087 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -96,7 +96,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
@@ -121,7 +121,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index afd471368f..e0c58896f0 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -107,8 +107,8 @@
{{if $showFileViewToggle}}
- {{svg "octicon-code"}} - {{svg "octicon-file"}} + +
{{end}} {{if $file.IsProtected}} @@ -200,8 +200,8 @@ {{$.locale.Tr "loading"}}
-
{{.locale.Tr "repo.issues.cancel"}}
-
{{.locale.Tr "repo.issues.save"}}
+ +
diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 992ccee8e4..431033e18e 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -65,14 +65,14 @@

{{.locale.Tr "repo.editor.commit_empty_file_text"}}

-
+
-
+ +
+
diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl index bbd5c2dbde..75a8b5d687 100644 --- a/templates/repo/editor/patch.tmpl +++ b/templates/repo/editor/patch.tmpl @@ -45,14 +45,14 @@

{{.locale.Tr "repo.editor.commit_empty_file_text"}}

-
- +
-
- + +
+
diff --git a/templates/repo/issue/labels.tmpl b/templates/repo/issue/labels.tmpl index 82cfcd0712..0a25d9c87f 100644 --- a/templates/repo/issue/labels.tmpl +++ b/templates/repo/issue/labels.tmpl @@ -6,7 +6,7 @@ {{template "repo/issue/navbar" .}} {{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
-
{{.locale.Tr "repo.issues.new_label"}}
+
{{end}} diff --git a/templates/repo/issue/labels/edit_delete_label.tmpl b/templates/repo/issue/labels/edit_delete_label.tmpl index 450061e835..38a948172f 100644 --- a/templates/repo/issue/labels/edit_delete_label.tmpl +++ b/templates/repo/issue/labels/edit_delete_label.tmpl @@ -6,16 +6,7 @@

{{.locale.Tr "repo.issues.label_deletion_desc"}}

-
-
- - {{.locale.Tr "modal.no"}} -
-
- - {{.locale.Tr "modal.yes"}} -
-
+ {{template "base/delete_modal_actions" .}}
-
+
-
+ +
+
diff --git a/templates/repo/issue/labels/label_new.tmpl b/templates/repo/issue/labels/label_new.tmpl index 62f7155b74..c937f28e8a 100644 --- a/templates/repo/issue/labels/label_new.tmpl +++ b/templates/repo/issue/labels/label_new.tmpl @@ -36,12 +36,15 @@ +
-
+
-
+ +
+
diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index ca05264e77..36faf86113 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -213,9 +213,9 @@ {{if not .Repository.IsArchived}} {{if .IsShowClosed}} -
{{.locale.Tr "repo.issues.action_open"}}
+ {{else}} -
{{.locale.Tr "repo.issues.action_close"}}
+ {{end}} diff --git a/templates/repo/issue/view_content/comments_delete_time.tmpl b/templates/repo/issue/view_content/comments_delete_time.tmpl index bc08d7fde7..b79b7ae2be 100644 --- a/templates/repo/issue/view_content/comments_delete_time.tmpl +++ b/templates/repo/issue/view_content/comments_delete_time.tmpl @@ -7,10 +7,7 @@ {{.ctxData.CsrfTokenHtml}}
{{.ctxData.locale.Tr "repo.issues.del_time"}}
-
-
{{.ctxData.locale.Tr "repo.issues.context.delete"}}
-
{{.ctxData.locale.Tr "repo.issues.add_time_cancel"}}
-
+ {{template "base/delete_modal_actions" .}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index e58f94aff3..165dca7e0c 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -346,8 +346,8 @@
-
{{.locale.Tr "repo.issues.add_time_short"}}
-
{{.locale.Tr "repo.issues.add_time_cancel"}}
+ +
@@ -532,14 +532,14 @@ {{end}}

-
+
-
+ +
+
{{end}} @@ -619,7 +619,7 @@ {{end}}
-
{{.locale.Tr "settings.cancel"}}
+
diff --git a/templates/repo/issue/view_content/update_branch_by_merge.tmpl b/templates/repo/issue/view_content/update_branch_by_merge.tmpl index 6d36a9b45f..49e4467dc3 100644 --- a/templates/repo/issue/view_content/update_branch_by_merge.tmpl +++ b/templates/repo/issue/view_content/update_branch_by_merge.tmpl @@ -19,8 +19,8 @@ diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index f0ac1e021e..2a8381b0b4 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -1,9 +1,7 @@
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}} -
- -
+ {{end}}

{{RenderIssueTitle $.Context .Issue.Title $.RepoLink $.Repository.ComposeMetas | RenderCodeBlock}} diff --git a/templates/repo/migrate/migrating.tmpl b/templates/repo/migrate/migrating.tmpl index a3552610c4..cd3c5e754e 100644 --- a/templates/repo/migrate/migrating.tmpl +++ b/templates/repo/migrate/migrating.tmpl @@ -72,7 +72,7 @@

-
{{.locale.Tr "settings.cancel"}}
+
diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl index f066f84ea2..6833b7d785 100644 --- a/templates/repo/projects/list.tmpl +++ b/templates/repo/projects/list.tmpl @@ -86,16 +86,7 @@

{{.locale.Tr "repo.projects.deletion_desc"}}

-
-
- - {{.locale.Tr "modal.no"}} -
-
- - {{.locale.Tr "modal.yes"}} -
-
+ {{template "base/delete_modal_actions" .}}
{{end}} {{template "base/footer" .}} diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index bef9cb9bf0..0248b9c6d2 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -33,7 +33,7 @@
-
{{$.locale.Tr "settings.cancel"}}
+
@@ -131,7 +131,7 @@
-
{{$.locale.Tr "settings.cancel"}}
+
@@ -148,7 +148,7 @@
-
{{$.locale.Tr "settings.cancel"}}
+
@@ -162,8 +162,8 @@ {{$.locale.Tr "repo.projects.column.deletion_desc"}} -
-
{{$.locale.Tr "settings.cancel"}}
+
{{/* TODO: Convert to base/delete_modal_actions.tmpl? */}} +
@@ -276,16 +276,7 @@

{{.locale.Tr "repo.projects.deletion_desc"}}

-
-
- - {{.locale.Tr "modal.no"}} -
-
- - {{.locale.Tr "modal.yes"}} -
-
+ {{template "base/delete_modal_actions" .}} {{end}} diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index d7c580fed9..8c4df98d19 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -114,7 +114,7 @@ {{$.locale.Tr "repo.release.delete_release"}} {{if .IsDraft}} - + @@ -125,9 +125,9 @@ {{end}} {{else}} {{if not .tag_name}} - + {{end}} - + diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index 22fddeb4df..ea4fba240e 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -8,9 +8,9 @@ {{.locale.Tr "repo.settings.deploy_keys"}}
{{if not .DisableSSH}} -
{{.locale.Tr "repo.settings.add_deploy_key"}}
+ {{else}} -
{{.locale.Tr "settings.ssh_disabled"}}
+ {{end}}
@@ -85,15 +85,6 @@

{{.locale.Tr "repo.settings.deploy_key_deletion_desc"}}

-
-
- - {{.locale.Tr "modal.no"}} -
-
- - {{.locale.Tr "modal.yes"}} -
-
+ {{template "base/delete_modal_actions" .}} {{template "base/footer" .}} diff --git a/templates/repo/settings/lfs.tmpl b/templates/repo/settings/lfs.tmpl index 566a701efb..9a38d32345 100644 --- a/templates/repo/settings/lfs.tmpl +++ b/templates/repo/settings/lfs.tmpl @@ -50,8 +50,8 @@

{{$.CsrfTokenHtml}} -
-
{{$.locale.Tr "settings.cancel"}}
+
{{/* TODO: Convert to base/delete_modal_actions */}} +
diff --git a/templates/repo/settings/lfs_pointers.tmpl b/templates/repo/settings/lfs_pointers.tmpl index 8eebc6e870..67021ba6cd 100644 --- a/templates/repo/settings/lfs_pointers.tmpl +++ b/templates/repo/settings/lfs_pointers.tmpl @@ -49,9 +49,9 @@ {{ShortSha .Oid}} {{else}} - + {{end}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 030c77b881..be07aeb0ff 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -825,7 +825,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
@@ -856,7 +856,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
@@ -892,7 +892,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
@@ -926,7 +926,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
@@ -958,7 +958,7 @@
-
{{.locale.Tr "settings.cancel"}}
+
@@ -988,10 +988,7 @@ {{.CsrfTokenHtml}} -
-
{{.locale.Tr "settings.cancel"}}
- -
+ {{template "base/delete_modal_actions" .}} {{end}} diff --git a/templates/repo/settings/webhook/delete_modal.tmpl b/templates/repo/settings/webhook/delete_modal.tmpl index fdc49ada4e..f455899663 100644 --- a/templates/repo/settings/webhook/delete_modal.tmpl +++ b/templates/repo/settings/webhook/delete_modal.tmpl @@ -6,14 +6,5 @@

{{.locale.Tr "repo.settings.webhook_deletion_desc"}}

-
-
- - {{.locale.Tr "modal.no"}} -
-
- - {{.locale.Tr "modal.yes"}} -
-
+ {{template "base/delete_modal_actions" .}} diff --git a/templates/repo/unicode_escape_prompt.tmpl b/templates/repo/unicode_escape_prompt.tmpl index d55bd0150a..12eff6aebe 100644 --- a/templates/repo/unicode_escape_prompt.tmpl +++ b/templates/repo/unicode_escape_prompt.tmpl @@ -1,7 +1,7 @@ {{if .EscapeStatus}} {{if .EscapeStatus.HasInvisible}}
- {{svg "octicon-x" 16 "close inside"}} +
{{$.root.locale.Tr "repo.invisible_runes_header"}}
@@ -12,7 +12,7 @@
{{else if .EscapeStatus.HasAmbiguous}}
- {{svg "octicon-x" 16 "close inside"}} +
{{$.root.locale.Tr "repo.ambiguous_runes_header"}}
diff --git a/templates/shared/actions/runner_list.tmpl b/templates/shared/actions/runner_list.tmpl index eabddbb30c..30c52c01b4 100644 --- a/templates/shared/actions/runner_list.tmpl +++ b/templates/shared/actions/runner_list.tmpl @@ -20,9 +20,9 @@
-
+
+
diff --git a/templates/shared/secrets/add_list.tmpl b/templates/shared/secrets/add_list.tmpl index 9105b7ad9b..4aa5f0ccd5 100644 --- a/templates/shared/secrets/add_list.tmpl +++ b/templates/shared/secrets/add_list.tmpl @@ -1,7 +1,7 @@

{{.locale.Tr "secrets.secrets"}}
-
{{.locale.Tr "secrets.creation"}}
+

diff --git a/templates/user/auth/grant.tmpl b/templates/user/auth/grant.tmpl index c906db3e0a..060b675273 100644 --- a/templates/user/auth/grant.tmpl +++ b/templates/user/auth/grant.tmpl @@ -23,7 +23,7 @@ - + Cancel
diff --git a/templates/user/auth/webauthn_error.tmpl b/templates/user/auth/webauthn_error.tmpl index 447d289a28..b6467de1aa 100644 --- a/templates/user/auth/webauthn_error.tmpl +++ b/templates/user/auth/webauthn_error.tmpl @@ -17,6 +17,6 @@
-
{{.locale.Tr "cancel"}}
+
diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl index 9a57bd5722..53f7d021e0 100644 --- a/templates/user/settings/account.tmpl +++ b/templates/user/settings/account.tmpl @@ -151,9 +151,9 @@
-
+
+ {{.locale.Tr "auth.forgot_password"}}
diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index b0cd37d44c..18132c4a75 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -276,15 +276,16 @@

{{.locale.Tr "settings.access_token_deletion_desc"}}

-
-
- + +
{{/* TODO: Convert to base/delete_modal_actions.tmpl */}} +
-
- + +
+
diff --git a/templates/user/settings/keys_gpg.tmpl b/templates/user/settings/keys_gpg.tmpl index c80890940a..93ca12a088 100644 --- a/templates/user/settings/keys_gpg.tmpl +++ b/templates/user/settings/keys_gpg.tmpl @@ -1,7 +1,7 @@

{{.locale.Tr "settings.manage_gpg_keys"}}
-
{{.locale.Tr "settings.add_key"}}
+

diff --git a/templates/user/settings/keys_principal.tmpl b/templates/user/settings/keys_principal.tmpl index cc1152b739..8012b874cd 100644 --- a/templates/user/settings/keys_principal.tmpl +++ b/templates/user/settings/keys_principal.tmpl @@ -3,9 +3,9 @@ {{.locale.Tr "settings.manage_ssh_principals"}}
{{if not .DisableSSH}} -
{{.locale.Tr "settings.add_new_principal"}}
+ {{else}} -
{{.locale.Tr "settings.ssh_disabled"}}
+ {{end}}
diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl index 891959d351..1ff4dab34e 100644 --- a/templates/user/settings/keys_ssh.tmpl +++ b/templates/user/settings/keys_ssh.tmpl @@ -2,11 +2,11 @@ {{.locale.Tr "settings.manage_ssh_keys"}}
{{if not .DisableSSH}} -
+
+ {{else}} -
{{.locale.Tr "settings.ssh_disabled"}}
+ {{end}}
diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl index 902b3fb2f3..2e107ca7fa 100644 --- a/templates/user/settings/repos.tmpl +++ b/templates/user/settings/repos.tmpl @@ -50,16 +50,7 @@ {{$.CsrfTokenHtml}} -
-
- - {{$.locale.Tr "modal.no"}} -
- -
+ {{template "base/delete_modal_actions" .}}
{{end}} @@ -77,16 +68,7 @@ {{$.CsrfTokenHtml}} -
-
- - {{$.locale.Tr "modal.no"}} -
- -
+ {{template "base/delete_modal_actions" .}} {{end}} diff --git a/templates/user/settings/security/twofa.tmpl b/templates/user/settings/security/twofa.tmpl index a4da947628..1a0a8a6432 100644 --- a/templates/user/settings/security/twofa.tmpl +++ b/templates/user/settings/security/twofa.tmpl @@ -13,7 +13,7 @@
{{.CsrfTokenHtml}}

{{.locale.Tr "settings.twofa_disable_note"}}

-
{{$.locale.Tr "settings.twofa_disable"}}
+
{{else}}

{{.locale.Tr "settings.twofa_not_enrolled"}}

diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js index d023e0bc36..be5aa876a5 100644 --- a/web_src/js/features/admin/common.js +++ b/web_src/js/features/admin/common.js @@ -198,7 +198,8 @@ export function initAdminCommon() { break; } }); - $('#delete-selection').on('click', function () { + $('#delete-selection').on('click', function (e) { + e.preventDefault(); const $this = $(this); $this.addClass('loading disabled'); const ids = []; diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 4fa6942467..0f36ce2bf8 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -202,7 +202,8 @@ export function initGlobalDropzone() { } export function initGlobalLinkActions() { - function showDeletePopup() { + function showDeletePopup(e) { + e.preventDefault(); const $this = $(this); const dataArray = $this.data(); let filter = ''; @@ -243,10 +244,10 @@ export function initGlobalLinkActions() { }); } }).modal('show'); - return false; } - function showAddAllPopup() { + function showAddAllPopup(e) { + e.preventDefault(); const $this = $(this); let filter = ''; if ($this.attr('id')) { @@ -272,7 +273,6 @@ export function initGlobalLinkActions() { }); } }).modal('show'); - return false; } function linkAction(e) { @@ -318,13 +318,21 @@ export function initGlobalLinkActions() { } export function initGlobalButtons() { - $('.show-panel.button').on('click', function () { + // There are many "cancel button" elements in modal dialogs, Fomantic UI expects they are button-like elements but never submit a form. + // However, Gitea misuses the modal dialog and put the cancel buttons inside forms, so we must prevent the form submission. + // There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content") + $(document).on('click', 'form .ui.cancel.button', (e) => { + e.preventDefault(); + }); + + $('.show-panel.button').on('click', function (e) { + e.preventDefault(); showElem($(this).data('panel')); }); - $('.hide-panel.button').on('click', function (event) { + $('.hide-panel.button').on('click', function (e) { // a `.hide-panel.button` can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"` - event.preventDefault(); + e.preventDefault(); let sel = $(this).attr('data-panel'); if (sel) { hideElem($(sel)); @@ -339,7 +347,8 @@ export function initGlobalButtons() { alert('Nothing to hide'); }); - $('.show-modal').on('click', function () { + $('.show-modal').on('click', function (e) { + e.preventDefault(); const modalDiv = $($(this).attr('data-modal')); for (const attrib of this.attributes) { if (!attrib.name.startsWith('data-modal-')) { @@ -360,7 +369,8 @@ export function initGlobalButtons() { } }); - $('.delete-post.button').on('click', function () { + $('.delete-post.button').on('click', function (e) { + e.preventDefault(); const $this = $(this); $.post($this.attr('data-request-url'), { _csrf: csrfToken diff --git a/web_src/js/features/common-issue.js b/web_src/js/features/common-issue.js index 0965caef15..ebc851d676 100644 --- a/web_src/js/features/common-issue.js +++ b/web_src/js/features/common-issue.js @@ -34,6 +34,7 @@ export function initCommonIssue() { }); $('.issue-action').on('click', async function (e) { + e.preventDefault(); let action = this.getAttribute('data-action'); let elementId = this.getAttribute('data-element-id'); const url = this.getAttribute('data-url'); diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 41c9dd118f..a8a27c2572 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -230,7 +230,8 @@ export function initRepoIssueStatusButton() { const value = easyMDE?.value() || $(this).val(); $statusButton.text($statusButton.data(value.length === 0 ? 'status' : 'status-and-comment')); }); - $statusButton.on('click', () => { + $statusButton.on('click', (e) => { + e.preventDefault(); $('#status').val($statusButton.data('status-val')); $('#comment-form').trigger('submit'); }); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 70542ad883..5346a0d274 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -412,7 +412,8 @@ async function onEditContent(event) { $saveButton.trigger('click'); }); - $editContentZone.find('.cancel.button').on('click', () => { + $editContentZone.find('.cancel.button').on('click', (e) => { + e.preventDefault(); showElem($renderContent); hideElem($editContentZone); if (dz) { diff --git a/web_src/svg/fontawesome-save.svg b/web_src/svg/fontawesome-save.svg new file mode 100644 index 0000000000..763d26abb1 --- /dev/null +++ b/web_src/svg/fontawesome-save.svg @@ -0,0 +1 @@ + \ No newline at end of file From b942838bd486f5d3919a14a128efe22fc55c6112 Mon Sep 17 00:00:00 2001 From: LeenHawk <127599173+LeenHawk@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:38:20 +0800 Subject: [PATCH 10/94] Update localization.zh-cn.md (#23448) As title. --- docs/content/doc/features/localization.zh-cn.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/content/doc/features/localization.zh-cn.md b/docs/content/doc/features/localization.zh-cn.md index 6a6f637326..44f9537f31 100644 --- a/docs/content/doc/features/localization.zh-cn.md +++ b/docs/content/doc/features/localization.zh-cn.md @@ -14,5 +14,17 @@ menu: --- # 本地化 +Gitea的本地化是通过我们的[Crowdin项目](https://crowdin.com/project/gitea)进行的。 -## TBD +对于对**英语翻译**的更改,可以发出pull-request,来更改[英语语言环境](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)中合适的关键字。 + +有关对**非英语**翻译的更改,请参阅上面的 Crowdin 项目。 + +## 支持的语言 +上述 Crowdin 项目中列出的任何语言一旦翻译了 25% 或更多都将得到支持。 + +翻译被接受后,它将在下一次 Crowdin 同步后反映在主存储库中,这通常是在任何 PR 合并之后。 + +在撰写本文时,这意味着更改后的翻译可能要到 Gitea 的下一个版本才会出现。 + +如果使用开发版本,则在同步更改内容后,它应该会在更新后立即显示。 From e82f1b15c7120ad13fd3b67cf7e2c6cb9915c22d Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 14 Mar 2023 12:09:06 +0800 Subject: [PATCH 11/94] Refactor dashboard repo list to Vue SFC (#23405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to #23394 The dashboard repo list mixes jQuery/Fomantic UI/Vue together, it's very diffcult to maintain and causes unfixable a11y problems. This PR uses two steps to refactor the repo list: 1. move `data-` attributes to JS object and use Vue data as much as possible https://github.com/go-gitea/gitea/pull/23405/commits/d3adc0dcacf7de87b9819277e6598ac3993bbfa3 2. move the code into a Vue SFC https://github.com/go-gitea/gitea/pull/23405/commits/7ebe55df6e67adfd272a4bf0a96ad6688edf661f Total: +516 −585 Screenshots:
![image](https://user-images.githubusercontent.com/2114189/224271457-a23e05be-d7d3-4247-a803-f0ee30c36f44.png) ![image](https://user-images.githubusercontent.com/2114189/224271504-76fbd3da-4d7a-4725-b0d1-fbff83caac63.png) ![image](https://user-images.githubusercontent.com/2114189/224271845-f007cadf-6c49-46bd-a65c-a3fc75bdba3b.png)
--------- Co-authored-by: John Olheiser --- templates/user/dashboard/repolist.tmpl | 233 +++------- web_src/js/components/DashboardRepoList.js | 345 -------------- web_src/js/components/DashboardRepoList.vue | 432 ++++++++++++++++++ .../js/components/RepoActivityTopAuthors.vue | 9 +- .../js/components/RepoBranchTagDropdown.js | 3 +- web_src/js/components/VueComponentLoader.js | 49 -- web_src/js/index.js | 4 +- web_src/js/svg.js | 26 +- 8 files changed, 516 insertions(+), 585 deletions(-) delete mode 100644 web_src/js/components/DashboardRepoList.js create mode 100644 web_src/js/components/DashboardRepoList.vue delete mode 100644 web_src/js/components/VueComponentLoader.js diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl index 97234176bd..0a8f427f9d 100644 --- a/templates/user/dashboard/repolist.tmpl +++ b/templates/user/dashboard/repolist.tmpl @@ -1,181 +1,54 @@ -
- -
+ + +
diff --git a/web_src/js/components/DashboardRepoList.js b/web_src/js/components/DashboardRepoList.js deleted file mode 100644 index 2328cc83a9..0000000000 --- a/web_src/js/components/DashboardRepoList.js +++ /dev/null @@ -1,345 +0,0 @@ -import {createApp, nextTick} from 'vue'; -import $ from 'jquery'; -import {initVueSvg, vueDelimiters} from './VueComponentLoader.js'; -import {initTooltip} from '../modules/tippy.js'; - -const {appSubUrl, assetUrlPrefix, pageData} = window.config; - -function initVueComponents(app) { - app.component('repo-search', { - delimiters: vueDelimiters, - props: { - searchLimit: { - type: Number, - default: 10 - }, - subUrl: { - type: String, - required: true - }, - uid: { - type: Number, - default: 0 - }, - teamId: { - type: Number, - required: false, - default: 0 - }, - organizations: { - type: Array, - default: () => [], - }, - isOrganization: { - type: Boolean, - default: true - }, - canCreateOrganization: { - type: Boolean, - default: false - }, - organizationsTotalCount: { - type: Number, - default: 0 - }, - moreReposLink: { - type: String, - default: '' - } - }, - - data() { - const params = new URLSearchParams(window.location.search); - - let tab = params.get('repo-search-tab'); - if (!tab) { - tab = 'repos'; - } - - let reposFilter = params.get('repo-search-filter'); - if (!reposFilter) { - reposFilter = 'all'; - } - - let privateFilter = params.get('repo-search-private'); - if (!privateFilter) { - privateFilter = 'both'; - } - - let archivedFilter = params.get('repo-search-archived'); - if (!archivedFilter) { - archivedFilter = 'unarchived'; - } - - let searchQuery = params.get('repo-search-query'); - if (!searchQuery) { - searchQuery = ''; - } - - let page = 1; - try { - page = parseInt(params.get('repo-search-page')); - } catch { - // noop - } - if (!page) { - page = 1; - } - - return { - hasMounted: false, // accessing $refs in computed() need to wait for mounted - tab, - repos: [], - reposTotalCount: 0, - reposFilter, - archivedFilter, - privateFilter, - page, - finalPage: 1, - searchQuery, - isLoading: false, - staticPrefix: assetUrlPrefix, - counts: {}, - repoTypes: { - all: { - searchMode: '', - }, - forks: { - searchMode: 'fork', - }, - mirrors: { - searchMode: 'mirror', - }, - sources: { - searchMode: 'source', - }, - collaborative: { - searchMode: 'collaborative', - }, - } - }; - }, - - computed: { - // used in `repolist.tmpl` - showMoreReposLink() { - return this.repos.length > 0 && this.repos.length < this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`]; - }, - searchURL() { - return `${this.subUrl}/repo/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=${this.searchQuery - }&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode - }${this.reposFilter !== 'all' ? '&exclusive=1' : '' - }${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : '' - }${this.privateFilter === 'private' ? '&is_private=true' : ''}${this.privateFilter === 'public' ? '&is_private=false' : '' - }`; - }, - repoTypeCount() { - return this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`]; - }, - checkboxArchivedFilterTitle() { - return this.hasMounted && this.$refs.checkboxArchivedFilter?.getAttribute(`data-title-${this.archivedFilter}`); - }, - checkboxArchivedFilterProps() { - return {checked: this.archivedFilter === 'archived', indeterminate: this.archivedFilter === 'both'}; - }, - checkboxPrivateFilterTitle() { - return this.hasMounted && this.$refs.checkboxPrivateFilter?.getAttribute(`data-title-${this.privateFilter}`); - }, - checkboxPrivateFilterProps() { - return {checked: this.privateFilter === 'private', indeterminate: this.privateFilter === 'both'}; - }, - }, - - mounted() { - const el = document.getElementById('dashboard-repo-list'); - this.changeReposFilter(this.reposFilter); - for (const elTooltip of el.querySelectorAll('.tooltip')) { - initTooltip(elTooltip); - } - $(el).find('.dropdown').dropdown(); - nextTick(() => { - this.$refs.search.focus(); - }); - - this.hasMounted = true; - }, - - methods: { - changeTab(t) { - this.tab = t; - this.updateHistory(); - }, - - changeReposFilter(filter) { - this.reposFilter = filter; - this.repos = []; - this.page = 1; - this.counts[`${filter}:${this.archivedFilter}:${this.privateFilter}`] = 0; - this.searchRepos(); - }, - - updateHistory() { - const params = new URLSearchParams(window.location.search); - - if (this.tab === 'repos') { - params.delete('repo-search-tab'); - } else { - params.set('repo-search-tab', this.tab); - } - - if (this.reposFilter === 'all') { - params.delete('repo-search-filter'); - } else { - params.set('repo-search-filter', this.reposFilter); - } - - if (this.privateFilter === 'both') { - params.delete('repo-search-private'); - } else { - params.set('repo-search-private', this.privateFilter); - } - - if (this.archivedFilter === 'unarchived') { - params.delete('repo-search-archived'); - } else { - params.set('repo-search-archived', this.archivedFilter); - } - - if (this.searchQuery === '') { - params.delete('repo-search-query'); - } else { - params.set('repo-search-query', this.searchQuery); - } - - if (this.page === 1) { - params.delete('repo-search-page'); - } else { - params.set('repo-search-page', `${this.page}`); - } - - const queryString = params.toString(); - if (queryString) { - window.history.replaceState({}, '', `?${queryString}`); - } else { - window.history.replaceState({}, '', window.location.pathname); - } - }, - - toggleArchivedFilter() { - if (this.archivedFilter === 'unarchived') { - this.archivedFilter = 'archived'; - } else if (this.archivedFilter === 'archived') { - this.archivedFilter = 'both'; - } else { // including both - this.archivedFilter = 'unarchived'; - } - this.page = 1; - this.repos = []; - this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; - this.searchRepos(); - }, - - togglePrivateFilter() { - if (this.privateFilter === 'both') { - this.privateFilter = 'public'; - } else if (this.privateFilter === 'public') { - this.privateFilter = 'private'; - } else { // including private - this.privateFilter = 'both'; - } - this.page = 1; - this.repos = []; - this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; - this.searchRepos(); - }, - - - changePage(page) { - this.page = page; - if (this.page > this.finalPage) { - this.page = this.finalPage; - } - if (this.page < 1) { - this.page = 1; - } - this.repos = []; - this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; - this.searchRepos(); - }, - - async searchRepos() { - this.isLoading = true; - - const searchedMode = this.repoTypes[this.reposFilter].searchMode; - const searchedURL = this.searchURL; - const searchedQuery = this.searchQuery; - - let response, json; - try { - if (!this.reposTotalCount) { - const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`; - response = await fetch(totalCountSearchURL); - this.reposTotalCount = response.headers.get('X-Total-Count'); - } - - response = await fetch(searchedURL); - json = await response.json(); - } catch { - if (searchedURL === this.searchURL) { - this.isLoading = false; - } - return; - } - - if (searchedURL === this.searchURL) { - this.repos = json.data; - const count = response.headers.get('X-Total-Count'); - if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') { - this.reposTotalCount = count; - } - this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = count; - this.finalPage = Math.ceil(count / this.searchLimit); - this.updateHistory(); - this.isLoading = false; - } - }, - - repoIcon(repo) { - if (repo.fork) { - return 'octicon-repo-forked'; - } else if (repo.mirror) { - return 'octicon-mirror'; - } else if (repo.template) { - return `octicon-repo-template`; - } else if (repo.private) { - return 'octicon-lock'; - } else if (repo.internal) { - return 'octicon-repo'; - } - return 'octicon-repo'; - } - }, - - template: document.getElementById('dashboard-repo-list-template'), - }); -} - -export function initDashboardRepoList() { - const el = document.getElementById('dashboard-repo-list'); - const dashboardRepoListData = pageData.dashboardRepoList || null; - if (!el || !dashboardRepoListData) return; - - const app = createApp({ - delimiters: vueDelimiters, - data() { - return { - searchLimit: dashboardRepoListData.searchLimit || 0, - subUrl: appSubUrl, - uid: dashboardRepoListData.uid || 0, - }; - }, - }); - initVueSvg(app); - initVueComponents(app); - app.mount(el); -} diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue new file mode 100644 index 0000000000..e295910fd0 --- /dev/null +++ b/web_src/js/components/DashboardRepoList.vue @@ -0,0 +1,432 @@ + + + diff --git a/web_src/js/components/RepoActivityTopAuthors.vue b/web_src/js/components/RepoActivityTopAuthors.vue index 37b6df9187..294ee6f7bc 100644 --- a/web_src/js/components/RepoActivityTopAuthors.vue +++ b/web_src/js/components/RepoActivityTopAuthors.vue @@ -51,7 +51,7 @@ diff --git a/web_src/js/components/RepoBranchTagDropdown.js b/web_src/js/components/RepoBranchTagDropdown.js index e1bf35c129..a8945b82d1 100644 --- a/web_src/js/components/RepoBranchTagDropdown.js +++ b/web_src/js/components/RepoBranchTagDropdown.js @@ -1,6 +1,5 @@ import {createApp, nextTick} from 'vue'; import $ from 'jquery'; -import {vueDelimiters} from './VueComponentLoader.js'; export function initRepoBranchTagDropdown(selector) { $(selector).each(function (dropdownIndex, elRoot) { @@ -39,7 +38,7 @@ export function initRepoBranchTagDropdown(selector) { } const view = createApp({ - delimiters: vueDelimiters, + delimiters: ['${', '}'], data() { return data; }, diff --git a/web_src/js/components/VueComponentLoader.js b/web_src/js/components/VueComponentLoader.js deleted file mode 100644 index 33ebf95eff..0000000000 --- a/web_src/js/components/VueComponentLoader.js +++ /dev/null @@ -1,49 +0,0 @@ -import {createApp} from 'vue'; -import {svgs} from '../svg.js'; - -export const vueDelimiters = ['${', '}']; - -let vueEnvInited = false; -export function initVueEnv() { - if (vueEnvInited) return; - vueEnvInited = true; - - // As far as I could tell, this is no longer possible. - // But there seem not to be a guide what to do instead. - // const isProd = window.config.runModeIsProd; - // Vue.config.devtools = !isProd; -} - -let vueSvgInited = false; -export function initVueSvg(app) { - if (vueSvgInited) return; - vueSvgInited = true; - - // register svg icon vue components, e.g. - for (const [name, htmlString] of Object.entries(svgs)) { - const template = htmlString - .replace(/height="[0-9]+"/, 'v-bind:height="size"') - .replace(/width="[0-9]+"/, 'v-bind:width="size"'); - - app.component(name, { - props: { - size: { - type: String, - default: '16', - }, - }, - template, - }); - } -} - -export function initVueApp(el, opts = {}) { - if (typeof el === 'string') { - el = document.querySelector(el); - } - if (!el) return null; - - return createApp( - {delimiters: vueDelimiters, ...opts} - ).mount(el); -} diff --git a/web_src/js/index.js b/web_src/js/index.js index 6b4f4ef3eb..480661118b 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -2,9 +2,8 @@ import './bootstrap.js'; import $ from 'jquery'; -import {initVueEnv} from './components/VueComponentLoader.js'; import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue'; -import {initDashboardRepoList} from './components/DashboardRepoList.js'; +import {initDashboardRepoList} from './components/DashboardRepoList.vue'; import {attachTribute} from './features/tribute.js'; import {initGlobalCopyToClipboardListener} from './features/clipboard.js'; @@ -100,7 +99,6 @@ $.fn.tab.settings.silent = true; // Disable the behavior of fomantic to toggle the checkbox when you press enter on a checkbox element. $.fn.checkbox.settings.enableEnterKey = false; -initVueEnv(); $(document).ready(() => { initGlobalCommon(); diff --git a/web_src/js/svg.js b/web_src/js/svg.js index 6476f16bfb..9eabca3fd3 100644 --- a/web_src/js/svg.js +++ b/web_src/js/svg.js @@ -31,8 +31,17 @@ import octiconSkip from '../../public/img/svg/octicon-skip.svg'; import octiconMeter from '../../public/img/svg/octicon-meter.svg'; import octiconBlocked from '../../public/img/svg/octicon-blocked.svg'; import octiconSync from '../../public/img/svg/octicon-sync.svg'; +import octiconFilter from '../../public/img/svg/octicon-filter.svg'; +import octiconPlus from '../../public/img/svg/octicon-plus.svg'; +import octiconSearch from '../../public/img/svg/octicon-search.svg'; +import octiconArchive from '../../public/img/svg/octicon-archive.svg'; +import octiconStar from '../../public/img/svg/octicon-star.svg'; +import giteaDoubleChevronLeft from '../../public/img/svg/gitea-double-chevron-left.svg'; +import giteaDoubleChevronRight from '../../public/img/svg/gitea-double-chevron-right.svg'; +import octiconChevronLeft from '../../public/img/svg/octicon-chevron-left.svg'; +import octiconOrganization from '../../public/img/svg/octicon-organization.svg'; -export const svgs = { +const svgs = { 'octicon-blocked': octiconBlocked, 'octicon-check-circle-fill': octiconCheckCircleFill, 'octicon-chevron-down': octiconChevronDown, @@ -66,14 +75,25 @@ export const svgs = { 'octicon-triangle-down': octiconTriangleDown, 'octicon-x': octiconX, 'octicon-x-circle-fill': octiconXCircleFill, + 'octicon-filter': octiconFilter, + 'octicon-plus': octiconPlus, + 'octicon-search': octiconSearch, + 'octicon-archive': octiconArchive, + 'octicon-star': octiconStar, + 'gitea-double-chevron-left': giteaDoubleChevronLeft, + 'gitea-double-chevron-right': giteaDoubleChevronRight, + 'octicon-chevron-left': octiconChevronLeft, + 'octicon-organization': octiconOrganization, }; +// TODO: use a more general approach to access SVG icons. At the moment, developers must check, pick and fill the names manually, most of the SVG icons in assets couldn't be used directly. + const parser = new DOMParser(); const serializer = new XMLSerializer(); -// retrieve a HTML string for given SVG icon name, size and additional classes +// retrieve an HTML string for given SVG icon name, size and additional classes export function svg(name, size = 16, className = '') { - if (!(name in svgs)) return ''; + if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`); if (size === 16 && !className) return svgs[name]; const document = parser.parseFromString(svgs[name], 'image/svg+xml'); From 0efa9d564941e6539df98ed4ddd906a05c1fa7e7 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 14 Mar 2023 00:10:01 -0400 Subject: [PATCH 12/94] fix markdown lint issue (#23457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI is failing with the following: ``` docs/content/doc/features/localization.zh-cn.md:16 MD022/blanks-around-headings/blanks-around-headers Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "# 本地化"] docs/content/doc/features/localization.zh-cn.md:23 MD022/blanks-around-headings/blanks-around-headers Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## 支持的语言"] ``` This fixes that error --- docs/content/doc/features/localization.zh-cn.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/doc/features/localization.zh-cn.md b/docs/content/doc/features/localization.zh-cn.md index 44f9537f31..dd2dc1fa90 100644 --- a/docs/content/doc/features/localization.zh-cn.md +++ b/docs/content/doc/features/localization.zh-cn.md @@ -14,6 +14,7 @@ menu: --- # 本地化 + Gitea的本地化是通过我们的[Crowdin项目](https://crowdin.com/project/gitea)进行的。 对于对**英语翻译**的更改,可以发出pull-request,来更改[英语语言环境](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)中合适的关键字。 @@ -21,6 +22,7 @@ Gitea的本地化是通过我们的[Crowdin项目](https://crowdin.com/project/g 有关对**非英语**翻译的更改,请参阅上面的 Crowdin 项目。 ## 支持的语言 + 上述 Crowdin 项目中列出的任何语言一旦翻译了 25% 或更多都将得到支持。 翻译被接受后,它将在下一次 Crowdin 同步后反映在主存储库中,这通常是在任何 PR 合并之后。 From 6ff5400af91aefb02cbc7dd59f6be23cc2bf7865 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 14 Mar 2023 13:11:38 +0800 Subject: [PATCH 13/94] Make branches list page operations remember current page (#23420) Close #23411 Always pass "page" query parameter to backend, and make backend respect it. The `ctx.FormInt("limit")` is never used, so removed. --------- Co-authored-by: Jason Song Co-authored-by: Lunny Xiao --- modules/context/pagination.go | 7 ++++--- routers/web/repo/branch.go | 17 +++++++---------- templates/repo/branch/list.tmpl | 8 ++++---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/modules/context/pagination.go b/modules/context/pagination.go index 3effd88f10..5a88c92053 100644 --- a/modules/context/pagination.go +++ b/modules/context/pagination.go @@ -18,10 +18,11 @@ type Pagination struct { urlParams []string } -// NewPagination creates a new instance of the Pagination struct -func NewPagination(total, page, issueNum, numPages int) *Pagination { +// NewPagination creates a new instance of the Pagination struct. +// "pagingNum" is "page size" or "limit", "current" is "page" +func NewPagination(total, pagingNum, current, numPages int) *Pagination { p := &Pagination{} - p.Paginater = paginator.New(total, page, issueNum, numPages) + p.Paginater = paginator.New(total, pagingNum, current, numPages) return p } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index d23367e047..9f26634311 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strings" "code.gitea.io/gitea/models" @@ -65,21 +66,17 @@ func Branches(ctx *context.Context) { if page <= 1 { page = 1 } + pageSize := setting.Git.BranchesRangeSize - limit := ctx.FormInt("limit") - if limit <= 0 || limit > setting.Git.BranchesRangeSize { - limit = setting.Git.BranchesRangeSize - } - - skip := (page - 1) * limit - log.Debug("Branches: skip: %d limit: %d", skip, limit) - defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, limit) + skip := (page - 1) * pageSize + log.Debug("Branches: skip: %d limit: %d", skip, pageSize) + defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize) if ctx.Written() { return } ctx.Data["Branches"] = branches ctx.Data["DefaultBranchBranch"] = defaultBranchBranch - pager := context.NewPagination(branchesCount, setting.Git.BranchesRangeSize, page, 5) + pager := context.NewPagination(branchesCount, pageSize, page, 5) pager.SetDefaultParams(ctx) ctx.Data["Page"] = pager @@ -165,7 +162,7 @@ func RestoreBranchPost(ctx *context.Context) { func redirect(ctx *context.Context) { ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Repo.RepoLink + "/branches", + "redirect": ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page")), }) } diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 7e8bf348a4..898be4d6bb 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -81,9 +81,9 @@ {{if not .LatestPullRequest}} {{if .IsIncluded}} - + {{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}} - + {{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} @@ -123,13 +123,13 @@ {{end}} {{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}} {{if .IsDeleted}} - {{else}} - {{end}} From aac07d010f261c00fb3bd9644c71dc108c668c11 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Tue, 14 Mar 2023 16:27:03 +0900 Subject: [PATCH 14/94] Add workflow error notification in ui (#23404) ![image](https://user-images.githubusercontent.com/18380374/224237847-07a30029-32d4-4af7-a36e-e55f0ed899aa.png) ![image](https://user-images.githubusercontent.com/18380374/224239309-a96120e1-5eec-41c0-89aa-9cf63d1df30c.png) --------- Co-authored-by: techknowlogick Co-authored-by: techknowlogick Co-authored-by: Lunny Xiao --- modules/actions/workflows.go | 40 ++++++++++++++++++++--------- options/locale/locale_en-US.ini | 2 ++ routers/web/repo/actions/actions.go | 26 ++++++++++++++++--- templates/repo/actions/list.tmpl | 12 ++++++++- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 7f0e6e4564..98fc831c31 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -44,6 +44,32 @@ func ListWorkflows(commit *git.Commit) (git.Entries, error) { return ret, nil } +func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) { + f, err := entry.Blob().DataAsync() + if err != nil { + return nil, err + } + content, err := io.ReadAll(f) + _ = f.Close() + if err != nil { + return nil, err + } + return content, nil +} + +func GetEventsFromContent(content []byte) ([]*jobparser.Event, error) { + workflow, err := model.ReadWorkflow(bytes.NewReader(content)) + if err != nil { + return nil, err + } + events, err := jobparser.ParseRawOn(&workflow.RawOn) + if err != nil { + return nil, err + } + + return events, nil +} + func DetectWorkflows(commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader) (map[string][]byte, error) { entries, err := ListWorkflows(commit) if err != nil { @@ -52,21 +78,11 @@ func DetectWorkflows(commit *git.Commit, triggedEvent webhook_module.HookEventTy workflows := make(map[string][]byte, len(entries)) for _, entry := range entries { - f, err := entry.Blob().DataAsync() + content, err := GetContentFromEntry(entry) if err != nil { return nil, err } - content, err := io.ReadAll(f) - _ = f.Close() - if err != nil { - return nil, err - } - workflow, err := model.ReadWorkflow(bytes.NewReader(content)) - if err != nil { - log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) - continue - } - events, err := jobparser.ParseRawOn(&workflow.RawOn) + events, err := GetEventsFromContent(content) if err != nil { log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) continue diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index e793c3ef03..afcf9ade04 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3360,5 +3360,7 @@ runs.open_tab = %d Open runs.closed_tab = %d Closed runs.commit = Commit runs.pushed_by = Pushed by +runs.valid_workflow_helper = Workflow config file is valid. +runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s need_approval_desc = Need approval to run workflows for fork pull request. diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 0e7a95ed07..dd2dc55bd5 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -23,6 +23,12 @@ const ( tplViewActions base.TplName = "repo/actions/view" ) +type Workflow struct { + Entry git.TreeEntry + IsInvalid bool + ErrMsg string +} + // MustEnableActions check if actions are enabled in settings func MustEnableActions(ctx *context.Context) { if !setting.Actions.Enabled { @@ -47,7 +53,7 @@ func List(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.actions") ctx.Data["PageIsActions"] = true - var workflows git.Entries + var workflows []Workflow if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return @@ -62,13 +68,27 @@ func List(ctx *context.Context) { ctx.Error(http.StatusInternalServerError, err.Error()) return } - workflows, err = actions.ListWorkflows(commit) + entries, err := actions.ListWorkflows(commit) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return } + workflows = make([]Workflow, 0, len(entries)) + for _, entry := range entries { + workflow := Workflow{Entry: *entry} + content, err := actions.GetContentFromEntry(entry) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + _, err = actions.GetEventsFromContent(content) + if err != nil { + workflow.IsInvalid = true + workflow.ErrMsg = err.Error() + } + workflows = append(workflows, workflow) + } } - ctx.Data["workflows"] = workflows ctx.Data["RepoLink"] = ctx.Repo.Repository.Link() diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index c5abff5251..5e8313fa5e 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -9,7 +9,17 @@ {{.locale.Tr "actions.runs.all_workflows"}}
{{range .workflows}} - {{.Name}} + {{.Entry.Name}} + {{if .IsInvalid}} + + + + {{else}} + + + + {{end}} + {{end}} From 03591f0f95823a0b1dcca969d2a3ed505c7e6d73 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 14 Mar 2023 03:45:21 -0400 Subject: [PATCH 15/94] add user rename endpoint to admin api (#22789) this is a simple endpoint that adds the ability to rename users to the admin API. Note: this is not in a mergeable state. It would be better if this was handled by a PATCH/POST to the /api/v1/admin/users/{username} endpoint and the username is modified. --------- Co-authored-by: Jason Song --- models/issues/pull.go | 4 +- models/user/user.go | 4 +- modules/structs/user.go | 9 +++++ routers/api/v1/admin/user.go | 58 +++++++++++++++++++++++++++++ routers/api/v1/api.go | 1 + routers/api/v1/swagger/options.go | 3 ++ routers/web/org/setting.go | 2 +- routers/web/user/setting/profile.go | 52 ++++++++------------------ services/agit/agit.go | 4 +- services/user/rename.go | 41 ++++++++++++++++++++ services/user/user.go | 16 ++++++++ templates/swagger/v1_json.tmpl | 56 ++++++++++++++++++++++++++++ 12 files changed, 206 insertions(+), 44 deletions(-) create mode 100644 services/user/rename.go diff --git a/models/issues/pull.go b/models/issues/pull.go index 6a1dc31556..a15ebec0b5 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -660,10 +660,10 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest, // GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request // By poster id. -func GetAllUnmergedAgitPullRequestByPoster(uid int64) ([]*PullRequest, error) { +func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) { pulls := make([]*PullRequest, 0, 10) - err := db.GetEngine(db.DefaultContext). + err := db.GetEngine(ctx). Where("has_merged=? AND flow = ? AND issue.is_closed=? AND issue.poster_id=?", false, PullRequestFlowAGit, false, uid). Join("INNER", "issue", "issue.id=pull_request.issue_id"). diff --git a/models/user/user.go b/models/user/user.go index 454779b9ea..82c2d3b6cd 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -742,13 +742,13 @@ func VerifyUserActiveCode(code string) (user *User) { } // ChangeUserName changes all corresponding setting from old user name to new one. -func ChangeUserName(u *User, newUserName string) (err error) { +func ChangeUserName(ctx context.Context, u *User, newUserName string) (err error) { oldUserName := u.Name if err = IsUsableUsername(newUserName); err != nil { return err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/modules/structs/user.go b/modules/structs/user.go index c5e96f3356..f68b92ac06 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -93,3 +93,12 @@ type UserSettingsOptions struct { HideEmail *bool `json:"hide_email"` HideActivity *bool `json:"hide_activity"` } + +// RenameUserOption options when renaming a user +type RenameUserOption struct { + // New username for this user. This name cannot be in use yet by any other user. + // + // required: true + // unique: true + NewName string `json:"new_username" binding:"Required"` +} diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 4192d8654d..369d13943a 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -461,3 +461,61 @@ func GetAllUsers(ctx *context.APIContext) { ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, &results) } + +// RenameUser api for renaming a user +func RenameUser(ctx *context.APIContext) { + // swagger:operation POST /admin/users/{username}/rename admin adminRenameUser + // --- + // summary: Rename a user + // produces: + // - application/json + // parameters: + // - name: username + // in: path + // description: existing username of user + // type: string + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/RenameUserOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + // "422": + // "$ref": "#/responses/validationError" + + if ctx.ContextUser.IsOrganization() { + ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name)) + return + } + + newName := web.GetForm(ctx).(*api.RenameUserOption).NewName + + if strings.EqualFold(newName, ctx.ContextUser.Name) { + // Noop as username is not changed + ctx.Status(http.StatusNoContent) + return + } + + // Check if user name has been changed + if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil { + switch { + case user_model.IsErrUserAlreadyExist(err): + ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken")) + case db.IsErrNameReserved(err): + ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_reserved", newName)) + case db.IsErrNamePatternNotAllowed(err): + ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_pattern_not_allowed", newName)) + case db.IsErrNameCharsNotAllowed(err): + ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_chars_not_allowed", newName)) + default: + ctx.ServerError("ChangeUserName", err) + } + return + } + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 735939a551..7001dc72ac 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1257,6 +1257,7 @@ func Routes(ctx gocontext.Context) *web.Route { m.Get("/orgs", org.ListUserOrgs) m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) + m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser) }, context_service.UserAssignmentAPI()) }) m.Group("/unadopted", func() { diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 979b184075..0c8d3d353f 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -48,6 +48,9 @@ type swaggerParameterBodies struct { // in:body CreateKeyOption api.CreateKeyOption + // in:body + RenameUserOption api.RenameUserOption + // in:body CreateLabelOption api.CreateLabelOption // in:body diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index b57ebfbcda..654e9000fa 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -79,7 +79,7 @@ func SettingsPost(ctx *context.Context) { ctx.Data["OrgName"] = true ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form) return - } else if err = user_model.ChangeUserName(org.AsUser(), form.Name); err != nil { + } else if err = user_model.ChangeUserName(ctx, org.AsUser(), form.Name); err != nil { switch { case db.IsErrNameReserved(err): ctx.Data["OrgName"] = true diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index f0f053a514..f500be7632 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -27,9 +27,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/services/agit" "code.gitea.io/gitea/services/forms" - container_service "code.gitea.io/gitea/services/packages/container" user_service "code.gitea.io/gitea/services/user" ) @@ -57,45 +55,25 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s return fmt.Errorf(ctx.Tr("form.username_change_not_local_user")) } - // Check if user name has been changed - if user.LowerName != strings.ToLower(newName) { - if err := user_model.ChangeUserName(user, newName); err != nil { - switch { - case user_model.IsErrUserAlreadyExist(err): - ctx.Flash.Error(ctx.Tr("form.username_been_taken")) - case user_model.IsErrEmailAlreadyUsed(err): - ctx.Flash.Error(ctx.Tr("form.email_been_used")) - case db.IsErrNameReserved(err): - ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName)) - case db.IsErrNamePatternNotAllowed(err): - ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName)) - case db.IsErrNameCharsNotAllowed(err): - ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName)) - default: - ctx.ServerError("ChangeUserName", err) - } - return err + // rename user + if err := user_service.RenameUser(ctx, user, newName); err != nil { + switch { + case user_model.IsErrUserAlreadyExist(err): + ctx.Flash.Error(ctx.Tr("form.username_been_taken")) + case user_model.IsErrEmailAlreadyUsed(err): + ctx.Flash.Error(ctx.Tr("form.email_been_used")) + case db.IsErrNameReserved(err): + ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName)) + case db.IsErrNamePatternNotAllowed(err): + ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName)) + case db.IsErrNameCharsNotAllowed(err): + ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName)) + default: + ctx.ServerError("ChangeUserName", err) } - } else { - if err := repo_model.UpdateRepositoryOwnerNames(user.ID, newName); err != nil { - ctx.ServerError("UpdateRepository", err) - return err - } - } - - // update all agit flow pull request header - err := agit.UserNameChanged(user, newName) - if err != nil { - ctx.ServerError("agit.UserNameChanged", err) return err } - if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil { - ctx.ServerError("UpdateRepositoryNames", err) - return err - } - - log.Trace("User name changed: %s -> %s", user.Name, newName) return nil } diff --git a/services/agit/agit.go b/services/agit/agit.go index b61cb6f3f5..32fc3cba4a 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -226,8 +226,8 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git. } // UserNameChanged handle user name change for agit flow pull -func UserNameChanged(user *user_model.User, newName string) error { - pulls, err := issues_model.GetAllUnmergedAgitPullRequestByPoster(user.ID) +func UserNameChanged(ctx context.Context, user *user_model.User, newName string) error { + pulls, err := issues_model.GetAllUnmergedAgitPullRequestByPoster(ctx, user.ID) if err != nil { return err } diff --git a/services/user/rename.go b/services/user/rename.go new file mode 100644 index 0000000000..af195d7d76 --- /dev/null +++ b/services/user/rename.go @@ -0,0 +1,41 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "context" + "fmt" + "strings" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/services/agit" + container_service "code.gitea.io/gitea/services/packages/container" +) + +func renameUser(ctx context.Context, u *user_model.User, newUserName string) error { + if u.IsOrganization() { + return fmt.Errorf("cannot rename organization") + } + + if err := user_model.ChangeUserName(ctx, u, newUserName); err != nil { + return err + } + + if err := agit.UserNameChanged(ctx, u, newUserName); err != nil { + return err + } + if err := container_service.UpdateRepositoryNames(ctx, u, newUserName); err != nil { + return err + } + + u.Name = newUserName + u.LowerName = strings.ToLower(newUserName) + if err := user_model.UpdateUser(ctx, u, false); err != nil { + return err + } + + log.Trace("User name changed: %s -> %s", u.Name, newUserName) + return nil +} diff --git a/services/user/user.go b/services/user/user.go index f0b8fe1c31..d52a2f404b 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -27,6 +27,22 @@ import ( "code.gitea.io/gitea/services/packages" ) +// RenameUser renames a user +func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + if err := renameUser(ctx, u, newUserName); err != nil { + return err + } + if err := committer.Commit(); err != nil { + return err + } + return err +} + // DeleteUser completely and permanently deletes everything of a user, // but issues/comments/pulls will be kept and shown as someone has been deleted, // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c304a7a497..7dc7f563c2 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -679,6 +679,46 @@ } } }, + "/admin/users/{username}/rename": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Rename a user", + "operationId": "adminRenameUser", + "parameters": [ + { + "type": "string", + "description": "existing username of user", + "name": "username", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/RenameUserOption" + } + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, "/admin/users/{username}/repos": { "post": { "consumes": [ @@ -19105,6 +19145,22 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "RenameUserOption": { + "description": "RenameUserOption options when renaming a user", + "type": "object", + "required": [ + "new_username" + ], + "properties": { + "new_username": { + "description": "New username for this user. This name cannot be in use yet by any other user.", + "type": "string", + "uniqueItems": true, + "x-go-name": "NewName" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "RepoCollaboratorPermission": { "description": "RepoCollaboratorPermission to get repository permission for a collaborator", "type": "object", From d56bb7420184c0c2f451f4bcaa96c9b3b00c393d Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 14 Mar 2023 03:54:40 -0400 Subject: [PATCH 16/94] add admin API email endpoints (#22792) add email endpoint to admin API to ensure API parity with admin dashboard. --- modules/structs/user_email.go | 3 ++ routers/api/v1/admin/email.go | 87 ++++++++++++++++++++++++++++++++++ routers/api/v1/api.go | 4 ++ services/convert/convert.go | 11 +++++ templates/swagger/v1_json.tmpl | 83 ++++++++++++++++++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 routers/api/v1/admin/email.go diff --git a/modules/structs/user_email.go b/modules/structs/user_email.go index 6a11e040af..9319667e8f 100644 --- a/modules/structs/user_email.go +++ b/modules/structs/user_email.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package structs @@ -9,6 +10,8 @@ type Email struct { Email string `json:"email"` Verified bool `json:"verified"` Primary bool `json:"primary"` + UserID int64 `json:"user_id"` + UserName string `json:"username"` } // CreateEmailOption options when creating email addresses diff --git a/routers/api/v1/admin/email.go b/routers/api/v1/admin/email.go new file mode 100644 index 0000000000..8d0491e070 --- /dev/null +++ b/routers/api/v1/admin/email.go @@ -0,0 +1,87 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package admin + +import ( + "net/http" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/convert" +) + +// GetAllEmails +func GetAllEmails(ctx *context.APIContext) { + // swagger:operation GET /admin/emails admin adminGetAllEmails + // --- + // summary: List all emails + // produces: + // - application/json + // parameters: + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/EmailList" + // "403": + // "$ref": "#/responses/forbidden" + + listOptions := utils.GetListOptions(ctx) + + emails, maxResults, err := user_model.SearchEmails(&user_model.SearchEmailOptions{ + Keyword: ctx.Params(":email"), + ListOptions: listOptions, + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetAllEmails", err) + return + } + + results := make([]*api.Email, len(emails)) + for i := range emails { + results[i] = convert.ToEmailSearch(emails[i]) + } + + ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) + ctx.SetTotalCountHeader(maxResults) + ctx.JSON(http.StatusOK, &results) +} + +// SearchEmail +func SearchEmail(ctx *context.APIContext) { + // swagger:operation GET /admin/emails/search admin adminSearchEmails + // --- + // summary: Search all emails + // produces: + // - application/json + // parameters: + // - name: q + // in: query + // description: keyword + // type: string + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/EmailList" + // "403": + // "$ref": "#/responses/forbidden" + + ctx.SetParams(":email", ctx.FormTrim("q")) + GetAllEmails(ctx) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 7001dc72ac..5c32164fa7 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1260,6 +1260,10 @@ func Routes(ctx gocontext.Context) *web.Route { m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser) }, context_service.UserAssignmentAPI()) }) + m.Group("/emails", func() { + m.Get("", admin.GetAllEmails) + m.Get("/search", admin.SearchEmail) + }) m.Group("/unadopted", func() { m.Get("", admin.ListUnadoptedRepositories) m.Post("/{username}/{reponame}", admin.AdoptRepository) diff --git a/services/convert/convert.go b/services/convert/convert.go index 5f2100a039..bce0e7ba21 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -38,6 +38,17 @@ func ToEmail(email *user_model.EmailAddress) *api.Email { } } +// ToEmail convert models.EmailAddress to api.Email +func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email { + return &api.Email{ + Email: email.Email, + Verified: email.IsActivated, + Primary: email.IsPrimary, + UserID: email.UID, + UserName: email.Name, + } +} + // ToBranch convert a git.Commit and git.Branch to an api.Branch func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { if bp == nil { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 7dc7f563c2..9c89b21fca 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -138,6 +138,80 @@ } } }, + "/admin/emails": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "List all emails", + "operationId": "adminGetAllEmails", + "parameters": [ + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/EmailList" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + } + }, + "/admin/emails/search": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Search all emails", + "operationId": "adminSearchEmails", + "parameters": [ + { + "type": "string", + "description": "keyword", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/EmailList" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + } + }, "/admin/hooks": { "get": { "produces": [ @@ -16999,6 +17073,15 @@ "type": "boolean", "x-go-name": "Primary" }, + "user_id": { + "type": "integer", + "format": "int64", + "x-go-name": "UserID" + }, + "username": { + "type": "string", + "x-go-name": "UserName" + }, "verified": { "type": "boolean", "x-go-name": "Verified" From ac8d71ff07a3354a27d6a5daab45d1e79e242269 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 14 Mar 2023 17:51:20 +0800 Subject: [PATCH 17/94] Refactor branch/tag selector to Vue SFC (#23421) Follow #23394 There were many bad smells in old code. This PR only moves the code into Vue SFC, doesn't touch the unrelated logic. update: after https://github.com/go-gitea/gitea/pull/23421/commits/5f23218c851e12132f538a404c946bbf6ff38e62 , there should be no usage of the vue-rumtime-compiler anymore (hopefully), so I think this PR could close #19851 --------- Co-authored-by: Lunny Xiao --- templates/repo/branch_dropdown.tmpl | 98 ++---- web_src/js/components/DashboardRepoList.vue | 2 +- .../js/components/PullRequestMergeForm.vue | 7 +- .../js/components/RepoBranchTagDropdown.js | 208 ------------- .../js/components/RepoBranchTagSelector.vue | 293 ++++++++++++++++++ web_src/js/features/repo-findfile.js | 7 +- web_src/js/features/repo-findfile.test.js | 7 +- web_src/js/features/repo-legacy.js | 4 +- web_src/js/svg.js | 18 +- web_src/js/utils/url.js | 3 + web_src/js/utils/url.test.js | 7 + web_src/less/_base.less | 4 - web_src/less/_repository.less | 6 - webpack.config.js | 4 + 14 files changed, 359 insertions(+), 309 deletions(-) delete mode 100644 web_src/js/components/RepoBranchTagDropdown.js create mode 100644 web_src/js/components/RepoBranchTagSelector.vue create mode 100644 web_src/js/utils/url.js create mode 100644 web_src/js/utils/url.test.js diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl index 8e81373aec..1ec4b7ef16 100644 --- a/templates/repo/branch_dropdown.tmpl +++ b/templates/repo/branch_dropdown.tmpl @@ -1,6 +1,20 @@ -{{$release := .release}} -{{$defaultBranch := $.root.BranchName}}{{if and .root.IsViewTag (not .noTag)}}{{$defaultBranch = .root.TagName}}{{end}}{{if eq $defaultBranch ""}}{{$defaultBranch = $.root.Repository.DefaultBranch}}{{end}} -{{$type := ""}}{{if and .root.IsViewTag (not .noTag)}}{{$type = "tag"}}{{else if .root.IsViewBranch}}{{$type = "branch"}}{{else}}{{$type = "tree"}}{{end}} +{{$defaultBranch := $.root.BranchName}} +{{if and .root.IsViewTag (not .noTag)}} + {{$defaultBranch = .root.TagName}} +{{end}} +{{if eq $defaultBranch ""}} + {{$defaultBranch = $.root.Repository.DefaultBranch}} +{{end}} + +{{$type := ""}} +{{if and .root.IsViewTag (not .noTag)}} + {{$type = "tag"}} +{{else if .root.IsViewBranch}} + {{$type = "branch"}} +{{else}} + {{$type = "tree"}} +{{end}} + {{$showBranchesInDropdown := not .root.HideBranchesInDropdown}} -
+
+ {{/* show dummy elements before Vue componment is mounted, this code must match the code in BranchTagSelector.vue */}}
diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index e295910fd0..cc76ab627f 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -73,7 +73,7 @@
  • - +
    {{ repo.full_name }}
    diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue index fc610d2194..4d8c14a76d 100644 --- a/web_src/js/components/PullRequestMergeForm.vue +++ b/web_src/js/components/PullRequestMergeForm.vue @@ -10,8 +10,8 @@ -d '{"context": "test/context", "description": "description", "state": "${state}", "target_url": "http://localhost"}' -->
    - -
    + +
    @@ -30,7 +30,8 @@ -
    +
    + {{ mergeForm.textClearMergeMessageHint }}
    diff --git a/web_src/js/components/RepoBranchTagDropdown.js b/web_src/js/components/RepoBranchTagDropdown.js deleted file mode 100644 index a8945b82d1..0000000000 --- a/web_src/js/components/RepoBranchTagDropdown.js +++ /dev/null @@ -1,208 +0,0 @@ -import {createApp, nextTick} from 'vue'; -import $ from 'jquery'; - -export function initRepoBranchTagDropdown(selector) { - $(selector).each(function (dropdownIndex, elRoot) { - const data = { - csrfToken: window.config.csrfToken, - items: [], - searchTerm: '', - menuVisible: false, - createTag: false, - release: null, - - isViewTag: false, - isViewBranch: false, - isViewTree: false, - - active: 0, - - ...window.config.pageData.branchDropdownDataList[dropdownIndex], - }; - - // the "data.defaultBranch" is ambiguous, it could be "branch name" or "tag name" - - if (data.showBranchesInDropdown && data.branches) { - for (const branch of data.branches) { - data.items.push({name: branch, url: branch, branch: true, tag: false, selected: branch === data.defaultBranch}); - } - } - if (!data.noTag && data.tags) { - for (const tag of data.tags) { - if (data.release) { - data.items.push({name: tag, url: tag, branch: false, tag: true, selected: tag === data.release.tagName}); - } else { - data.items.push({name: tag, url: tag, branch: false, tag: true, selected: tag === data.defaultBranch}); - } - } - } - - const view = createApp({ - delimiters: ['${', '}'], - data() { - return data; - }, - computed: { - filteredItems() { - const items = this.items.filter((item) => { - return ((this.mode === 'branches' && item.branch) || (this.mode === 'tags' && item.tag)) && - (!this.searchTerm || item.name.toLowerCase().includes(this.searchTerm.toLowerCase())); - }); - - // no idea how to fix this so linting rule is disabled instead - this.active = (items.length === 0 && this.showCreateNewBranch ? 0 : -1); // eslint-disable-line vue/no-side-effects-in-computed-properties - return items; - }, - showNoResults() { - return this.filteredItems.length === 0 && !this.showCreateNewBranch; - }, - showCreateNewBranch() { - if (this.disableCreateBranch || !this.searchTerm) { - return false; - } - - return this.items.filter((item) => item.name.toLowerCase() === this.searchTerm.toLowerCase()).length === 0; - } - }, - - watch: { - menuVisible(visible) { - if (visible) { - this.focusSearchField(); - } - } - }, - - beforeMount() { - switch (data.viewType) { - case 'tree': - this.isViewTree = true; - break; - case 'tag': - this.isViewTag = true; - break; - default: - this.isViewBranch = true; - break; - } - - document.body.addEventListener('click', (event) => { - if (elRoot.contains(event.target)) return; - if (this.menuVisible) { - this.menuVisible = false; - } - }); - }, - - methods: { - selectItem(item) { - const prev = this.getSelected(); - if (prev !== null) { - prev.selected = false; - } - item.selected = true; - const url = (item.tag) ? this.tagURLPrefix + item.url + this.tagURLSuffix : this.branchURLPrefix + item.url + this.branchURLSuffix; - if (!this.branchForm) { - window.location.href = url; - } else { - this.isViewTree = false; - this.isViewTag = false; - this.isViewBranch = false; - this.$refs.dropdownRefName.textContent = item.name; - if (this.setAction) { - $(`#${this.branchForm}`).attr('action', url); - } else { - $(`#${this.branchForm} input[name="refURL"]`).val(url); - } - $(`#${this.branchForm} input[name="ref"]`).val(item.name); - if (item.tag) { - this.isViewTag = true; - $(`#${this.branchForm} input[name="refType"]`).val('tag'); - } else { - this.isViewBranch = true; - $(`#${this.branchForm} input[name="refType"]`).val('branch'); - } - if (this.submitForm) { - $(`#${this.branchForm}`).trigger('submit'); - } - this.menuVisible = false; - } - }, - createNewBranch() { - if (!this.showCreateNewBranch) return; - $(this.$refs.newBranchForm).trigger('submit'); - }, - focusSearchField() { - nextTick(() => { - this.$refs.searchField.focus(); - }); - }, - getSelected() { - for (let i = 0, j = this.items.length; i < j; ++i) { - if (this.items[i].selected) return this.items[i]; - } - return null; - }, - getSelectedIndexInFiltered() { - for (let i = 0, j = this.filteredItems.length; i < j; ++i) { - if (this.filteredItems[i].selected) return i; - } - return -1; - }, - scrollToActive() { - let el = this.$refs[`listItem${this.active}`]; - if (!el || !el.length) return; - if (Array.isArray(el)) { - el = el[0]; - } - - const cont = this.$refs.scrollContainer; - if (el.offsetTop < cont.scrollTop) { - cont.scrollTop = el.offsetTop; - } else if (el.offsetTop + el.clientHeight > cont.scrollTop + cont.clientHeight) { - cont.scrollTop = el.offsetTop + el.clientHeight - cont.clientHeight; - } - }, - keydown(event) { - if (event.keyCode === 40) { // arrow down - event.preventDefault(); - - if (this.active === -1) { - this.active = this.getSelectedIndexInFiltered(); - } - - if (this.active + (this.showCreateNewBranch ? 0 : 1) >= this.filteredItems.length) { - return; - } - this.active++; - this.scrollToActive(); - } else if (event.keyCode === 38) { // arrow up - event.preventDefault(); - - if (this.active === -1) { - this.active = this.getSelectedIndexInFiltered(); - } - - if (this.active <= 0) { - return; - } - this.active--; - this.scrollToActive(); - } else if (event.keyCode === 13) { // enter - event.preventDefault(); - - if (this.active >= this.filteredItems.length) { - this.createNewBranch(); - } else if (this.active >= 0) { - this.selectItem(this.filteredItems[this.active]); - } - } else if (event.keyCode === 27) { // escape - event.preventDefault(); - this.menuVisible = false; - } - } - } - }); - view.mount(this); - }); -} diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue new file mode 100644 index 0000000000..6a65eeec6f --- /dev/null +++ b/web_src/js/components/RepoBranchTagSelector.vue @@ -0,0 +1,293 @@ + + + diff --git a/web_src/js/features/repo-findfile.js b/web_src/js/features/repo-findfile.js index 093f90fe8e..078c822aa2 100644 --- a/web_src/js/features/repo-findfile.js +++ b/web_src/js/features/repo-findfile.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import {svg} from '../svg.js'; import {toggleElem} from '../utils/dom.js'; +import {pathEscapeSegments} from '../utils/url.js'; const {csrf} = window.config; @@ -73,10 +74,6 @@ export function filterRepoFilesWeighted(files, filter) { return filterResult; } -export function escapePath(s) { - return s.split('/').map(encodeURIComponent).join('/'); -} - function filterRepoFiles(filter) { const treeLink = $repoFindFileInput.attr('data-url-tree-link'); $repoFindFileTableBody.empty(); @@ -88,7 +85,7 @@ function filterRepoFiles(filter) { for (const r of filterResult) { const $row = $(tmplRow); const $a = $row.find('a'); - $a.attr('href', `${treeLink}/${escapePath(r.matchResult.join(''))}`); + $a.attr('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`); const $octiconFile = $(svg('octicon-file')).addClass('gt-mr-3'); $a.append($octiconFile); // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz'] diff --git a/web_src/js/features/repo-findfile.test.js b/web_src/js/features/repo-findfile.test.js index 5032185396..a90b0bf0a2 100644 --- a/web_src/js/features/repo-findfile.test.js +++ b/web_src/js/features/repo-findfile.test.js @@ -1,5 +1,5 @@ import {describe, expect, test} from 'vitest'; -import {strSubMatch, calcMatchedWeight, filterRepoFilesWeighted, escapePath} from './repo-findfile.js'; +import {strSubMatch, calcMatchedWeight, filterRepoFilesWeighted} from './repo-findfile.js'; describe('Repo Find Files', () => { test('strSubMatch', () => { @@ -32,9 +32,4 @@ describe('Repo Find Files', () => { expect(res).toHaveLength(2); expect(res[0].matchResult).toEqual(['', 'word', '.txt']); }); - - test('escapePath', () => { - expect(escapePath('a/b/c')).toEqual('a/b/c'); - expect(escapePath('a/b/ c')).toEqual('a/b/%20c'); - }); }); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 5346a0d274..4454b92ccc 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -11,7 +11,7 @@ import { import {initUnicodeEscapeButton} from './repo-unicode-escape.js'; import {svg} from '../svg.js'; import {htmlEscape} from 'escape-goat'; -import {initRepoBranchTagDropdown} from '../components/RepoBranchTagDropdown.js'; +import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; import { initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown, initRepoCommonLanguageStats, @@ -486,7 +486,7 @@ export function initRepository() { // File list and commits if ($('.repository.file.list').length > 0 || $('.branch-dropdown').length > 0 || $('.repository.commits').length > 0 || $('.repository.release').length > 0) { - initRepoBranchTagDropdown('.choose.reference .ui.dropdown'); + initRepoBranchTagSelector('.js-branch-tag-selector'); } // Wiki diff --git a/web_src/js/svg.js b/web_src/js/svg.js index 9eabca3fd3..e431ca57e6 100644 --- a/web_src/js/svg.js +++ b/web_src/js/svg.js @@ -1,3 +1,4 @@ +import {h} from 'vue'; import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg'; import octiconChevronRight from '../../public/img/svg/octicon-chevron-right.svg'; import octiconClock from '../../public/img/svg/octicon-clock.svg'; @@ -40,6 +41,8 @@ import giteaDoubleChevronLeft from '../../public/img/svg/gitea-double-chevron-le import giteaDoubleChevronRight from '../../public/img/svg/gitea-double-chevron-right.svg'; import octiconChevronLeft from '../../public/img/svg/octicon-chevron-left.svg'; import octiconOrganization from '../../public/img/svg/octicon-organization.svg'; +import octiconTag from '../../public/img/svg/octicon-tag.svg'; +import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg'; const svgs = { 'octicon-blocked': octiconBlocked, @@ -84,9 +87,13 @@ const svgs = { 'gitea-double-chevron-right': giteaDoubleChevronRight, 'octicon-chevron-left': octiconChevronLeft, 'octicon-organization': octiconOrganization, + 'octicon-tag': octiconTag, + 'octicon-git-branch': octiconGitBranch, }; -// TODO: use a more general approach to access SVG icons. At the moment, developers must check, pick and fill the names manually, most of the SVG icons in assets couldn't be used directly. +// TODO: use a more general approach to access SVG icons. +// At the moment, developers must check, pick and fill the names manually, +// most of the SVG icons in assets couldn't be used directly. const parser = new DOMParser(); const serializer = new XMLSerializer(); @@ -112,12 +119,7 @@ export const SvgIcon = { size: {type: Number, default: 16}, className: {type: String, default: ''}, }, - - computed: { - svg() { - return svg(this.name, this.size, this.className); - }, + render() { + return h('span', {innerHTML: svg(this.name, this.size, this.className)}); }, - - template: `` }; diff --git a/web_src/js/utils/url.js b/web_src/js/utils/url.js new file mode 100644 index 0000000000..a40737ca6f --- /dev/null +++ b/web_src/js/utils/url.js @@ -0,0 +1,3 @@ +export function pathEscapeSegments(s) { + return s.split('/').map(encodeURIComponent).join('/'); +} diff --git a/web_src/js/utils/url.test.js b/web_src/js/utils/url.test.js new file mode 100644 index 0000000000..ef2ffaa5f9 --- /dev/null +++ b/web_src/js/utils/url.test.js @@ -0,0 +1,7 @@ +import {expect, test} from 'vitest'; +import {pathEscapeSegments} from './url.js'; + +test('pathEscapeSegments', () => { + expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c'); + expect(pathEscapeSegments('a/b/ c')).toEqual('a/b/%20c'); +}); diff --git a/web_src/less/_base.less b/web_src/less/_base.less index 1cf65e784c..cabf707aad 100644 --- a/web_src/less/_base.less +++ b/web_src/less/_base.less @@ -1924,10 +1924,6 @@ footer { display: block; } -[v-cloak] { - display: none !important; -} - .repos-search { padding-bottom: 0 !important; } diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index 0069a31cec..c842c4ca65 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -222,12 +222,6 @@ font-size: 1.2em; } - .choose.reference { - .header .icon { - font-size: 1.4em; - } - } - .repo-path { .section, diff --git a/webpack.config.js b/webpack.config.js index 245791e7ea..46bdd6acfa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -196,6 +196,10 @@ export default { ], }, plugins: [ + new webpack.DefinePlugin({ + __VUE_OPTIONS_API__: true, // at the moment, many Vue components still use the Vue Options API + __VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production + }), new VueLoaderPlugin(), new MiniCssExtractPlugin({ filename: 'css/[name].css', From c492e8631251a91714eec4ea7f7334110909aef2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 14 Mar 2023 19:49:59 +0800 Subject: [PATCH 18/94] Remove wrongly added column on migration test fixtures (#23456) Fix https://drone.gitea.io/go-gitea/gitea/69418/3/8 Migration fixtures are in `models/migrations/fixtures`, every folder will be used only by the test with the same name. For `Test_DeleteOrphanedIssueLabels`, the fixture should keep consistent as the database structure at that time. So the newly added `exclusive` is not right. Just revert the change in https://github.com/go-gitea/gitea/pull/22585/files#diff-f8db9cbbaa10bf7b27eb726884454db821a4b4f8cb9a0d50435555908761bbcb --- .../fixtures/Test_DeleteOrphanedIssueLabels/label.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml index 085b7f0882..d651c87d5b 100644 --- a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml +++ b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml @@ -4,7 +4,6 @@ org_id: 0 name: label1 color: '#abcdef' - exclusive: false num_issues: 2 num_closed_issues: 0 @@ -14,7 +13,6 @@ org_id: 0 name: label2 color: '#000000' - exclusive: false num_issues: 1 num_closed_issues: 1 - @@ -23,7 +21,6 @@ org_id: 3 name: orglabel3 color: '#abcdef' - exclusive: false num_issues: 0 num_closed_issues: 0 @@ -33,7 +30,6 @@ org_id: 3 name: orglabel4 color: '#000000' - exclusive: false num_issues: 1 num_closed_issues: 0 @@ -43,6 +39,5 @@ org_id: 0 name: pull-test-label color: '#000000' - exclusive: false num_issues: 0 num_closed_issues: 0 From 8e4063479736464aa3ef4372bd5e770d3ed7571c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 14 Mar 2023 20:50:51 +0800 Subject: [PATCH 19/94] Convert GitHub event on actions and fix some pull_request events. (#23037) Follow #22680 Partially Fix #22958, on pull_request, `opened`, `reopened`, `synchronize` supported, `edited` hasn't been supported yet because Gitea doesn't trigger that events. --------- Co-authored-by: yp05327 <576951401@qq.com> --- modules/actions/github.go | 41 +++++ modules/actions/workflows.go | 301 +++++++++++++++++++++-------------- 2 files changed, 220 insertions(+), 122 deletions(-) create mode 100644 modules/actions/github.go diff --git a/modules/actions/github.go b/modules/actions/github.go new file mode 100644 index 0000000000..bcde9a0f55 --- /dev/null +++ b/modules/actions/github.go @@ -0,0 +1,41 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + webhook_module "code.gitea.io/gitea/modules/webhook" + + "github.com/nektos/act/pkg/jobparser" +) + +const ( + githubEventPullRequest = "pull_request" + githubEventPullRequestTarget = "pull_request_target" + githubEventPullRequestReviewComment = "pull_request_review_comment" + githubEventPullRequestReview = "pull_request_review" + githubEventRegistryPackage = "registry_package" + githubEventCreate = "create" + githubEventDelete = "delete" + githubEventFork = "fork" + githubEventPush = "push" + githubEventIssues = "issues" + githubEventIssueComment = "issue_comment" + githubEventRelease = "release" + githubEventPullRequestComment = "pull_request_comment" +) + +func convertFromGithubEvent(evt *jobparser.Event) string { + switch evt.Name { + case githubEventPullRequest, githubEventPullRequestTarget, githubEventPullRequestReview, + githubEventPullRequestReviewComment: + return string(webhook_module.HookEventPullRequest) + case githubEventRegistryPackage: + return string(webhook_module.HookEventPackage) + case githubEventCreate, githubEventDelete, githubEventFork, githubEventPush, + githubEventIssues, githubEventIssueComment, githubEventRelease, githubEventPullRequestComment: + fallthrough + default: + return evt.Name + } +} diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 98fc831c31..67c3b12427 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -88,9 +88,7 @@ func DetectWorkflows(commit *git.Commit, triggedEvent webhook_module.HookEventTy continue } for _, evt := range events { - if evt.Name != triggedEvent.Event() { - continue - } + log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent) if detectMatched(commit, triggedEvent, payload, evt) { workflows[entry.Name()] = content } @@ -101,138 +99,197 @@ func DetectWorkflows(commit *git.Commit, triggedEvent webhook_module.HookEventTy } func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) bool { + if convertFromGithubEvent(evt) != string(triggedEvent) { + return false + } + + switch triggedEvent { + case webhook_module.HookEventCreate, + webhook_module.HookEventDelete, + webhook_module.HookEventFork, + webhook_module.HookEventIssueAssign, + webhook_module.HookEventIssueLabel, + webhook_module.HookEventIssueMilestone, + webhook_module.HookEventPullRequestAssign, + webhook_module.HookEventPullRequestLabel, + webhook_module.HookEventPullRequestMilestone, + webhook_module.HookEventPullRequestComment, + webhook_module.HookEventPullRequestReviewApproved, + webhook_module.HookEventPullRequestReviewRejected, + webhook_module.HookEventPullRequestReviewComment, + webhook_module.HookEventWiki, + webhook_module.HookEventRepository, + webhook_module.HookEventRelease, + webhook_module.HookEventPackage: + if len(evt.Acts) != 0 { + log.Warn("Ignore unsupported %s event arguments %q", triggedEvent, evt.Acts) + } + // no special filter parameters for these events, just return true if name matched + return true + + case webhook_module.HookEventPush: + return matchPushEvent(commit, payload.(*api.PushPayload), evt) + + case webhook_module.HookEventIssues: + return matchIssuesEvent(commit, payload.(*api.IssuePayload), evt) + + case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestSync: + return matchPullRequestEvent(commit, payload.(*api.PullRequestPayload), evt) + + case webhook_module.HookEventIssueComment: + return matchIssueCommentEvent(commit, payload.(*api.IssueCommentPayload), evt) + + default: + log.Warn("unsupported event %q", triggedEvent) + return false + } +} + +func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobparser.Event) bool { + // with no special filter parameters if len(evt.Acts) == 0 { return true } - switch triggedEvent { - case webhook_module.HookEventCreate: - fallthrough - case webhook_module.HookEventDelete: - fallthrough - case webhook_module.HookEventFork: - log.Warn("unsupported event %q", triggedEvent.Event()) - return false - case webhook_module.HookEventPush: - pushPayload := payload.(*api.PushPayload) - matchTimes := 0 - // all acts conditions should be satisfied - for cond, vals := range evt.Acts { - switch cond { - case "branches", "tags": - refShortName := git.RefName(pushPayload.Ref).ShortName() - for _, val := range vals { - if glob.MustCompile(val, '/').Match(refShortName) { - matchTimes++ - break - } + matchTimes := 0 + // all acts conditions should be satisfied + for cond, vals := range evt.Acts { + switch cond { + case "branches", "tags": + refShortName := git.RefName(pushPayload.Ref).ShortName() + for _, val := range vals { + if glob.MustCompile(val, '/').Match(refShortName) { + matchTimes++ + break } - case "paths": - filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before) - if err != nil { - log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) - } else { - for _, val := range vals { - matched := false - for _, file := range filesChanged { - if glob.MustCompile(val, '/').Match(file) { - matched = true - break - } - } - if matched { - matchTimes++ + } + case "paths": + filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before) + if err != nil { + log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) + } else { + for _, val := range vals { + matched := false + for _, file := range filesChanged { + if glob.MustCompile(val, '/').Match(file) { + matched = true break } } - } - default: - log.Warn("unsupported condition %q", cond) - } - } - return matchTimes == len(evt.Acts) - - case webhook_module.HookEventIssues: - fallthrough - case webhook_module.HookEventIssueAssign: - fallthrough - case webhook_module.HookEventIssueLabel: - fallthrough - case webhook_module.HookEventIssueMilestone: - fallthrough - case webhook_module.HookEventIssueComment: - fallthrough - case webhook_module.HookEventPullRequest: - prPayload := payload.(*api.PullRequestPayload) - matchTimes := 0 - // all acts conditions should be satisfied - for cond, vals := range evt.Acts { - switch cond { - case "types": - for _, val := range vals { - if glob.MustCompile(val, '/').Match(string(prPayload.Action)) { + if matched { matchTimes++ break } } - case "branches": - refShortName := git.RefName(prPayload.PullRequest.Base.Ref).ShortName() - for _, val := range vals { - if glob.MustCompile(val, '/').Match(refShortName) { - matchTimes++ - break - } - } - case "paths": - filesChanged, err := commit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref) - if err != nil { - log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) - } else { - for _, val := range vals { - matched := false - for _, file := range filesChanged { - if glob.MustCompile(val, '/').Match(file) { - matched = true - break - } - } - if matched { - matchTimes++ - break - } - } - } - default: - log.Warn("unsupported condition %q", cond) } + default: + log.Warn("push event unsupported condition %q", cond) } - return matchTimes == len(evt.Acts) - case webhook_module.HookEventPullRequestAssign: - fallthrough - case webhook_module.HookEventPullRequestLabel: - fallthrough - case webhook_module.HookEventPullRequestMilestone: - fallthrough - case webhook_module.HookEventPullRequestComment: - fallthrough - case webhook_module.HookEventPullRequestReviewApproved: - fallthrough - case webhook_module.HookEventPullRequestReviewRejected: - fallthrough - case webhook_module.HookEventPullRequestReviewComment: - fallthrough - case webhook_module.HookEventPullRequestSync: - fallthrough - case webhook_module.HookEventWiki: - fallthrough - case webhook_module.HookEventRepository: - fallthrough - case webhook_module.HookEventRelease: - fallthrough - case webhook_module.HookEventPackage: - fallthrough - default: - log.Warn("unsupported event %q", triggedEvent.Event()) } - return false + return matchTimes == len(evt.Acts) +} + +func matchIssuesEvent(commit *git.Commit, issuePayload *api.IssuePayload, evt *jobparser.Event) bool { + // with no special filter parameters + if len(evt.Acts) == 0 { + return true + } + + matchTimes := 0 + // all acts conditions should be satisfied + for cond, vals := range evt.Acts { + switch cond { + case "types": + for _, val := range vals { + if glob.MustCompile(val, '/').Match(string(issuePayload.Action)) { + matchTimes++ + break + } + } + default: + log.Warn("issue event unsupported condition %q", cond) + } + } + return matchTimes == len(evt.Acts) +} + +func matchPullRequestEvent(commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool { + // with no special filter parameters + if len(evt.Acts) == 0 { + // defaultly, only pull request opened and synchronized will trigger workflow + return prPayload.Action == api.HookIssueSynchronized || prPayload.Action == api.HookIssueOpened + } + + matchTimes := 0 + // all acts conditions should be satisfied + for cond, vals := range evt.Acts { + switch cond { + case "types": + action := prPayload.Action + if prPayload.Action == api.HookIssueSynchronized { + action = "synchronize" + } + log.Trace("matching pull_request %s with %v", action, vals) + for _, val := range vals { + if glob.MustCompile(val, '/').Match(string(action)) { + matchTimes++ + break + } + } + case "branches": + refShortName := git.RefName(prPayload.PullRequest.Base.Ref).ShortName() + for _, val := range vals { + if glob.MustCompile(val, '/').Match(refShortName) { + matchTimes++ + break + } + } + case "paths": + filesChanged, err := commit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref) + if err != nil { + log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) + } else { + for _, val := range vals { + matched := false + for _, file := range filesChanged { + if glob.MustCompile(val, '/').Match(file) { + matched = true + break + } + } + if matched { + matchTimes++ + break + } + } + } + default: + log.Warn("pull request event unsupported condition %q", cond) + } + } + return matchTimes == len(evt.Acts) +} + +func matchIssueCommentEvent(commit *git.Commit, issueCommentPayload *api.IssueCommentPayload, evt *jobparser.Event) bool { + // with no special filter parameters + if len(evt.Acts) == 0 { + return true + } + + matchTimes := 0 + // all acts conditions should be satisfied + for cond, vals := range evt.Acts { + switch cond { + case "types": + for _, val := range vals { + if glob.MustCompile(val, '/').Match(string(issueCommentPayload.Action)) { + matchTimes++ + break + } + } + default: + log.Warn("issue comment unsupported condition %q", cond) + } + } + return matchTimes == len(evt.Acts) } From db9bb3699aaa67abdce01b8e4280c4dbf0dc4250 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 14 Mar 2023 12:37:11 -0400 Subject: [PATCH 20/94] Push to create docs (#23458) This PR adds user friendly documentation on how to use push to create feature --- .../content/doc/usage/push-to-create.en-us.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/content/doc/usage/push-to-create.en-us.md diff --git a/docs/content/doc/usage/push-to-create.en-us.md b/docs/content/doc/usage/push-to-create.en-us.md new file mode 100644 index 0000000000..e06ad4185d --- /dev/null +++ b/docs/content/doc/usage/push-to-create.en-us.md @@ -0,0 +1,35 @@ +--- +date: "2020-07-06T16:00:00+02:00" +title: "Usage: Push To Create" +slug: "push-to-create" +weight: 15 +toc: false +draft: false +menu: + sidebar: + parent: "usage" + name: "Push To Create" + weight: 15 + identifier: "push-to-create" +--- + +# Push To Create + +Push to create is a feature that allows you to push to a repository that does not exist yet in Gitea. This is useful for automation and for allowing users to create repositories without having to go through the web interface. This feature is disabled by default. + +## Enabling Push To Create +In the `app.ini` file, set `ENABLE_PUSH_CREATE_USER` to `true` and `ENABLE_PUSH_CREATE_ORG` to `true` if you want to allow users to create repositories in their own user account and in organizations they are a member of respectively. Restart Gitea for the changes to take effect. You can read more about these two options in the [Configuration Cheat Sheet]({{< relref "doc/advanced/config-cheat-sheet.en-us.md#repository-repository" >}}). + +## Using Push To Create + +Assuming you have a git repository in the current directory, you can push to a repository that does not exist yet in Gitea by running the following command: + +```shell +# Add the remote you want to push to +git remote add origin git@{domain}:{username}/{repo name that does not exist yet}.git + +# push to the remote +git push -u origin main +``` + +This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. \ No newline at end of file From 991ab3b5ad4a0742b3965464b8f963ad7a766958 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 14 Mar 2023 12:53:02 -0400 Subject: [PATCH 21/94] Lint Markdown pass --- docs/content/doc/usage/push-to-create.en-us.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/doc/usage/push-to-create.en-us.md b/docs/content/doc/usage/push-to-create.en-us.md index e06ad4185d..be0aaacf3b 100644 --- a/docs/content/doc/usage/push-to-create.en-us.md +++ b/docs/content/doc/usage/push-to-create.en-us.md @@ -18,6 +18,7 @@ menu: Push to create is a feature that allows you to push to a repository that does not exist yet in Gitea. This is useful for automation and for allowing users to create repositories without having to go through the web interface. This feature is disabled by default. ## Enabling Push To Create + In the `app.ini` file, set `ENABLE_PUSH_CREATE_USER` to `true` and `ENABLE_PUSH_CREATE_ORG` to `true` if you want to allow users to create repositories in their own user account and in organizations they are a member of respectively. Restart Gitea for the changes to take effect. You can read more about these two options in the [Configuration Cheat Sheet]({{< relref "doc/advanced/config-cheat-sheet.en-us.md#repository-repository" >}}). ## Using Push To Create @@ -32,4 +33,4 @@ git remote add origin git@{domain}:{username}/{repo name that does not exist yet git push -u origin main ``` -This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. \ No newline at end of file +This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. From 6e75739c5ba1de30c37adbd9e590674b583912c2 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Tue, 14 Mar 2023 12:12:27 -0500 Subject: [PATCH 22/94] Push option bonus for PTC docs (#23473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up for #23458 I could have suggested this on the original PR, but I thought there would be more to add. Hadn't noticed the push options docs already had nearly the same shell command. 😅 Signed-off-by: jolheiser --- docs/content/doc/usage/push-options.en-us.md | 2 +- docs/content/doc/usage/push-options.zh-tw.md | 2 +- docs/content/doc/usage/push-to-create.en-us.md | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/content/doc/usage/push-options.en-us.md b/docs/content/doc/usage/push-options.en-us.md index 8d7de19609..b58a91c2cc 100644 --- a/docs/content/doc/usage/push-options.en-us.md +++ b/docs/content/doc/usage/push-options.en-us.md @@ -29,5 +29,5 @@ were added. Example of changing a repository's visibility to public: ```shell -git push -o repo.private=false -u origin master +git push -o repo.private=false -u origin main ``` diff --git a/docs/content/doc/usage/push-options.zh-tw.md b/docs/content/doc/usage/push-options.zh-tw.md index d6ffbe695b..558492af77 100644 --- a/docs/content/doc/usage/push-options.zh-tw.md +++ b/docs/content/doc/usage/push-options.zh-tw.md @@ -29,5 +29,5 @@ Gitea 從 `1.13` 版開始支援某些 [push options](https://git-scm.com/docs/g 以下範例修改儲存庫的可見性為公開: ```shell -git push -o repo.private=false -u origin master +git push -o repo.private=false -u origin main ``` diff --git a/docs/content/doc/usage/push-to-create.en-us.md b/docs/content/doc/usage/push-to-create.en-us.md index be0aaacf3b..7ed3f56368 100644 --- a/docs/content/doc/usage/push-to-create.en-us.md +++ b/docs/content/doc/usage/push-to-create.en-us.md @@ -34,3 +34,7 @@ git push -u origin main ``` This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. + +## Push options (bonus) + +Push-to-create will default to the visibility defined by `DEFAULT_PUSH_CREATE_PRIVATE` in `app.ini`. To explicitly set the visibility, you can use a [push option]({{< relref "doc/usage/push-options.en-us.md" >}}). From 32204fcf8bf780143f93165281b899c58a5a3f9c Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 14 Mar 2023 16:09:01 -0400 Subject: [PATCH 23/94] test_env: hardcode major go version in use (#23464) hardcode the version of test_env we use in docker, so that we can use different major versions of golang between versions of Gitea --------- Co-authored-by: Lauris BH --- .drone.yml | 40 +++++++++++++++++----------------- Makefile | 2 +- modules/highlight/highlight.go | 6 +++-- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1181b809b0..2bcf494d91 100644 --- a/.drone.yml +++ b/.drone.yml @@ -44,7 +44,7 @@ steps: depends_on: [deps-frontend] - name: lint-backend - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env pull: always commands: - make lint-backend @@ -58,7 +58,7 @@ steps: path: /go - name: lint-backend-windows - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env commands: - make golangci-lint-windows vet environment: @@ -73,7 +73,7 @@ steps: path: /go - name: lint-backend-gogit - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env commands: - make lint-backend environment: @@ -234,13 +234,13 @@ steps: path: /go - name: prepare-test-env - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env pull: always commands: - ./build/test-env-prepare.sh - name: build - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - ./build/test-env-check.sh @@ -255,7 +255,7 @@ steps: path: /go - name: test-pgsql - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - timeout -s ABRT 50m make test-pgsql-migration test-pgsql @@ -336,13 +336,13 @@ steps: path: /go - name: prepare-test-env - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env pull: always commands: - ./build/test-env-prepare.sh - name: build - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - ./build/test-env-check.sh @@ -357,7 +357,7 @@ steps: path: /go - name: unit-test - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - make unit-test-coverage test-check @@ -373,7 +373,7 @@ steps: path: /go - name: unit-test-gogit - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - make unit-test-coverage test-check @@ -389,7 +389,7 @@ steps: path: /go - name: test-mysql - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - make test-mysql-migration integration-test-coverage @@ -490,13 +490,13 @@ steps: path: /go - name: prepare-test-env - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env pull: always commands: - ./build/test-env-prepare.sh - name: build - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - ./build/test-env-check.sh @@ -511,7 +511,7 @@ steps: path: /go - name: test-mysql8 - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - timeout -s ABRT 50m make test-mysql8-migration test-mysql8 @@ -580,13 +580,13 @@ steps: path: /go - name: prepare-test-env - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env pull: always commands: - ./build/test-env-prepare.sh - name: build - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - ./build/test-env-check.sh @@ -601,7 +601,7 @@ steps: path: /go - name: test-mssql - image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env user: gitea commands: - make test-mssql-migration test-mssql @@ -660,13 +660,13 @@ steps: path: /go - name: prepare-test-env - image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-arm64 # https://gitea.com/gitea/test-env pull: always commands: - ./build/test-env-prepare.sh - name: build - image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-arm64 # https://gitea.com/gitea/test-env user: gitea commands: - ./build/test-env-check.sh @@ -681,7 +681,7 @@ steps: path: /go - name: test-sqlite - image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env + image: gitea/test_env:linux-1.20-arm64 # https://gitea.com/gitea/test-env user: gitea commands: - timeout -s ABRT 50m make test-sqlite-migration test-sqlite diff --git a/Makefile b/Makefile index d770ed453f..1fde15c83e 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ AIR_PACKAGE ?= github.com/cosmtrek/air@v1.40.4 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.6.0 ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.2 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2 GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10 MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.4 diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index a5c38940a7..fac682b8a8 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -36,6 +36,8 @@ var ( once sync.Once cache *lru.TwoQueueCache + + githubStyles = styles.Get("github") ) // NewContext loads custom highlight map from local config @@ -121,7 +123,7 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string { return code } // style not used for live site but need to pass something - err = formatter.Format(htmlw, styles.GitHub, iterator) + err = formatter.Format(htmlw, githubStyles, iterator) if err != nil { log.Error("Can't format code: %v", err) return code @@ -184,7 +186,7 @@ func File(fileName, language string, code []byte) ([]string, string, error) { lines := make([]string, 0, len(tokensLines)) for _, tokens := range tokensLines { iterator = chroma.Literator(tokens...) - err = formatter.Format(htmlBuf, styles.GitHub, iterator) + err = formatter.Format(htmlBuf, githubStyles, iterator) if err != nil { return nil, "", fmt.Errorf("can't format code: %w", err) } From b2c1c17f38935a099d9153e2e53b494aafa5d8ea Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Tue, 14 Mar 2023 22:55:49 +0200 Subject: [PATCH 24/94] Fix due date being wrong on issue list (#23475) Exactly like #22302 but in the issue list page --- templates/shared/issuelist.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index 16bb3c1ec7..ae9cb55d44 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -108,7 +108,7 @@ {{svg "octicon-calendar" 14 "gt-mr-2"}} - + {{end}} From 74b80d1b2f7823ba4ad373903966885e81b41165 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 15 Mar 2023 00:17:03 +0000 Subject: [PATCH 25/94] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 1 + options/locale/locale_de-DE.ini | 1 + options/locale/locale_el-GR.ini | 1 + options/locale/locale_es-ES.ini | 1 + options/locale/locale_it-IT.ini | 1 + options/locale/locale_ja-JP.ini | 1 + options/locale/locale_lv-LV.ini | 1 + options/locale/locale_pt-BR.ini | 1 + options/locale/locale_pt-PT.ini | 12 ++++++++--- options/locale/locale_tr-TR.ini | 35 +++++++++++++++++++++++++++++++++ options/locale/locale_zh-CN.ini | 1 + options/locale/locale_zh-TW.ini | 1 + 12 files changed, 54 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 543cd44045..258267fac1 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -3269,6 +3269,7 @@ rubygems.dependencies.development=Vývojové závislosti rubygems.required.ruby=Vyžaduje verzi Ruby rubygems.required.rubygems=Vyžaduje verzi RubyGem rubygems.documentation=Další informace o registru RubyGems naleznete v
    dokumentaci. +swift.registry=Nastavte tento registr z příkazového řádku: vagrant.install=Pro přidání Vagrant box spusťte následující příkaz: vagrant.documentation=Další informace o registru Vagrant naleznete v dokumentaci. settings.link=Propojit tento balíček s repozitářem diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 9bee2104e4..d195fe206c 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -3122,6 +3122,7 @@ rubygems.dependencies.development=Entwicklungsabhängigkeiten rubygems.required.ruby=Benötigt Ruby Version rubygems.required.rubygems=Benötigt RubyGem Version rubygems.documentation=Weitere Informationen zur RubyGems-Paketverwaltung findest du in der Dokumentation. +swift.registry=Diese Registry über die Kommandozeile einrichten: vagrant.install=Um eine Vagrant-Box hinzuzufügen, führen Sie folgenden Befehl aus: vagrant.documentation=Für weitere Informationen zur Vagrant-Registry, siehe Dokumentation. settings.link=Dieses Paket einem Repository zuweisen diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 6affdf7e8b..5fd330fa84 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -3155,6 +3155,7 @@ rubygems.dependencies.development=Εξαρτήσεις Ανάπτυξης rubygems.required.ruby=Απαιτεί την έκδοση Ruby rubygems.required.rubygems=Απαιτεί έκδοση RubyGem rubygems.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο RubyGems, ανατρέξτε στην τεκμηρίωση. +swift.registry=Ρυθμίστε αυτό το μητρώο από τη γραμμή εντολών: vagrant.install=Για προσθήκη ενός κυτίου Vagrant, εκτελέστε την ακόλουθη εντολή: vagrant.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο του Vagrant, ανατρέξτε στην τεκμηρίωση. settings.link=Σύνδεση αυτού του πακέτου με ένα αποθετήριο diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index f104d3c49d..f67eb7ee9f 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -3154,6 +3154,7 @@ rubygems.dependencies.development=Dependencias de desarrollo rubygems.required.ruby=Requiere versión Ruby rubygems.required.rubygems=Requiere la versión de RubyGem rubygems.documentation=Para obtener más información sobre el registro de RubyGems, consulte la documentación. +swift.registry=Configurar este registro desde la línea de comandos: vagrant.install=Para añadir un paquete Vagrant, ejecuta el siguiente comando: vagrant.documentation=Para más información sobre el registro de paquetes Vagrant, revisa la documentación. settings.link=Vincular este paquete a un repositorio diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index d77baee6e5..a4dfa501c5 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -3121,6 +3121,7 @@ rubygems.dependencies.development=Dipendenze Di Sviluppo rubygems.required.ruby=Richiede la versione di Ruby rubygems.required.rubygems=Richiede la versione RubyGem rubygems.documentation=Per ulteriori informazioni sul registro di RubyGems, vedere la documentazione. +swift.registry=Configura questo registro dalla riga di comando: settings.link=Collega questo pacchetto a un repository settings.link.description=Se si collega un pacchetto a un repository, il pacchetto è elencato nell'elenco dei pacchetti del repository. settings.link.select=Seleziona Repository diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 8e77329f5c..c0ff33b1e7 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -3232,6 +3232,7 @@ rubygems.dependencies.development=開発用依存関係 rubygems.required.ruby=必要なRubyバージョン rubygems.required.rubygems=必要なRubyGemバージョン rubygems.documentation=RubyGemsレジストリの詳細については、ドキュメント を参照してください。 +swift.registry=このレジストリをコマンドラインからセットアップします: vagrant.install=Vagrant ボックスを追加するには、次のコマンドを実行します。 vagrant.documentation=Vagrantレジストリの詳細については ドキュメントを参照してください。 settings.link=このパッケージをリポジトリにリンク diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 0f7862f8a3..2172c23f21 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -3329,6 +3329,7 @@ rubygems.dependencies.development=Izstrādes atkarības rubygems.required.ruby=Nepieciešamā Ruby versija rubygems.required.rubygems=Nepieciešamā RubyGem versija rubygems.documentation=Papildus informācija par RubyGems reģistru pieejama dokumentācijā. +swift.registry=Konfigurējiet šo reģistru no komandrindas: vagrant.install=Lai pievienotu Vagrant kasti, izpildiet sekojošu komandu: vagrant.documentation=Papildus informācija par Vagrant reģistru pieejama dokumentācijā. settings.link=Piesaistīt pakotni šim repozitorijam diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index b34b5642dc..5ec3e084bf 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -3237,6 +3237,7 @@ rubygems.dependencies.development=Dependências de Desenvolvimento rubygems.required.ruby=Requer o Ruby versão rubygems.required.rubygems=Requer o RubyGem versão rubygems.documentation=Para obter mais informações sobre o registro do RubyGems, consulte a documentação. +swift.registry=Configure este registro pela linha de comando: vagrant.install=Para adicionar uma Vagrant box, execute o seguinte comando: vagrant.documentation=Para obter mais informações sobre o registro do Vagrant, consulte a documentação. settings.link=Vincular este pacote a um repositório diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index ef7a400ec8..ee36a0f4e6 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1357,8 +1357,8 @@ issues.opened_by_fake=%[1]s aberta(s) por %[2]s issues.closed_by_fake=por %[2]s foi fechada %[1]s issues.previous=Anterior issues.next=Seguinte -issues.open_title=Aberta -issues.closed_title=Fechada +issues.open_title=aberta(s) +issues.closed_title=fechada(s) issues.draft_title=Rascunho issues.num_comments=%d comentários issues.commented_at=`comentou %s` @@ -1780,7 +1780,7 @@ activity.title.issues_1=%d questão activity.title.issues_n=%d questões activity.title.issues_closed_from=%s resolvida(s) de %s activity.title.issues_created_by=%s criada por %s -activity.closed_issue_label=Encerrada +activity.closed_issue_label=Fechada activity.new_issues_count_1=questão nova activity.new_issues_count_n=questões novas activity.new_issue_label=Em aberto @@ -2934,6 +2934,8 @@ config.git_disable_diff_highlight=Desabilitar o realce de sintaxe no diff config.git_max_diff_lines=Número máximo de linhas diff (por ficheiro) config.git_max_diff_line_characters=Número máximos de caracteres diff (por linha) config.git_max_diff_files=Número máximo de ficheiros diff a serem apresentados +config.git_enable_reflogs=Habilitar reflogs +config.git_reflog_expiry_time=Tempo de expiração config.git_gc_args=Argumentos da recolha de lixo config.git_migrate_timeout=Prazo da migração config.git_mirror_timeout=Prazo para sincronização da réplica @@ -3237,6 +3239,10 @@ rubygems.dependencies.development=Dependências de desenvolvimento rubygems.required.ruby=Requer a versão do Ruby rubygems.required.rubygems=Requer a versão do RubyGem rubygems.documentation=Para obter mais informações sobre o registo do RubyGems, consulte a documentação. +swift.registry=Configurar este registo usando a linha de comandos: +swift.install=Adicione o pacote no seu ficheiro Package.swift: +swift.install2=e execute o seguinte comando: +swift.documentation=Para obter mais informações sobre o registo do Swift, consulte a documentação. vagrant.install=Para adicionar uma máquina virtual Vagrant, execute o seguinte comando: vagrant.documentation=Para obter mais informações sobre o registo do Vagrant, consulte a documentação. settings.link=Vincular este pacote a um repositório diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 131dfa69af..ae5b42c7d8 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -57,6 +57,7 @@ new_mirror=Yeni Yansı new_fork=Yeni Depo Çatalı new_org=Yeni Organizasyon new_project=Yeni Proje +new_project_column=Yeni Sütun manage_org=Organizasyonları Yönet admin_panel=Site Yönetimi account_settings=Hesap Ayarları @@ -90,9 +91,11 @@ disabled=Devre Dışı copy=Kopyala copy_url=URL'yi kopyala +copy_content=İçeriği kopyala copy_branch=Dal adını kopyala copy_success=Kopyalandı! copy_error=Kopyalama başarısız oldu +copy_type_unsupported=Bu dosya türü kopyalanamaz write=Yaz preview=Önizleme @@ -109,6 +112,10 @@ never=Asla rss_feed=RSS Beslemesi [aria] +navbar=Gezinti Çubuğu +footer=Alt Bilgi +footer.software=Yazılım Hakkında +footer.links=Bağlantılar [filter] string.asc=A - Z @@ -240,7 +247,10 @@ default_enable_timetracking_popup=Yeni depolar için zaman takibini varsayılan no_reply_address=Gizlenecek E-Posta Alan Adı no_reply_address_helper=Gizlenmiş e-posta adresine sahip kullanıcılar için alan adı. Örneğin 'ali' kullanıcı adı, gizlenmiş e-postalar için alan adı 'yanityok.ornek.org' olarak ayarlandığında Git günlüğüne 'ali@yanityok.ornek.org' olarak kaydedilecektir. password_algorithm=Parola Hash Algoritması +invalid_password_algorithm=Hatalı parola hash algoritması password_algorithm_helper=Parola için hash algoritmasını ayarlayın. Algoritmalar değişen gereksinimlere ve güce sahiptirler. `argon2` iyi özelliklere sahip olmasına rağmen fazla miktarda bellek kullanır ve küçük sistemler için uygun olmayabilir. +enable_update_checker=Güncelleme Denetleyicisini Etkinleştir +enable_update_checker_helper=Düzenli olarak gitea.io'ya bağlanarak yeni yayınlanan sürümleri denetler. [home] uname_holder=Kullanıcı Adı veya E-Posta Adresi @@ -290,14 +300,36 @@ code_last_indexed_at=Son endekslenen %s relevant_repositories_tooltip=Çatal olan veya konusu, simgesi veya açıklaması olmayan depolar gizlenmiştir. relevant_repositories=`Sadece ilişkili depolar gösteriliyor, belgeye bakabilirsiniz. +swift.registry=Bu kütüğü komut satırını kullanarak kurun: vagrant.install=Vagrant paketi eklemek için aşağıdaki komutu çalıştırın: vagrant.documentation=Vagrant kütüğü hakkında daha fazla bilgi için, belgeye bakabilirsiniz. settings.link=Bu paketi bir depoya bağlayın diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index aa4cc4dde9..21836963f1 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -3232,6 +3232,7 @@ rubygems.dependencies.development=开发依赖 rubygems.required.ruby=需要 Ruby 版本 rubygems.required.rubygems=需要 RubyGem 版本 rubygems.documentation=关于 RubyGems 注册中心的更多信息,请参阅 文档。 +swift.registry=从命令行设置此注册中心: vagrant.install=若要添加一个 Vagrant box,请运行以下命令: vagrant.documentation=关于 Vagrant 注册中心的更多信息,请参阅 文档。 settings.link=将此软件包链接到仓库 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index e98e55b777..5ef7b96dee 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -3232,6 +3232,7 @@ rubygems.dependencies.development=開發相依性 rubygems.required.ruby=需要的 Ruby 版本 rubygems.required.rubygems=需要的 RubyGem 版本 rubygems.documentation=關於 RubyGems registry 的詳情請參閱說明文件。 +swift.registry=透過下列命令設定此註冊中心: vagrant.install=執行下列命令以新增 Vagrant box: vagrant.documentation=關於 Vagrant registry 的詳情請參閱說明文件。 settings.link=連結此套件到儲存庫 From 69c9ab387f2db4eab29749480ca21206de75e77a Mon Sep 17 00:00:00 2001 From: Jordan Cech <18233718+jayczech23@users.noreply.github.com> Date: Tue, 14 Mar 2023 18:39:36 -0600 Subject: [PATCH 26/94] Update app.example.ini (#23480) --- custom/conf/app.example.ini | 2 +- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index e53ed7ad9f..1e55ce73a2 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -20,7 +20,7 @@ ;; - The environment variable `$GITEA_WORK_DIR` ;; - A built-in value set at build time (see building from source) ;; - Otherwise it defaults to the directory of the _`AppPath`_ -;; - If any of the above are relative paths then they are made absolute against the +;; - If any of the above are relative paths then they are made absolute against ;; the directory of the _`AppPath`_ ;; - _`CustomPath`_: This is the base directory for custom templates and other options. ;; It is determined by using the first set thing in the following hierarchy: diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 4b9c519cd8..e0df371a0f 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -46,7 +46,7 @@ reported as part of the default configuration when running `gitea --help` or on - The environment variable `$GITEA_WORK_DIR` - A built-in value set at build time (see building from source) - Otherwise it defaults to the directory of the _`AppPath`_ - - If any of the above are relative paths then they are made absolute against the + - If any of the above are relative paths then they are made absolute against the directory of the _`AppPath`_ - _`CustomPath`_: This is the base directory for custom templates and other options. It is determined by using the first set thing in the following hierarchy: From f96eef872f2500e8610b6c6c244121643bf4951f Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 14 Mar 2023 21:46:13 -0400 Subject: [PATCH 27/94] Skip DB tests duplicate runs on push to branches (#23476) This skips all testing-* pipelines on push to main or release/* branches. This decreases the total build time on those, as in theory they should already be run for PRs before merging. Fixes https://github.com/go-gitea/gitea/issues/22011 --------- Co-authored-by: Lunny Xiao --- .drone.yml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/.drone.yml b/.drone.yml index 2bcf494d91..08b64b2388 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,8 +9,6 @@ platform: trigger: event: - - push - - tag - pull_request paths: exclude: @@ -180,8 +178,6 @@ depends_on: trigger: event: - - push - - tag - pull_request paths: exclude: @@ -285,8 +281,6 @@ depends_on: trigger: event: - - push - - tag - pull_request paths: exclude: @@ -449,8 +443,6 @@ depends_on: trigger: event: - - push - - tag - pull_request paths: exclude: @@ -538,8 +530,6 @@ depends_on: trigger: event: - - push - - tag - pull_request paths: exclude: @@ -627,8 +617,6 @@ depends_on: trigger: event: - - push - - tag - pull_request paths: exclude: @@ -1477,7 +1465,7 @@ steps: --- kind: pipeline type: docker -name: docker-linux-amd64-dry-run +name: docker-linux-arm64-dry-run platform: os: linux @@ -1487,8 +1475,8 @@ depends_on: - compliance trigger: - ref: - - "refs/pull/**" + event: + - pull_request paths: exclude: - docs/** From ea1b92621bb5b06d96f360a52b6e85c6d7401533 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 15 Mar 2023 02:53:14 +0100 Subject: [PATCH 28/94] Use `gitea/test_env` image instead of `golang` (#23455) The `safe.directory` setting was not executed for pull requests, which made subsequent `deps-backend` target fail at `go mod download`. To fix it, split thep and perform the git config unconditionally. Example: https://drone.gitea.io/go-gitea/gitea/69477/4/3 Co-authored-by: Lunny Xiao --- .drone.yml | 49 +++++++++++++++++-------------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/.drone.yml b/.drone.yml index 08b64b2388..0e8e8abcbf 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,7 +26,7 @@ steps: - make deps-frontend - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -90,7 +90,7 @@ steps: depends_on: [deps-frontend] - name: checks-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 commands: - make --always-make checks-backend # ensure the 'go-licenses' make target runs depends_on: [deps-backend] @@ -111,7 +111,7 @@ steps: depends_on: [deps-frontend] - name: build-backend-no-gcc - image: golang:1.19 # this step is kept as the lowest version of golang that we support + image: gitea/test_env:linux-1.19-amd64 # this step is kept as the lowest version of golang that we support pull: always environment: GOPROXY: https://goproxy.io @@ -123,7 +123,7 @@ steps: path: /go - name: build-backend-arm64 - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 environment: GOPROXY: https://goproxy.io GOOS: linux @@ -138,7 +138,7 @@ steps: path: /go - name: build-backend-windows - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 environment: GOPROXY: https://goproxy.io GOOS: windows @@ -152,7 +152,7 @@ steps: path: /go - name: build-backend-386 - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 environment: GOPROXY: https://goproxy.io GOOS: linux @@ -213,7 +213,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force when: event: @@ -221,7 +220,7 @@ steps: - pull_request - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -313,7 +312,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force when: event: @@ -321,7 +319,7 @@ steps: - pull_request - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -399,7 +397,7 @@ steps: path: /go - name: generate-coverage - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 commands: - make coverage environment: @@ -465,7 +463,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force when: event: @@ -473,7 +470,7 @@ steps: - pull_request - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -553,7 +550,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force when: event: @@ -561,7 +557,7 @@ steps: - pull_request - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -631,7 +627,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force when: event: @@ -639,7 +634,7 @@ steps: - pull_request - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-arm64 pull: always commands: - make deps-backend @@ -721,7 +716,7 @@ steps: depends_on: [deps-frontend] - name: deps-backend - image: golang:1.18 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -830,7 +825,7 @@ trigger: steps: - name: download - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - timeout -s ABRT 40m make generate-license generate-gitignore @@ -890,7 +885,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: deps-frontend @@ -900,7 +894,7 @@ steps: - make deps-frontend - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -1026,7 +1020,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: deps-frontend @@ -1036,7 +1029,7 @@ steps: - make deps-frontend - name: deps-backend - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 pull: always commands: - make deps-backend @@ -1136,7 +1129,7 @@ trigger: steps: - name: build-docs - image: golang:1.20 + image: gitea/test_env:linux-1.20-amd64 commands: - cd docs - make trans-copy clean build @@ -1190,7 +1183,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish @@ -1268,7 +1260,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish @@ -1341,7 +1332,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish @@ -1415,7 +1405,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish @@ -1533,7 +1522,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish @@ -1611,7 +1599,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish @@ -1687,7 +1674,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish @@ -1761,7 +1747,6 @@ steps: image: docker:git pull: always commands: - - git config --global --add safe.directory /drone/src - git fetch --tags --force - name: publish From bf730528cadc4727eab8844934b6a0716d327243 Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 15 Mar 2023 03:19:27 +0100 Subject: [PATCH 29/94] Fix 'View File' button in code search (#23478) - Right-align 'View File' button - Add 'role' attribute to button link Before: Screenshot 2023-03-14 at 22 02 16 After: Screenshot 2023-03-14 at 22 02 33 Co-authored-by: Lunny Xiao --- templates/code/searchresults.tmpl | 6 +++--- templates/repo/search.tmpl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/code/searchresults.tmpl b/templates/code/searchresults.tmpl index 74721a5a67..684e2860bf 100644 --- a/templates/code/searchresults.tmpl +++ b/templates/code/searchresults.tmpl @@ -11,15 +11,15 @@ {{range $result := .SearchResults}} {{$repo := (index $.RepoMaps .RepoID)}}
    -

    - +

    + {{$repo.FullName}} {{if $repo.IsArchived}} {{$.locale.Tr "repo.desc.archived"}} {{end}} - {{.Filename}} - {{$.locale.Tr "repo.diff.view_file"}} + {{$.locale.Tr "repo.diff.view_file"}}

    diff --git a/templates/repo/search.tmpl b/templates/repo/search.tmpl index 21f758e4a9..ff4e218e2d 100644 --- a/templates/repo/search.tmpl +++ b/templates/repo/search.tmpl @@ -39,9 +39,9 @@ {{if $showFileViewToggle}} -
    + {{/* for image or CSV, it can have a horizontal scroll bar, there won't be review comment context menu (position absolute) which would be clipped by "overflow" */}} +
    {{if $isImage}} {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 9e974b9ea1..1c02de2d7a 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -22,6 +22,7 @@ /* below class names match Tailwind CSS */ .gt-pointer-events-none { pointer-events: none !important; } .gt-relative { position: relative !important; } +.gt-overflow-x-scroll { overflow-x: scroll !important; } .gt-mono { font-family: var(--fonts-monospace) !important; diff --git a/web_src/css/repository.css b/web_src/css/repository.css index bebe3ff8f4..d2040ec0ac 100644 --- a/web_src/css/repository.css +++ b/web_src/css/repository.css @@ -3337,10 +3337,6 @@ td.blob-excerpt { min-width: 100px; } -.diff-file-body { - overflow-x: scroll; -} - .diff-stats-bar { display: inline-block; background-color: var(--color-red); From 4b722068058931ab2734955a6b4120d5fa3d0830 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 16 Mar 2023 20:06:53 +0100 Subject: [PATCH 48/94] Update mini-css-extract-plugin, remove postcss (#23520) Follow-up and proper fix for https://github.com/go-gitea/gitea/pull/23504 Update to [mini-css-extract-plugin@2.7.4](https://github.com/webpack-contrib/mini-css-extract-plugin/releases/tag/v2.7.4) which fixes our specific issue described in https://github.com/webpack-contrib/css-loader/issues/1503 and which allows us to again drop the postcss dependency. Backport of this is not necessary as I have included it in https://github.com/go-gitea/gitea/pull/23508. Co-authored-by: techknowlogick --- package-lock.json | 378 ++++++------------------------ package.json | 7 +- web_src/css/themes/theme-auto.css | 1 - webpack.config.js | 13 - 4 files changed, 70 insertions(+), 329 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3aceff202..29335dade1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,12 +30,9 @@ "katex": "0.16.4", "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.0.2", - "mini-css-extract-plugin": "2.7.2", + "mini-css-extract-plugin": "2.7.4", "monaco-editor": "0.34.1", "monaco-editor-webpack-plugin": "7.0.1", - "postcss-import": "15.1.0", - "postcss-loader": "7.0.2", - "postcss-url": "10.1.3", "pretty-ms": "8.0.0", "sortablejs": "1.15.0", "swagger-ui-dist": "4.15.5", @@ -46,7 +43,7 @@ "vue-bar-graph": "2.0.0", "vue-loader": "17.0.1", "vue3-calendar-heatmap": "2.0.0", - "webpack": "5.76.0", + "webpack": "5.76.2", "webpack-cli": "5.0.1", "workbox-routing": "6.5.4", "workbox-strategies": "6.5.4", @@ -85,6 +82,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, "dependencies": { "@babel/highlight": "^7.18.6" }, @@ -96,6 +94,7 @@ "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -104,6 +103,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", @@ -117,6 +117,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -128,6 +129,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -141,6 +143,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -148,12 +151,14 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -162,6 +167,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -170,6 +176,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -1563,11 +1570,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, "node_modules/@types/tern": { "version": "0.23.4", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", @@ -2398,6 +2400,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -2893,11 +2896,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "node_modules/cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==" - }, "node_modules/cytoscape": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz", @@ -3829,6 +3827,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -5267,6 +5266,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -5399,7 +5399,8 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "node_modules/is-bigint": { "version": "1.0.4", @@ -5766,7 +5767,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -5938,14 +5940,6 @@ "node": ">=0.10.0" } }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/known-css-properties": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", @@ -6050,7 +6044,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/linkify-it": { "version": "4.0.1", @@ -6536,9 +6531,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", - "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.4.tgz", + "integrity": "sha512-V5zkjajQx9gnedglDap7ZjD1mNFNISzyllzrc+9+R4iwPRUAR0St20ADflQbWkVUQ2u/QU55t8mKaxUek8Cciw==", "dependencies": { "schema-utils": "^4.0.0" }, @@ -7004,6 +6999,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -7015,6 +7011,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -7107,6 +7104,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "engines": { "node": ">=8" } @@ -7288,58 +7286,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-loader": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", - "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.8" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -7435,67 +7381,6 @@ "node": ">=4" } }, - "node_modules/postcss-url": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", - "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", - "dependencies": { - "make-dir": "~3.1.0", - "mime": "~2.5.2", - "minimatch": "~3.0.4", - "xxhashjs": "~0.2.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-url/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/postcss-url/node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss-url/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/postcss-url/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -7669,22 +7554,6 @@ "node": ">=0.10.0" } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/read-cache/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -7958,6 +7827,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -9534,9 +9404,9 @@ } }, "node_modules/webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", + "version": "5.76.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", + "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -10065,14 +9935,6 @@ "node": "*" } }, - "node_modules/xxhashjs": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", - "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", - "dependencies": { - "cuint": "^0.2.2" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -10088,14 +9950,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", @@ -10156,6 +10010,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, "requires": { "@babel/highlight": "^7.18.6" } @@ -10163,12 +10018,14 @@ "@babel/helper-validator-identifier": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true }, "@babel/highlight": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", @@ -10179,6 +10036,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -10187,6 +10045,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -10197,6 +10056,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -10204,22 +10064,26 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -11153,11 +11017,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, "@types/tern": { "version": "0.23.4", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", @@ -11786,7 +11645,8 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true }, "camelcase": { "version": "5.3.1", @@ -12167,11 +12027,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==" - }, "cytoscape": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz", @@ -12860,6 +12715,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -13937,6 +13793,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -14030,7 +13887,8 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "is-bigint": { "version": "1.0.4", @@ -14284,7 +14142,8 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "4.1.0", @@ -14411,11 +14270,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, - "klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==" - }, "known-css-properties": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", @@ -14499,7 +14353,8 @@ "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "linkify-it": { "version": "4.0.1", @@ -14885,9 +14740,9 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", - "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.4.tgz", + "integrity": "sha512-V5zkjajQx9gnedglDap7ZjD1mNFNISzyllzrc+9+R4iwPRUAR0St20ADflQbWkVUQ2u/QU55t8mKaxUek8Cciw==", "requires": { "schema-utils": "^4.0.0" } @@ -15248,6 +15103,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "requires": { "callsites": "^3.0.0" } @@ -15256,6 +15112,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -15316,7 +15173,8 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true }, "pathe": { "version": "0.2.0", @@ -15444,40 +15302,6 @@ "source-map-js": "^1.0.2" } }, - "postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-loader": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", - "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", - "requires": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.8" - }, - "dependencies": { - "cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - } - } - }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -15538,45 +15362,6 @@ "util-deprecate": "^1.0.2" } }, - "postcss-url": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", - "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", - "requires": { - "make-dir": "~3.1.0", - "mime": "~2.5.2", - "minimatch": "~3.0.4", - "xxhashjs": "~0.2.2" - }, - "dependencies": { - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - } - }, - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" - }, - "minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -15710,21 +15495,6 @@ } } }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "requires": { - "pify": "^2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" - } - } - }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -15927,7 +15697,8 @@ "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true }, "reusify": { "version": "1.0.4", @@ -17080,9 +16851,9 @@ "dev": true }, "webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", + "version": "5.76.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", + "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -17443,14 +17214,6 @@ "integrity": "sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==", "dev": true }, - "xxhashjs": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", - "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", - "requires": { - "cuint": "^0.2.2" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -17463,11 +17226,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - }, "yargs": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", diff --git a/package.json b/package.json index 2aa30f6745..afc7752257 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,9 @@ "katex": "0.16.4", "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.0.2", - "mini-css-extract-plugin": "2.7.2", + "mini-css-extract-plugin": "2.7.4", "monaco-editor": "0.34.1", "monaco-editor-webpack-plugin": "7.0.1", - "postcss-import": "15.1.0", - "postcss-loader": "7.0.2", - "postcss-url": "10.1.3", "pretty-ms": "8.0.0", "sortablejs": "1.15.0", "swagger-ui-dist": "4.15.5", @@ -46,7 +43,7 @@ "vue-bar-graph": "2.0.0", "vue-loader": "17.0.1", "vue3-calendar-heatmap": "2.0.0", - "webpack": "5.76.0", + "webpack": "5.76.2", "webpack-cli": "5.0.1", "workbox-routing": "6.5.4", "workbox-strategies": "6.5.4", diff --git a/web_src/css/themes/theme-auto.css b/web_src/css/themes/theme-auto.css index ced3ef5238..e0d31e2cfb 100644 --- a/web_src/css/themes/theme-auto.css +++ b/web_src/css/themes/theme-auto.css @@ -1,2 +1 @@ -/* This import requires postcss-import so the media query is preserved in the output */ @import "./theme-arc-green.css" (prefers-color-scheme: dark); diff --git a/webpack.config.js b/webpack.config.js index 69a306a024..ec8a195940 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -145,23 +145,10 @@ export default { loader: 'css-loader', options: { sourceMap: true, - importLoaders: 1, url: {filter: filterCssImport}, import: {filter: filterCssImport}, }, }, - { - loader: 'postcss-loader', /* for conditional import in theme-auto.css */ - options: { - sourceMap: true, - postcssOptions: { - plugins: [ - 'postcss-import', - 'postcss-url', - ], - }, - }, - }, ], }, { From 272cf6a2a976d4e22cccdaea720f48861d9b200e Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 16 Mar 2023 21:40:56 +0100 Subject: [PATCH 49/94] Make time tooltips interactive (#23526) Fixes https://codeberg.org/forgejo/forgejo/issues/511 Screenshot 2023-03-16 at 20 23 10 --- modules/timeutil/since.go | 4 ++-- web_src/js/modules/tippy.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index 295ba7c10e..53c22258c4 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -233,7 +233,7 @@ func TimeSince(then time.Time, lang translation.Locale) template.HTML { } func htmlTimeSince(then, now time.Time, lang translation.Locale) template.HTML { - return template.HTML(fmt.Sprintf(`%s`, + return template.HTML(fmt.Sprintf(`%s`, then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang.Language())), timeSince(then, now, lang))) } @@ -244,7 +244,7 @@ func TimeSinceUnix(then TimeStamp, lang translation.Locale) template.HTML { } func htmlTimeSinceUnix(then, now TimeStamp, lang translation.Locale) template.HTML { - return template.HTML(fmt.Sprintf(`%s`, + return template.HTML(fmt.Sprintf(`%s`, then.FormatInLocation(GetTimeFormat(lang.Language()), setting.DefaultUILocation), timeSinceUnix(int64(then), int64(now), lang))) } diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js index ce8f0369f1..720f8ba53b 100644 --- a/web_src/js/modules/tippy.js +++ b/web_src/js/modules/tippy.js @@ -33,6 +33,7 @@ export function initTooltip(el, props = {}) { content, delay: 100, role: 'tooltip', + ...(el.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true} : {}), ...props, }); } From e200c68bad4ea7fb8d6a76e04004857a0ade9fb3 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Fri, 17 Mar 2023 00:16:11 +0000 Subject: [PATCH 50/94] [skip ci] Updated translations via Crowdin --- options/locale/locale_zh-TW.ini | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 923d7c4c76..c478dc3134 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -821,6 +821,7 @@ remove_account_link=刪除已連結的帳戶 remove_account_link_desc=刪除連結帳戶將撤銷其對 Gitea 帳戶的存取權限。是否繼續? remove_account_link_success=已移除連結的帳戶。 +hooks.desc=這個使用者的所有存儲庫都會觸發在此新增的 Webhook。 orgs_none=您尚未成為任一組織的成員。 repos_none=您不擁有任何存儲庫 @@ -2289,6 +2290,8 @@ release.edit_subheader=發布、整理專案的版本。 release.tag_name=標籤名稱 release.target=目標分支 release.tag_helper=新增或選擇現有的標籤。 +release.tag_helper_new=新標籤,將在目標上建立此標籤。 +release.tag_helper_existing=現有的標籤。 release.title=標題 release.content=內容 release.prerelease_desc=標記為 Pre-Release @@ -2809,6 +2812,8 @@ auths.still_in_used=此認證來源正在使用中。請先轉換或刪除使用 auths.deletion_success=已刪除認證來源。 auths.login_source_exist=認證來源「%s」已經存在。 auths.login_source_of_type_exist=已經有相同類型的認證來源。 +auths.unable_to_initialize_openid=無法初始化 OpenID 連接提供者: %s +auths.invalid_openIdConnectAutoDiscoveryURL=自動探索 URL 無效 (它必須是以 http:// 或 https:// 開頭的有效 URL) config.server_config=伺服器組態 config.app_name=網站標題 @@ -2930,6 +2935,7 @@ config.git_max_diff_lines=差異比較時顯示的最多行數 (單檔) config.git_max_diff_line_characters=差異比較時顯示的最多字元數 (單行) config.git_max_diff_files=差異比較時顯示的最多檔案數 config.git_enable_reflogs=啟用 Reflogs +config.git_reflog_expiry_time=到期時間 config.git_gc_args=GC 參數 config.git_migrate_timeout=遷移逾時 config.git_mirror_timeout=鏡像更新超時 @@ -3234,6 +3240,9 @@ rubygems.required.ruby=需要的 Ruby 版本 rubygems.required.rubygems=需要的 RubyGem 版本 rubygems.documentation=關於 RubyGems registry 的詳情請參閱說明文件。 swift.registry=透過下列命令設定此註冊中心: +swift.install=將此套件加入您的 Package.swift 檔: +swift.install2=並執行下列命令: +swift.documentation=關於 Swift registry 的詳情請參閱說明文件。 vagrant.install=執行下列命令以新增 Vagrant box: vagrant.documentation=關於 Vagrant registry 的詳情請參閱說明文件。 settings.link=連結此套件到儲存庫 @@ -3351,6 +3360,8 @@ runs.open_tab=%d 開放中 runs.closed_tab=%d 已關閉 runs.commit=提交 runs.pushed_by=推送者 +runs.valid_workflow_helper=工作流程設定檔有效。 +runs.invalid_workflow_helper=工作流程設定檔無效。請檢查您的設定檔: %s need_approval_desc=來自 Frok 儲存庫的合併請求需要核可才能執行工作流程。 From 345aa0975676031b97a99cc7a449d5fd680dba77 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 17 Mar 2023 11:08:05 +0800 Subject: [PATCH 51/94] Fix aria.js bugs: incorrect role element problem, mobile focus problem, tippy problem (#23450) This PR is extracted from #23346 to address some unclear (I don't understand) code-belonging concerns. This PR needs to be backported, otherwise the `aria.js` is too buggy in some cases. Since there would be two minor conflicts, I will do the backport manually. Before: the `aria.js` is still buggy in some cases. After: tested with AppleVoice, Android TalkBack * Fix incorrect dropdown init code * Fix incorrect role element (the menu role should be on the `$menu` element, but not on the `$focusable`) * Fix the focus-show-click-hide problem on mobile. Now the language menu works as expected * Fix incorrect dropdown template function setting * Clarify the logic in aria.js * Hide item's tippy after menu gets hidden * Fix incorrect tippy `setProps` after `destroy` * Fix UI lag problem when page gets redirected during menu hiding animation with screen reader * Improve comments * Implement the layout proposed by #19861
    https://github.com/go-gitea/gitea/blob/d74a7efb60f94a4b8e6e5f65332f94f1be31b761/web_src/js/features/aria.md?plain=1#L38-L47
    --- templates/base/footer_content.tmpl | 2 +- .../repo/issue/view_content/add_reaction.tmpl | 2 +- .../repo/issue/view_content/context_menu.tmpl | 2 +- web_src/js/features/aria.js | 165 ++++++++++++------ web_src/js/features/aria.md | 81 +++++++-- web_src/js/features/common-global.js | 35 ++-- web_src/js/features/repo-legacy.js | 3 - web_src/js/modules/tippy.js | 3 +- 8 files changed, 203 insertions(+), 90 deletions(-) diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index c3b96a0245..53d0a2c77c 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -21,7 +21,7 @@ {{end}}
    + @@ -64,6 +65,7 @@ + - diff --git a/web_src/css/user.css b/web_src/css/user.css index a3379440df..8722181c29 100644 --- a/web_src/css/user.css +++ b/web_src/css/user.css @@ -90,27 +90,6 @@ padding: 8px 15px; } -.user.notification .svg { - float: left; - font-size: 2em; -} - -.user.notification .svg.green { - color: var(--color-green); -} - -.user.notification .svg.red { - color: var(--color-red); -} - -.user.notification .svg.purple { - color: var(--color-purple); -} - -.user.notification .svg.blue { - color: var(--color-blue); -} - .user.notification .content { float: left; margin-left: 7px; @@ -175,4 +154,13 @@ #notification_div .tab.segment { overflow-x: auto; + padding: 0; +} + +#notification_div .menu .active.item { + background: var(--color-box-body); +} + +#notification_table { + border: none; } From ce9dee5a1e8ae670c97621bca409d8cf43a90102 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 22 Mar 2023 04:02:49 +0800 Subject: [PATCH 87/94] Introduce path Clean/Join helper functions (#23495) Since #23493 has conflicts with latest commits, this PR is my proposal for fixing #23371 Details are in the comments And refactor the `modules/options` module, to make it always use "filepath" to access local files. Benefits: * No need to do `util.CleanPath(strings.ReplaceAll(p, "\\", "/"))), "/")` any more (not only one before) * The function behaviors are clearly defined --- models/git/lfs_lock.go | 6 +- modules/options/base.go | 67 +++++++++++--- modules/options/dynamic.go | 48 ++-------- modules/options/static.go | 39 +++----- modules/public/public.go | 26 ++---- modules/storage/local.go | 17 ++-- modules/storage/local_test.go | 20 ++--- modules/storage/minio.go | 2 +- modules/util/path.go | 94 +++++++++++++++++--- modules/util/path_test.go | 74 +++++++++++++-- routers/web/base.go | 6 +- routers/web/repo/editor.go | 2 +- routers/web/repo/lfs.go | 2 +- services/migrations/gitea_uploader.go | 4 +- services/packages/container/blob_uploader.go | 4 +- services/repository/files/file.go | 2 +- 16 files changed, 261 insertions(+), 152 deletions(-) diff --git a/models/git/lfs_lock.go b/models/git/lfs_lock.go index 178fa72f09..261c73032a 100644 --- a/models/git/lfs_lock.go +++ b/models/git/lfs_lock.go @@ -34,7 +34,7 @@ func init() { // BeforeInsert is invoked from XORM before inserting an object of this type. func (l *LFSLock) BeforeInsert() { - l.Path = util.CleanPath(l.Path) + l.Path = util.PathJoinRel(l.Path) } // CreateLFSLock creates a new lock. @@ -49,7 +49,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo return nil, err } - lock.Path = util.CleanPath(lock.Path) + lock.Path = util.PathJoinRel(lock.Path) lock.RepoID = repo.ID l, err := GetLFSLock(dbCtx, repo, lock.Path) @@ -69,7 +69,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo // GetLFSLock returns release by given path. func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (*LFSLock, error) { - path = util.CleanPath(path) + path = util.PathJoinRel(path) rel := &LFSLock{RepoID: repo.ID} has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel) if err != nil { diff --git a/modules/options/base.go b/modules/options/base.go index e83e8df5d0..7882ed0081 100644 --- a/modules/options/base.go +++ b/modules/options/base.go @@ -7,36 +7,38 @@ import ( "fmt" "io/fs" "os" - "path" "path/filepath" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) +var directories = make(directorySet) + // Locale reads the content of a specific locale from static/bindata or custom path. func Locale(name string) ([]byte, error) { - return fileFromDir(path.Join("locale", util.CleanPath(name))) + return fileFromOptionsDir("locale", name) } // Readme reads the content of a specific readme from static/bindata or custom path. func Readme(name string) ([]byte, error) { - return fileFromDir(path.Join("readme", util.CleanPath(name))) + return fileFromOptionsDir("readme", name) } // Gitignore reads the content of a gitignore locale from static/bindata or custom path. func Gitignore(name string) ([]byte, error) { - return fileFromDir(path.Join("gitignore", util.CleanPath(name))) + return fileFromOptionsDir("gitignore", name) } // License reads the content of a specific license from static/bindata or custom path. func License(name string) ([]byte, error) { - return fileFromDir(path.Join("license", util.CleanPath(name))) + return fileFromOptionsDir("license", name) } // Labels reads the content of a specific labels from static/bindata or custom path. func Labels(name string) ([]byte, error) { - return fileFromDir(path.Join("label", util.CleanPath(name))) + return fileFromOptionsDir("label", name) } // WalkLocales reads the content of a specific locale @@ -79,17 +81,54 @@ func walkAssetDir(root string, callback func(path, name string, d fs.DirEntry, e return nil } -func statDirIfExist(dir string) ([]string, error) { - isDir, err := util.IsDir(dir) +// mustLocalPathAbs coverts a path to absolute path +// FIXME: the old behavior (StaticRootPath might not be absolute), not ideal, just keep the same as before +func mustLocalPathAbs(s string) string { + abs, err := filepath.Abs(s) if err != nil { - return nil, fmt.Errorf("unable to check if static directory %s is a directory. %w", dir, err) + // This should never happen in a real system. If it happens, the user must have already been in trouble: the system is not able to resolve its own paths. + log.Fatal("Unable to get absolute path for %q: %v", s, err) } - if !isDir { - return nil, nil + return abs +} + +func joinLocalPaths(baseDirs []string, subDir string, elems ...string) (paths []string) { + abs := make([]string, len(elems)+2) + abs[1] = subDir + copy(abs[2:], elems) + for _, baseDir := range baseDirs { + abs[0] = mustLocalPathAbs(baseDir) + paths = append(paths, util.FilePathJoinAbs(abs...)) } - files, err := util.StatDir(dir, true) - if err != nil { - return nil, fmt.Errorf("unable to read directory %q. %w", dir, err) + return paths +} + +func listLocalDirIfExist(baseDirs []string, subDir string, elems ...string) (files []string, err error) { + for _, localPath := range joinLocalPaths(baseDirs, subDir, elems...) { + isDir, err := util.IsDir(localPath) + if err != nil { + return nil, fmt.Errorf("unable to check if path %q is a directory. %w", localPath, err) + } else if !isDir { + continue + } + + dirFiles, err := util.StatDir(localPath, true) + if err != nil { + return nil, fmt.Errorf("unable to read directory %q. %w", localPath, err) + } + files = append(files, dirFiles...) } return files, nil } + +func readLocalFile(baseDirs []string, subDir string, elems ...string) ([]byte, error) { + for _, localPath := range joinLocalPaths(baseDirs, subDir, elems...) { + data, err := os.ReadFile(localPath) + if err == nil { + return data, nil + } else if !os.IsNotExist(err) { + log.Error("Unable to read file %q. Error: %v", localPath, err) + } + } + return nil, os.ErrNotExist +} diff --git a/modules/options/dynamic.go b/modules/options/dynamic.go index 8c954492ae..3d6261983f 100644 --- a/modules/options/dynamic.go +++ b/modules/options/dynamic.go @@ -6,62 +6,26 @@ package options import ( - "fmt" - "os" - "path" - - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) -var directories = make(directorySet) - // Dir returns all files from static or custom directory. func Dir(name string) ([]string, error) { if directories.Filled(name) { return directories.Get(name), nil } - var result []string - - for _, dir := range []string{ - path.Join(setting.CustomPath, "options", name), // custom dir - path.Join(setting.StaticRootPath, "options", name), // static dir - } { - files, err := statDirIfExist(dir) - if err != nil { - return nil, err - } - result = append(result, files...) + result, err := listLocalDirIfExist([]string{setting.CustomPath, setting.StaticRootPath}, "options", name) + if err != nil { + return nil, err } return directories.AddAndGet(name, result), nil } -// fileFromDir is a helper to read files from static or custom path. -func fileFromDir(name string) ([]byte, error) { - customPath := path.Join(setting.CustomPath, "options", name) - - isFile, err := util.IsFile(customPath) - if err != nil { - log.Error("Unable to check if %s is a file. Error: %v", customPath, err) - } - if isFile { - return os.ReadFile(customPath) - } - - staticPath := path.Join(setting.StaticRootPath, "options", name) - - isFile, err = util.IsFile(staticPath) - if err != nil { - log.Error("Unable to check if %s is a file. Error: %v", staticPath, err) - } - if isFile { - return os.ReadFile(staticPath) - } - - return []byte{}, fmt.Errorf("Asset file does not exist: %s", name) +// fileFromOptionsDir is a helper to read files from custom or static path. +func fileFromOptionsDir(elems ...string) ([]byte, error) { + return readLocalFile([]string{setting.CustomPath, setting.StaticRootPath}, "options", elems...) } // IsDynamic will return false when using embedded data (-tags bindata) diff --git a/modules/options/static.go b/modules/options/static.go index 549f4e25b1..0482dea681 100644 --- a/modules/options/static.go +++ b/modules/options/static.go @@ -8,33 +8,20 @@ package options import ( "fmt" "io" - "os" - "path" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) -var directories = make(directorySet) - -// Dir returns all files from bindata or custom directory. +// Dir returns all files from custom directory or bindata. func Dir(name string) ([]string, error) { if directories.Filled(name) { return directories.Get(name), nil } - var result []string - - for _, dir := range []string{ - path.Join(setting.CustomPath, "options", name), // custom dir - // no static dir - } { - files, err := statDirIfExist(dir) - if err != nil { - return nil, err - } - result = append(result, files...) + result, err := listLocalDirIfExist([]string{setting.CustomPath}, "options", name) + if err != nil { + return nil, err } files, err := AssetDir(name) @@ -64,24 +51,18 @@ func AssetDir(dirName string) ([]string, error) { return results, nil } -// fileFromDir is a helper to read files from bindata or custom path. -func fileFromDir(name string) ([]byte, error) { - customPath := path.Join(setting.CustomPath, "options", name) - - isFile, err := util.IsFile(customPath) - if err != nil { - log.Error("Unable to check if %s is a file. Error: %v", customPath, err) - } - if isFile { - return os.ReadFile(customPath) +// fileFromOptionsDir is a helper to read files from custom path or bindata. +func fileFromOptionsDir(elems ...string) ([]byte, error) { + // only try custom dir, no static dir + if data, err := readLocalFile([]string{setting.CustomPath}, "options", elems...); err == nil { + return data, nil } - f, err := Assets.Open(name) + f, err := Assets.Open(util.PathJoinRelX(elems...)) if err != nil { return nil, err } defer f.Close() - return io.ReadAll(f) } diff --git a/modules/public/public.go b/modules/public/public.go index e1d60d89eb..30b03a2795 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -45,29 +45,19 @@ func AssetsHandlerFunc(opts *Options) http.HandlerFunc { return } - file := req.URL.Path - file = file[len(opts.Prefix):] - if len(file) == 0 { - resp.WriteHeader(http.StatusNotFound) - return - } - if strings.Contains(file, "\\") { - resp.WriteHeader(http.StatusBadRequest) - return - } - file = "/" + file - - var written bool + var corsSent bool if opts.CorsHandler != nil { - written = true opts.CorsHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { - written = false + corsSent = true })).ServeHTTP(resp, req) } - if written { + // If CORS is not sent, the response must have been written by other handlers + if !corsSent { return } + file := req.URL.Path[len(opts.Prefix):] + // custom files if opts.handle(resp, req, http.Dir(custPath), file) { return @@ -102,8 +92,8 @@ func setWellKnownContentType(w http.ResponseWriter, file string) { } func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool { - // use clean to keep the file is a valid path with no . or .. - f, err := fs.Open(util.CleanPath(file)) + // actually, fs (http.FileSystem) is designed to be a safe interface, relative paths won't bypass its parent directory, it's also fine to do a clean here + f, err := fs.Open(util.PathJoinRelX(file)) if err != nil { if os.IsNotExist(err) { return false diff --git a/modules/storage/local.go b/modules/storage/local.go index 15f5761e8f..d22974a65a 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -5,11 +5,11 @@ package storage import ( "context" + "fmt" "io" "net/url" "os" "path/filepath" - "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" @@ -41,13 +41,19 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error } config := configInterface.(LocalStorageConfig) + if !filepath.IsAbs(config.Path) { + return nil, fmt.Errorf("LocalStorageConfig.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path) + } log.Info("Creating new Local Storage at %s", config.Path) if err := os.MkdirAll(config.Path, os.ModePerm); err != nil { return nil, err } if config.TemporaryPath == "" { - config.TemporaryPath = config.Path + "/tmp" + config.TemporaryPath = filepath.Join(config.Path, "tmp") + } + if !filepath.IsAbs(config.TemporaryPath) { + return nil, fmt.Errorf("LocalStorageConfig.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath) } return &LocalStorage{ @@ -58,7 +64,7 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error } func (l *LocalStorage) buildLocalPath(p string) string { - return filepath.Join(l.dir, util.CleanPath(strings.ReplaceAll(p, "\\", "/"))) + return util.FilePathJoinAbs(l.dir, p) } // Open a file @@ -128,10 +134,7 @@ func (l *LocalStorage) URL(path, name string) (*url.URL, error) { // IterateObjects iterates across the objects in the local storage func (l *LocalStorage) IterateObjects(prefix string, fn func(path string, obj Object) error) error { - dir := l.dir - if prefix != "" { - dir = filepath.Join(l.dir, util.CleanPath(prefix)) - } + dir := l.buildLocalPath(prefix) return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil { return err diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go index 2b112df8f1..9649761a0f 100644 --- a/modules/storage/local_test.go +++ b/modules/storage/local_test.go @@ -20,29 +20,29 @@ func TestBuildLocalPath(t *testing.T) { expected string }{ { - "a", + "/a", "0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", - "a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", + "/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", }, { - "a", + "/a", "../0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", - "a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", + "/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", }, { - "a", + "/a", "0\\a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", - "a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", + "/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", }, { - "b", + "/b", "a/../0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", - "b/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", + "/b/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", }, { - "b", + "/b", "a\\..\\0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", - "b/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", + "/b/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14", }, } diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 8cc06bcdd3..5c67dbf26a 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -121,7 +121,7 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error } func (m *MinioStorage) buildMinioPath(p string) string { - return strings.TrimPrefix(path.Join(m.basePath, util.CleanPath(strings.ReplaceAll(p, "\\", "/"))), "/") + return util.PathJoinRelX(m.basePath, p) } // Open open a file diff --git a/modules/util/path.go b/modules/util/path.go index 5aa9e15f5c..37d06e9813 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -5,6 +5,7 @@ package util import ( "errors" + "fmt" "net/url" "os" "path" @@ -14,21 +15,92 @@ import ( "strings" ) -// CleanPath ensure to clean the path -func CleanPath(p string) string { - if strings.HasPrefix(p, "/") { - return path.Clean(p) +// PathJoinRel joins the path elements into a single path, each element is cleaned by path.Clean separately. +// It only returns the following values (like path.Join), any redundant part (empty, relative dots, slashes) is removed. +// It's caller's duty to make every element not bypass its own directly level, to avoid security issues. +// +// empty => `` +// `` => `` +// `..` => `.` +// `dir` => `dir` +// `/dir/` => `dir` +// `foo\..\bar` => `foo\..\bar` +// {`foo`, ``, `bar`} => `foo/bar` +// {`foo`, `..`, `bar`} => `foo/bar` +func PathJoinRel(elem ...string) string { + elems := make([]string, len(elem)) + for i, e := range elem { + if e == "" { + continue + } + elems[i] = path.Clean("/" + e) + } + p := path.Join(elems...) + if p == "" { + return "" + } else if p == "/" { + return "." + } else { + return p[1:] } - return path.Clean("/" + p)[1:] } -// EnsureAbsolutePath ensure that a path is absolute, making it -// relative to absoluteBase if necessary -func EnsureAbsolutePath(path, absoluteBase string) string { - if filepath.IsAbs(path) { - return path +// PathJoinRelX joins the path elements into a single path like PathJoinRel, +// and covert all backslashes to slashes. (X means "extended", also means the combination of `\` and `/`). +// It's caller's duty to make every element not bypass its own directly level, to avoid security issues. +// It returns similar results as PathJoinRel except: +// +// `foo\..\bar` => `bar` (because it's processed as `foo/../bar`) +// +// All backslashes are handled as slashes, the result only contains slashes. +func PathJoinRelX(elem ...string) string { + elems := make([]string, len(elem)) + for i, e := range elem { + if e == "" { + continue + } + elems[i] = path.Clean("/" + strings.ReplaceAll(e, "\\", "/")) } - return filepath.Join(absoluteBase, path) + return PathJoinRel(elems...) +} + +const pathSeparator = string(os.PathSeparator) + +// FilePathJoinAbs joins the path elements into a single file path, each element is cleaned by filepath.Clean separately. +// All slashes/backslashes are converted to path separators before cleaning, the result only contains path separators. +// The first element must be an absolute path, caller should prepare the base path. +// It's caller's duty to make every element not bypass its own directly level, to avoid security issues. +// Like PathJoinRel, any redundant part (empty, relative dots, slashes) is removed. +// +// {`/foo`, ``, `bar`} => `/foo/bar` +// {`/foo`, `..`, `bar`} => `/foo/bar` +func FilePathJoinAbs(elem ...string) string { + elems := make([]string, len(elem)) + + // POISX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators + // to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/` + if isOSWindows() { + elems[0] = filepath.Clean(elem[0]) + } else { + elems[0] = filepath.Clean(strings.ReplaceAll(elem[0], "\\", pathSeparator)) + } + if !filepath.IsAbs(elems[0]) { + // This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead + panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", elems[0], elems)) + } + + for i := 1; i < len(elem); i++ { + if elem[i] == "" { + continue + } + if isOSWindows() { + elems[i] = filepath.Clean(pathSeparator + elem[i]) + } else { + elems[i] = filepath.Clean(pathSeparator + strings.ReplaceAll(elem[i], "\\", pathSeparator)) + } + } + // the elems[0] must be an absolute path, just join them together + return filepath.Join(elems...) } // IsDir returns true if given path is a directory, diff --git a/modules/util/path_test.go b/modules/util/path_test.go index 2f020f924d..1d27c9bf0c 100644 --- a/modules/util/path_test.go +++ b/modules/util/path_test.go @@ -138,13 +138,75 @@ func TestMisc_IsReadmeFileName(t *testing.T) { } func TestCleanPath(t *testing.T) { - cases := map[string]string{ - "../../test": "test", - "/test": "/test", - "/../test": "/test", + cases := []struct { + elems []string + expected string + }{ + {[]string{}, ``}, + {[]string{``}, ``}, + {[]string{`..`}, `.`}, + {[]string{`a`}, `a`}, + {[]string{`/a/`}, `a`}, + {[]string{`../a/`, `../b`, `c/..`, `d`}, `a/b/d`}, + {[]string{`a\..\b`}, `a\..\b`}, + {[]string{`a`, ``, `b`}, `a/b`}, + {[]string{`a`, `..`, `b`}, `a/b`}, + {[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`}, + } + for _, c := range cases { + assert.Equal(t, c.expected, PathJoinRel(c.elems...), "case: %v", c.elems) } - for k, v := range cases { - assert.Equal(t, v, CleanPath(k)) + cases = []struct { + elems []string + expected string + }{ + {[]string{}, ``}, + {[]string{``}, ``}, + {[]string{`..`}, `.`}, + {[]string{`a`}, `a`}, + {[]string{`/a/`}, `a`}, + {[]string{`../a/`, `../b`, `c/..`, `d`}, `a/b/d`}, + {[]string{`a\..\b`}, `b`}, + {[]string{`a`, ``, `b`}, `a/b`}, + {[]string{`a`, `..`, `b`}, `a/b`}, + {[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`}, + } + for _, c := range cases { + assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems) + } + + // for POSIX only, but the result is similar on Windows, because the first element must be an absolute path + if isOSWindows() { + cases = []struct { + elems []string + expected string + }{ + {[]string{`C:\..`}, `C:\`}, + {[]string{`C:\a`}, `C:\a`}, + {[]string{`C:\a/`}, `C:\a`}, + {[]string{`C:\..\a\`, `../b`, `c\..`, `d`}, `C:\a\b\d`}, + {[]string{`C:\a/..\b`}, `C:\b`}, + {[]string{`C:\a`, ``, `b`}, `C:\a\b`}, + {[]string{`C:\a`, `..`, `b`}, `C:\a\b`}, + {[]string{`C:\lfs`, `repo/..`, `user/../path`}, `C:\lfs\path`}, + } + } else { + cases = []struct { + elems []string + expected string + }{ + {[]string{`/..`}, `/`}, + {[]string{`/a`}, `/a`}, + {[]string{`/a/`}, `/a`}, + {[]string{`/../a/`, `../b`, `c/..`, `d`}, `/a/b/d`}, + {[]string{`/a\..\b`}, `/b`}, + {[]string{`/a`, ``, `b`}, `/a/b`}, + {[]string{`/a`, `..`, `b`}, `/a/b`}, + {[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`}, + } + } + for _, c := range cases { + assert.Equal(t, c.expected, FilePathJoinAbs(c.elems...), "case: %v", c.elems) } } diff --git a/routers/web/base.go b/routers/web/base.go index 2eb0b6f391..da18a75643 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -45,7 +45,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor routing.UpdateFuncInfo(req.Context(), funcInfo) rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/") - rPath = util.CleanPath(strings.ReplaceAll(rPath, "\\", "/")) + rPath = util.PathJoinRelX(rPath) u, err := objStore.URL(rPath, path.Base(rPath)) if err != nil { @@ -81,8 +81,8 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor routing.UpdateFuncInfo(req.Context(), funcInfo) rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/") - rPath = util.CleanPath(strings.ReplaceAll(rPath, "\\", "/")) - if rPath == "" { + rPath = util.PathJoinRelX(rPath) + if rPath == "" || rPath == "." { http.Error(w, "file not found", http.StatusNotFound) return } diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 4f208098e4..07241b8870 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -726,7 +726,7 @@ func UploadFilePost(ctx *context.Context) { func cleanUploadFileName(name string) string { // Rebase the filename - name = strings.Trim(util.CleanPath(name), "/") + name = util.PathJoinRel(name) // Git disallows any filenames to have a .git directory in them. for _, part := range strings.Split(name, "/") { if strings.ToLower(part) == ".git" { diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go index 43f5527986..9957869c99 100644 --- a/routers/web/repo/lfs.go +++ b/routers/web/repo/lfs.go @@ -207,7 +207,7 @@ func LFSLockFile(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") return } - lockPath = util.CleanPath(lockPath) + lockPath = util.PathJoinRel(lockPath) if len(lockPath) == 0 { ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath)) ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index ca961524d1..0eb34b5fe5 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -865,8 +865,8 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { _, _, line, _ = git.ParseDiffHunkString(comment.DiffHunk) } - // SECURITY: The TreePath must be cleaned! - comment.TreePath = util.CleanPath(comment.TreePath) + // SECURITY: The TreePath must be cleaned! use relative path + comment.TreePath = util.PathJoinRel(comment.TreePath) var patch string reader, writer := io.Pipe() diff --git a/services/packages/container/blob_uploader.go b/services/packages/container/blob_uploader.go index 860672587d..bae2e2d6af 100644 --- a/services/packages/container/blob_uploader.go +++ b/services/packages/container/blob_uploader.go @@ -8,8 +8,6 @@ import ( "errors" "io" "os" - "path/filepath" - "strings" packages_model "code.gitea.io/gitea/models/packages" packages_module "code.gitea.io/gitea/modules/packages" @@ -33,7 +31,7 @@ type BlobUploader struct { } func buildFilePath(id string) string { - return filepath.Join(setting.Packages.ChunkedUploadPath, util.CleanPath(strings.ReplaceAll(id, "\\", "/"))) + return util.FilePathJoinAbs(setting.Packages.ChunkedUploadPath, id) } // NewBlobUploader creates a new blob uploader for the given id diff --git a/services/repository/files/file.go b/services/repository/files/file.go index 7939491aec..dc1e547dcd 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -129,7 +129,7 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m // CleanUploadFileName Trims a filename and returns empty string if it is a .git directory func CleanUploadFileName(name string) string { // Rebase the filename - name = strings.Trim(util.CleanPath(name), "/") + name = util.PathJoinRel(name) // Git disallows any filenames to have a .git directory in them. for _, part := range strings.Split(name, "/") { if strings.ToLower(part) == ".git" { From 9b0190884d88ad15cf67e1288a302d22e88d37aa Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 22 Mar 2023 04:35:02 +0800 Subject: [PATCH 88/94] Remove `id="comment-form"` dead code, fix tag (#23555) The code has been dead code since #5073 . `#5073` duplicated the code in a new `if` block. The dead code blocks * #23290 --- templates/repo/issue/view_content.tmpl | 68 ++++++-------------------- 1 file changed, 15 insertions(+), 53 deletions(-) diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 1b3cbdbec2..3e43701b63 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -17,15 +17,15 @@ {{$createdStr:= TimeSinceUnix .Issue.CreatedUnix $.locale}}
    - +
    - {{if .Issue.OriginalAuthor}} + {{if .Issue.OriginalAuthor}} - {{else}} + {{else}} {{avatar $.Context .Issue.Poster}} - {{end}} + {{end}}
    @@ -97,6 +97,7 @@ {{if and .Issue.IsPull (not $.Repository.IsArchived)}} {{template "repo/issue/view_content/pull".}} {{end}} + {{if .IsSigned}} {{if and (or .IsRepoAdmin .HasIssuesOrPullsWritePermission (not .Issue.IsLocked)) (not .Repository.IsArchived)}}
    @@ -142,61 +143,22 @@ {{end}}
    {{end}} - {{else}} - {{if .Repository.IsArchived}} -
    - {{if .Issue.IsPull}} - {{.locale.Tr "repo.archive.pull.nocomment"}} - {{else}} - {{.locale.Tr "repo.archive.issue.nocomment"}} - {{end}} -
    - {{else}} - {{if .IsSigned}} - {{if .Repository.IsArchived}} -
    - - {{avatar $.Context .SignedUser}} - -
    -
    - {{template "repo/issue/comment_tab" .}} - {{.CsrfTokenHtml}} - - - -
    + {{else}} {{/* not .IsSigned */}} + {{if .Repository.IsArchived}} +
    + {{if .Issue.IsPull}} + {{.locale.Tr "repo.archive.pull.nocomment"}} + {{else}} + {{.locale.Tr "repo.archive.issue.nocomment"}} + {{end}}
    - {{end}} {{else}}
    {{.locale.Tr "repo.issues.sign_in_require_desc" (.SignInLink|Escape) | Safe}}
    {{end}} - {{end}} - {{end}} - + {{end}}{{/* end if: .IsSigned */}} +
    {{template "repo/issue/view_content/sidebar" .}} From 76a1edf74f5f7fdf733b46fde12df644295e9fe8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 22 Mar 2023 05:04:17 +0800 Subject: [PATCH 89/94] Decouple the issue-template code from comment_tab.tmpl (#23556) It would help #23290 The issue-template code is only useful for "new issue" or "new PR", so it could only be put in the `new_form.tmpl` --- templates/repo/issue/comment_tab.tmpl | 17 ----------------- templates/repo/issue/new_form.tmpl | 24 +++++++++++++++++++++++- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl index b04a3c6bbb..b8e8d2d9aa 100644 --- a/templates/repo/issue/comment_tab.tmpl +++ b/templates/repo/issue/comment_tab.tmpl @@ -1,19 +1,3 @@ -{{if .Fields}} - - {{range .Fields}} - {{if eq .Type "input"}} - {{template "repo/issue/fields/input" Dict "Context" $.Context "item" .}} - {{else if eq .Type "markdown"}} - {{template "repo/issue/fields/markdown" Dict "Context" $.Context "item" .}} - {{else if eq .Type "textarea"}} - {{template "repo/issue/fields/textarea" Dict "Context" $.Context "item" .}} - {{else if eq .Type "dropdown"}} - {{template "repo/issue/fields/dropdown" Dict "Context" $.Context "item" .}} - {{else if eq .Type "checkboxes"}} - {{template "repo/issue/fields/checkboxes" Dict "Context" $.Context "item" .}} - {{end}} - {{end}} -{{else}}
    -{{end}} {{if .IsAttachmentEnabled}}
    {{template "repo/upload" .}} diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 4945203ca5..f6ef8e6754 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -16,7 +16,29 @@
    {{.locale.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}
    {{end}}
    - {{template "repo/issue/comment_tab" .}} + {{if .Fields}} + + {{range .Fields}} + {{if eq .Type "input"}} + {{template "repo/issue/fields/input" Dict "Context" $.Context "item" .}} + {{else if eq .Type "markdown"}} + {{template "repo/issue/fields/markdown" Dict "Context" $.Context "item" .}} + {{else if eq .Type "textarea"}} + {{template "repo/issue/fields/textarea" Dict "Context" $.Context "item" .}} + {{else if eq .Type "dropdown"}} + {{template "repo/issue/fields/dropdown" Dict "Context" $.Context "item" .}} + {{else if eq .Type "checkboxes"}} + {{template "repo/issue/fields/checkboxes" Dict "Context" $.Context "item" .}} + {{end}} + {{end}} + {{if .IsAttachmentEnabled}} +
    + {{template "repo/upload" .}} +
    + {{end}} + {{else}} + {{template "repo/issue/comment_tab" .}} + {{end}}
    {{.locale.Tr "actions.runners.status"}} {{.locale.Tr "actions.runners.id"}} {{.locale.Tr "actions.runners.name"}}{{.locale.Tr "actions.runners.version"}} {{.locale.Tr "actions.runners.owner_type"}} {{.locale.Tr "actions.runners.labels"}} {{.locale.Tr "actions.runners.last_online"}} {{.ID}}

    {{.Name}}

    {{.Version}} {{.OwnType}} {{range .AllLabels}}{{.}}{{end}} From ccd3a55bf4f07a390e13d48100c58fb937fd3dcf Mon Sep 17 00:00:00 2001 From: delvh Date: Mon, 20 Mar 2023 08:42:23 +0100 Subject: [PATCH 76/94] Add CHANGELOG for 1.19.0 (#23583) Co-authored-by: silverwind Co-authored-by: Lauris BH Co-authored-by: techknowlogick Co-authored-by: Lunny Xiao --- CHANGELOG.md | 350 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 507722f31b..324b0cdfd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,356 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.19.0](https://github.com/go-gitea/gitea/releases/tag/1.19.0) - 2023-03-19 + +* BREAKING + * Add loading yaml label template files (#22976) (#23232) + * Make issue and code search support camel case for Bleve (#22829) + * Repositories: by default disable all units except code and pulls on forks (#22541) + * Support template for merge message description (#22248) + * Remove ONLY_SHOW_RELEVANT_REPOS setting (#21962) + * Implement actions (#21937) + * Remove deprecated DSA host key from Docker Container (#21522) + * Improve valid user name check (#20136) +* SECURITY + * Return 404 instead of 403 if user can not access the repo (#23155) (#23158) + * Support scoped access tokens (#20908) +* FEATURES + * Add support for commit cross references (#22645) + * Scoped labels (#22585) + * Add Chef package registry (#22554) + * Support asciicast files as new markup (#22448) + * cgo cross-compile for freebsd (#22397) + * Add cron method to gc LFS MetaObjects (#22385) + * Add new captcha: cloudflare turnstile (#22369) + * Enable `@`- completion popup on the release description textarea (#22359) + * make /{username}.png redirect to user/org avatar (#22356) + * Add Conda package registry (#22262) + * Support org/user level projects (#22235) + * Add Mermaid copy button (#22225) + * Add user secrets (#22191) + * Secrets storage with SecretKey encrypted (#22142) + * Preview images for Issue cards in Project Board view (#22112) + * Add support for incoming emails (#22056) + * Add Cargo package registry (#21888) + * Add option to prohibit fork if user reached maximum limit of repositories (#21848) + * Add attention blocks within quote blocks for `Note` and `Warning` (#21711) + * Add Feed for Releases and Tags (#21696) + * Add package registry cleanup rules (#21658) + * Add "Copy" button to file view of raw text (#21629) + * Allow disable sitemap (#21617) + * Add package registry quota limits (#21584) + * Map OIDC groups to Orgs/Teams (#21441) + * Keep languages defined in .gitattributes (#21403) + * Add Webhook authorization header (#20926) + * Supports wildcard protected branch (#20825) + * Copy citation file content, in APA and BibTex format, on repo home page (#19999) +* API + * Match api migration behavior to web behavior (#23552) (#23573) + * Purge API comment (#23451) (#23452) + * User creation API: allow custom "created" timestamps (#22549) + * Add `updated_at` field to PullReview API object (#21812) + * Add API management for issue/pull and comment attachments (#21783) + * Add API endpoint to get latest release (#21267) + * Support system hook API (#14537) +* ENHANCEMENTS + * Add `.patch` to `attachment.ALLOWED_TYPES` (#23580) (#23582) + * Fix sticky header in diff view (#23554) (#23568) + * Refactor merge/update git command calls (#23366) (#23544) + * Fix review comment context menu clipped bug (#23523) (#23543) + * Imrove scroll behavior to hash issuecomment(scroll position, auto expand if file is folded, and on refreshing) (#23513) (#23540) + * Increase horizontal page padding (#23507) (#23537) + * Use octicon-verified for gpg signatures (#23529) (#23536) + * Make time tooltips interactive (#23526) (#23527) + * Replace Less with CSS (#23508) + * Fix 'View File' button in code search (#23478) (#23483) + * Convert GitHub event on actions and fix some pull_request events. (#23037) (#23471) + * Support reflogs (#22451) (#23438) + * Fix actions frontend bugs (pagination, long name alignment) and small simplify (#23370) (#23436) + * Scoped label display and documentation tweaks (#23430) (#23433) + * Add missing tabs to org projects page (#22705) (#23412) + * Fix and move "Use this template" button (#23398) (#23408) + * Handle OpenID discovery URL errors a little nicer when creating/editing sources (#23397) (#23403) + * Rename `canWriteUnit` to `canWriteProjects` (#23386) (#23399) + * Refactor and tidy-up the merge/update branch code (#22568) (#23365) + * Refactor `setting.Database.UseXXX` to methods (#23354) (#23356) + * Fix incorrect project links and use symlink icon for org-wide projects (#23325) (#23336) + * Fix PR view misalignment caused by long name file (#23321) (#23335) + * Scoped labels: don't require holding alt key to remove (#23303) (#23331) + * Add context when rendering labels or emojis (#23281) (#23319) + * Change interactiveBorder to fix popup preview (#23169) (#23314) + * Scoped labels: set aria-disabled on muted Exclusive option for a11y (#23306) (#23311) + * update to mermaid v10 (#23178) (#23299) + * Fix code wrap for unbroken lines (#23268) (#23293) + * Use async await to fix empty quote reply at first time (#23168) (#23256) + * Fix switched citation format (#23250) (#23253) + * Allow `
    + {{if eq .Status 3}} - {{svg "octicon-pin"}} + {{svg "octicon-pin" 16 "text blue"}} {{else if not $issue}} - {{svg "octicon-repo"}} + {{svg "octicon-repo" 16 "text grey"}} {{else if $issue.IsPull}} {{if $issue.IsClosed}} {{if $issue.GetPullRequest.HasMerged}} - {{svg "octicon-git-merge"}} + {{svg "octicon-git-merge" 16 "text purple"}} {{else}} - {{svg "octicon-git-pull-request"}} + {{svg "octicon-git-pull-request" 16 "text red"}} {{end}} {{else}} - {{svg "octicon-git-pull-request"}} + {{svg "octicon-git-pull-request" 16 "text green"}} {{end}} {{else}} {{if $issue.IsClosed}} - {{svg "octicon-issue-closed"}} + {{svg "octicon-issue-closed" 16 "text red"}} {{else}} - {{svg "octicon-issue-opened"}} + {{svg "octicon-issue-opened" 16 "text green"}} {{end}} {{end}}