0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-14 00:01:05 +02:00

Merge branch 'main' into fix-24635

Signed-off-by: Excellencedev <ademiluyisuccessandexcellence@gmail.com>
This commit is contained in:
Excellencedev 2026-01-16 14:55:05 +01:00 committed by GitHub
commit f7fc8792d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
110 changed files with 2702 additions and 1720 deletions

View File

@ -13,7 +13,7 @@
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {}, "ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}, "ghcr.io/jsburckhardt/devcontainer-features/uv:1": {},
"ghcr.io/devcontainers/features/python:1": { "ghcr.io/devcontainers/features/python:1": {
"version": "3.13" "version": "3.14"
}, },
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
}, },

4
.github/labeler.yml vendored
View File

@ -46,7 +46,7 @@ modifies/internal:
- ".gitpod.yml" - ".gitpod.yml"
- ".markdownlint.yaml" - ".markdownlint.yaml"
- ".spectral.yaml" - ".spectral.yaml"
- "stylelint.config.ts" - "stylelint.config.*"
- ".yamllint.yaml" - ".yamllint.yaml"
- ".github/**" - ".github/**"
- ".gitea/**" - ".gitea/**"
@ -89,4 +89,4 @@ topic/code-linting:
- ".markdownlint.yaml" - ".markdownlint.yaml"
- ".spectral.yaml" - ".spectral.yaml"
- ".yamllint.yaml" - ".yamllint.yaml"
- "stylelint.config.ts" - "stylelint.config.*"

View File

@ -20,7 +20,7 @@ jobs:
- run: make generate-gitignore - run: make generate-gitignore
timeout-minutes: 40 timeout-minutes: 40
- name: push translations to repo - name: push translations to repo
uses: appleboy/git-push-action@v1.0.0 uses: appleboy/git-push-action@v1.2.0
with: with:
author_email: "teabot@gitea.io" author_email: "teabot@gitea.io"
author_name: GiteaBot author_name: GiteaBot

View File

@ -29,7 +29,7 @@ jobs:
- name: update locales - name: update locales
run: ./build/update-locales.sh run: ./build/update-locales.sh
- name: push translations to repo - name: push translations to repo
uses: appleboy/git-push-action@v1.0.0 uses: appleboy/git-push-action@v1.2.0
with: with:
author_email: "teabot@gitea.io" author_email: "teabot@gitea.io"
author_name: GiteaBot author_name: GiteaBot

View File

@ -39,7 +39,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7 - uses: astral-sh/setup-uv@v7
- run: uv python install 3.12 - run: uv python install 3.14
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6 - uses: actions/setup-node@v6
with: with:
@ -59,7 +59,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7 - uses: astral-sh/setup-uv@v7
- run: uv python install 3.12 - run: uv python install 3.14
- run: make deps-py - run: make deps-py
- run: make lint-yaml - run: make lint-yaml

View File

@ -48,6 +48,8 @@ linters:
desc: do not use the ini package, use gitea's config system instead desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache - pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system desc: do not use the go-chi cache package, use gitea's cache system
- pkg: github.com/pkg/errors
desc: use builtin errors package instead
nolintlint: nolintlint:
allow-unused: false allow-unused: false
require-explanation: true require-explanation: true

View File

@ -80,7 +80,7 @@ The more detailed and specific you are, the faster we can fix the issue. \
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \ It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report. Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
Please be kind, remember that Gitea comes at no cost to you, and you're getting free help. Please be kindremember that Gitea comes at no cost to you, and you're getting free help.
### Types of issues ### Types of issues

View File

@ -32,10 +32,6 @@ export default defineConfig([
languageOptions: { languageOptions: {
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: 'module', sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
},
parser: typescriptParser, parser: typescriptParser,
parserOptions: { parserOptions: {
sourceType: 'module', sourceType: 'module',
@ -233,7 +229,7 @@ export default defineConfig([
'@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}], '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}],
'@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}], '@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}],
'@typescript-eslint/no-useless-constructor': [0], '@typescript-eslint/no-useless-constructor': [0],
'@typescript-eslint/no-useless-default-assignment': [0], // https://github.com/typescript-eslint/typescript-eslint/issues/11847 '@typescript-eslint/no-useless-default-assignment': [2],
'@typescript-eslint/no-useless-empty-export': [0], '@typescript-eslint/no-useless-empty-export': [0],
'@typescript-eslint/no-wrapper-object-types': [2], '@typescript-eslint/no-wrapper-object-types': [2],
'@typescript-eslint/non-nullable-type-assertion-style': [0], '@typescript-eslint/non-nullable-type-assertion-style': [0],
@ -264,6 +260,7 @@ export default defineConfig([
'@typescript-eslint/restrict-template-expressions': [0], '@typescript-eslint/restrict-template-expressions': [0],
'@typescript-eslint/return-await': [0], '@typescript-eslint/return-await': [0],
'@typescript-eslint/strict-boolean-expressions': [0], '@typescript-eslint/strict-boolean-expressions': [0],
'@typescript-eslint/strict-void-return': [0],
'@typescript-eslint/switch-exhaustiveness-check': [0], '@typescript-eslint/switch-exhaustiveness-check': [0],
'@typescript-eslint/triple-slash-reference': [2], '@typescript-eslint/triple-slash-reference': [2],
'@typescript-eslint/typedef': [0], '@typescript-eslint/typedef': [0],
@ -362,7 +359,7 @@ export default defineConfig([
'import-x/no-self-import': [2], 'import-x/no-self-import': [2],
'import-x/no-unassigned-import': [0], 'import-x/no-unassigned-import': [0],
'import-x/no-unresolved': [2, {commonjs: true, ignore: ['\\?.+$']}], 'import-x/no-unresolved': [2, {commonjs: true, ignore: ['\\?.+$']}],
// 'import-x/no-unused-modules': [2, {unusedExports: true}], // not compatible with eslint 9 'import-x/no-unused-modules': [0], // incompatible with eslint 9
'import-x/no-useless-path-segments': [2, {commonjs: true}], 'import-x/no-useless-path-segments': [2, {commonjs: true}],
'import-x/no-webpack-loader-syntax': [2], 'import-x/no-webpack-loader-syntax': [2],
'import-x/order': [0], 'import-x/order': [0],
@ -989,38 +986,19 @@ export default defineConfig([
'vitest/valid-title': [2], 'vitest/valid-title': [2],
}, },
}, },
{
files: ['web_src/js/types.ts'],
rules: {
'import-x/no-unused-modules': [0],
},
},
{ {
files: ['**/*.d.ts'], files: ['**/*.d.ts'],
rules: { rules: {
'import-x/no-unused-modules': [0],
'@typescript-eslint/consistent-type-definitions': [0], '@typescript-eslint/consistent-type-definitions': [0],
'@typescript-eslint/consistent-type-imports': [0], '@typescript-eslint/consistent-type-imports': [0],
}, },
}, },
{ {
files: ['*.config.*'], files: ['*', 'tools/**/*'],
rules: { languageOptions: {globals: globals.node},
'import-x/no-unused-modules': [0],
},
}, },
{ {
files: ['web_src/**/*', 'docs/**/*'], files: ['web_src/**/*', 'docs/**/*'],
languageOptions: {globals: globals.browser}, languageOptions: {globals: globals.browser},
}, },
{
files: ['web_src/**/*'],
languageOptions: {
globals: {
...globals.browser,
__webpack_public_path__: true,
process: false, // https://github.com/webpack/webpack/issues/15833
},
},
},
]); ]);

2
go.mod
View File

@ -95,7 +95,6 @@ require (
github.com/olivere/elastic/v7 v7.0.32 github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.5.0 github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_golang v1.23.0
github.com/quasoft/websspi v1.1.2 github.com/quasoft/websspi v1.1.2
@ -251,6 +250,7 @@ require (
github.com/philhofer/fwd v1.2.0 // indirect github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/common v0.65.0 // indirect

View File

@ -12,6 +12,8 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"xorm.io/builder"
) )
// Stopwatch represents a stopwatch for time tracking. // Stopwatch represents a stopwatch for time tracking.
@ -232,3 +234,14 @@ func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (
}) })
return ok, err return ok, err
} }
// RemoveStopwatchesByRepoID removes all stopwatches for a user in a specific repository
// this function should be called before removing all the issues of the repository
func RemoveStopwatchesByRepoID(ctx context.Context, userID, repoID int64) error {
_, err := db.GetEngine(ctx).
Where("`stopwatch`.user_id = ?", userID).
And(builder.In("`stopwatch`.issue_id",
builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID}))).
Delete(new(Stopwatch))
return err
}

View File

@ -213,6 +213,18 @@ func GetColumn(ctx context.Context, columnID int64) (*Column, error) {
return column, nil return column, nil
} }
func GetColumnByIDAndProjectID(ctx context.Context, columnID, projectID int64) (*Column, error) {
column := new(Column)
has, err := db.GetEngine(ctx).ID(columnID).And("project_id=?", projectID).Get(column)
if err != nil {
return nil, err
} else if !has {
return nil, ErrProjectColumnNotExist{ColumnID: columnID}
}
return column, nil
}
// UpdateColumn updates a project column // UpdateColumn updates a project column
func UpdateColumn(ctx context.Context, column *Column) error { func UpdateColumn(ctx context.Context, column *Column) error {
var fieldToUpdate []string var fieldToUpdate []string

View File

@ -302,6 +302,18 @@ func GetProjectByID(ctx context.Context, id int64) (*Project, error) {
return p, nil return p, nil
} }
func GetProjectByIDAndOwner(ctx context.Context, id, ownerID int64) (*Project, error) {
p := new(Project)
has, err := db.GetEngine(ctx).ID(id).And("owner_id = ?", ownerID).Get(p)
if err != nil {
return nil, err
} else if !has {
return nil, ErrProjectNotExist{ID: id}
}
return p, nil
}
// GetProjectForRepoByID returns the projects in a repository // GetProjectForRepoByID returns the projects in a repository
func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, error) { func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, error) {
p := new(Project) p := new(Project)

View File

@ -166,6 +166,11 @@ func GetAttachmentByReleaseIDFileName(ctx context.Context, releaseID int64, file
return attach, nil return attach, nil
} }
func GetUnlinkedAttachmentsByUserID(ctx context.Context, userID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
return attachments, db.GetEngine(ctx).Where("uploader_id = ? AND issue_id = 0 AND release_id = 0 AND comment_id = 0", userID).Find(&attachments)
}
// DeleteAttachment deletes the given attachment and optionally the associated file. // DeleteAttachment deletes the given attachment and optionally the associated file.
func DeleteAttachment(ctx context.Context, a *Attachment, remove bool) error { func DeleteAttachment(ctx context.Context, a *Attachment, remove bool) error {
_, err := DeleteAttachments(ctx, []*Attachment{a}, remove) _, err := DeleteAttachments(ctx, []*Attachment{a}, remove)

View File

@ -101,3 +101,19 @@ func TestGetAttachmentsByUUIDs(t *testing.T) {
assert.Equal(t, int64(1), attachList[0].IssueID) assert.Equal(t, int64(1), attachList[0].IssueID)
assert.Equal(t, int64(5), attachList[1].IssueID) assert.Equal(t, int64(5), attachList[1].IssueID)
} }
func TestGetUnlinkedAttachmentsByUserID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
attachments, err := repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 8)
assert.NoError(t, err)
assert.Len(t, attachments, 1)
assert.Equal(t, int64(10), attachments[0].ID)
assert.Zero(t, attachments[0].IssueID)
assert.Zero(t, attachments[0].ReleaseID)
assert.Zero(t, attachments[0].CommentID)
attachments, err = repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 1)
assert.NoError(t, err)
assert.Empty(t, attachments)
}

View File

@ -93,15 +93,21 @@ func init() {
db.RegisterModel(new(Release)) db.RegisterModel(new(Release))
} }
// LoadAttributes load repo and publisher attributes for a release func (r *Release) LoadRepo(ctx context.Context) (err error) {
func (r *Release) LoadAttributes(ctx context.Context) error { if r.Repo != nil {
var err error return nil
if r.Repo == nil {
r.Repo, err = GetRepositoryByID(ctx, r.RepoID)
if err != nil {
return err
}
} }
r.Repo, err = GetRepositoryByID(ctx, r.RepoID)
return err
}
// LoadAttributes load repo and publisher attributes for a release
func (r *Release) LoadAttributes(ctx context.Context) (err error) {
if err := r.LoadRepo(ctx); err != nil {
return err
}
if r.Publisher == nil { if r.Publisher == nil {
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID) r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
if err != nil { if err != nil {
@ -168,6 +174,11 @@ func UpdateReleaseNumCommits(ctx context.Context, rel *Release) error {
// AddReleaseAttachments adds a release attachments // AddReleaseAttachments adds a release attachments
func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) { func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
rel, err := GetReleaseByID(ctx, releaseID)
if err != nil {
return err
}
// Check attachments // Check attachments
attachments, err := GetAttachmentsByUUIDs(ctx, attachmentUUIDs) attachments, err := GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
if err != nil { if err != nil {
@ -175,6 +186,10 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs
} }
for i := range attachments { for i := range attachments {
if attachments[i].RepoID != rel.RepoID {
return util.NewPermissionDeniedErrorf("attachment belongs to different repository")
}
if attachments[i].ReleaseID != 0 { if attachments[i].ReleaseID != 0 {
return util.NewPermissionDeniedErrorf("release permission denied") return util.NewPermissionDeniedErrorf("release permission denied")
} }

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -37,3 +38,16 @@ func Test_FindTagsByCommitIDs(t *testing.T) {
assert.Equal(t, "delete-tag", rels[1].TagName) assert.Equal(t, "delete-tag", rels[1].TagName)
assert.Equal(t, "v1.0", rels[2].TagName) assert.Equal(t, "v1.0", rels[2].TagName)
} }
func TestAddReleaseAttachmentsRejectsDifferentRepo(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12" // attachment 2 belongs to repo 2
err := AddReleaseAttachments(t.Context(), 1, []string{uuid})
assert.Error(t, err)
assert.ErrorIs(t, err, util.ErrPermissionDenied)
attach, err := GetAttachmentByUUID(t.Context(), uuid)
assert.NoError(t, err)
assert.Zero(t, attach.ReleaseID, "attachment should not be linked to release on failure")
}

View File

@ -176,3 +176,13 @@ func WatchIfAuto(ctx context.Context, userID, repoID int64, isWrite bool) error
} }
return watchRepoMode(ctx, watch, WatchModeAuto) return watchRepoMode(ctx, watch, WatchModeAuto)
} }
// ClearRepoWatches clears all watches for a repository and from the user that watched it.
// Used when a repository is set to private.
func ClearRepoWatches(ctx context.Context, repoID int64) error {
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_watches = 0 WHERE id = ?", repoID); err != nil {
return err
}
return db.DeleteBeans(ctx, Watch{RepoID: repoID})
}

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestIsWatching(t *testing.T) { func TestIsWatching(t *testing.T) {
@ -119,3 +120,21 @@ func TestWatchIfAuto(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, watchers, prevCount) assert.Len(t, watchers, prevCount)
} }
func TestClearRepoWatches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
const repoID int64 = 1
watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repoID)
require.NoError(t, err)
require.NotEmpty(t, watchers)
assert.NoError(t, repo_model.ClearRepoWatches(t.Context(), repoID))
watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repoID)
assert.NoError(t, err)
assert.Empty(t, watchers)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
assert.Zero(t, repo.NumWatches)
}

View File

@ -102,7 +102,13 @@ func DeleteUserOpenID(ctx context.Context, openid *UserOpenID) (err error) {
} }
// ToggleUserOpenIDVisibility toggles visibility of an openid address of given user. // ToggleUserOpenIDVisibility toggles visibility of an openid address of given user.
func ToggleUserOpenIDVisibility(ctx context.Context, id int64) (err error) { func ToggleUserOpenIDVisibility(ctx context.Context, id int64, user *User) error {
_, err = db.GetEngine(ctx).Exec("update `user_open_id` set `show` = not `show` where `id` = ?", id) affected, err := db.GetEngine(ctx).Exec("update `user_open_id` set `show` = not `show` where `id` = ? AND uid = ?", id, user.ID)
return err if err != nil {
return err
}
if n, _ := affected.RowsAffected(); n != 1 {
return util.NewNotExistErrorf("OpenID is unknown")
}
return nil
} }

View File

@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -33,12 +34,14 @@ func TestGetUserOpenIDs(t *testing.T) {
func TestToggleUserOpenIDVisibility(t *testing.T) { func TestToggleUserOpenIDVisibility(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
user, err := user_model.GetUserByID(t.Context(), int64(2))
require.NoError(t, err)
oids, err := user_model.GetUserOpenIDs(t.Context(), int64(2)) oids, err := user_model.GetUserOpenIDs(t.Context(), int64(2))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, oids, 1) require.Len(t, oids, 1)
assert.True(t, oids[0].Show) assert.True(t, oids[0].Show)
err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID) err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
require.NoError(t, err) require.NoError(t, err)
oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
@ -46,7 +49,7 @@ func TestToggleUserOpenIDVisibility(t *testing.T) {
require.Len(t, oids, 1) require.Len(t, oids, 1)
assert.False(t, oids[0].Show) assert.False(t, oids[0].Show)
err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID) err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
require.NoError(t, err) require.NoError(t, err)
oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
@ -55,3 +58,13 @@ func TestToggleUserOpenIDVisibility(t *testing.T) {
assert.True(t, oids[0].Show) assert.True(t, oids[0].Show)
} }
} }
func TestToggleUserOpenIDVisibilityRequiresOwnership(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
unauthorizedUser, err := user_model.GetUserByID(t.Context(), int64(2))
require.NoError(t, err)
err = user_model.ToggleUserOpenIDVisibility(t.Context(), int64(1), unauthorizedUser)
require.Error(t, err)
assert.ErrorIs(t, err, util.ErrNotExist)
}

View File

@ -9,6 +9,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -91,7 +92,13 @@ loop:
} }
for _, userStopwatches := range usersStopwatches { for _, userStopwatches := range usersStopwatches {
apiSWs, err := convert.ToStopWatches(ctx, userStopwatches.StopWatches) u, err := user_model.GetUserByID(ctx, userStopwatches.UserID)
if err != nil {
log.Error("Unable to get user %d: %v", userStopwatches.UserID, err)
continue
}
apiSWs, err := convert.ToStopWatches(ctx, u, userStopwatches.StopWatches)
if err != nil { if err != nil {
if !issues_model.IsErrIssueNotExist(err) { if !issues_model.IsErrIssueNotExist(err) {
log.Error("Unable to APIFormat stopwatches: %v", err) log.Error("Unable to APIFormat stopwatches: %v", err)

View File

@ -426,9 +426,9 @@ type RunStdError interface {
} }
type runStdError struct { type runStdError struct {
err error err error // usually the low-level error like `*exec.ExitError`
stderr string stderr string // git command's stderr output
errMsg string errMsg string // the cached error message for Error() method
} }
func (r *runStdError) Error() string { func (r *runStdError) Error() string {
@ -448,6 +448,22 @@ func (r *runStdError) Stderr() string {
return r.stderr return r.stderr
} }
func ErrorAsStderr(err error) (string, bool) {
var runErr RunStdError
if errors.As(err, &runErr) {
return runErr.Stderr(), true
}
return "", false
}
func StderrHasPrefix(err error, prefix string) bool {
stderr, ok := ErrorAsStderr(err)
if !ok {
return false
}
return strings.HasPrefix(stderr, prefix)
}
func IsErrorExitCode(err error, code int) bool { func IsErrorExitCode(err error, code int) bool {
var exitError *exec.ExitError var exitError *exec.ExitError
if errors.As(err, &exitError) { if errors.As(err, &exitError) {

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/tempdir" "code.gitea.io/gitea/modules/tempdir"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -99,3 +100,14 @@ func TestCommandString(t *testing.T) {
cmd = NewCommand("url: https://a:b@c/", "/root/dir-a/dir-b") cmd = NewCommand("url: https://a:b@c/", "/root/dir-a/dir-b")
assert.Equal(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString()) assert.Equal(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString())
} }
func TestRunStdError(t *testing.T) {
e := &runStdError{stderr: "some error"}
var err RunStdError = e
var asErr RunStdError
require.ErrorAs(t, err, &asErr)
require.Equal(t, "some error", asErr.Stderr())
require.ErrorAs(t, fmt.Errorf("wrapped %w", err), &asErr)
}

View File

@ -3,12 +3,18 @@
package gitcmd package gitcmd
import "fmt" import (
"fmt"
// ConcatenateError concatenats an error with stderr string "code.gitea.io/gitea/modules/util"
)
// ConcatenateError concatenates an error with stderr string
// FIXME: use RunStdError instead
func ConcatenateError(err error, stderr string) error { func ConcatenateError(err error, stderr string) error {
if len(stderr) == 0 { if len(stderr) == 0 {
return err return err
} }
return fmt.Errorf("%w - %s", err, stderr) errMsg := fmt.Sprintf("%s - %s", err.Error(), stderr)
return util.ErrorWrap(&runStdError{err: err, stderr: stderr, errMsg: errMsg}, "%s", errMsg)
} }

View File

@ -46,8 +46,8 @@ func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
entry.Size = optional.Some(size) entry.Size = optional.Some(size)
} }
entry.EntryMode, err = ParseEntryMode(string(entryMode)) entry.EntryMode = ParseEntryMode(string(entryMode))
if err != nil || entry.EntryMode == EntryModeNoEntry { if entry.EntryMode == EntryModeNoEntry {
return nil, fmt.Errorf("invalid ls-tree output (invalid mode): %q, err: %w", line, err) return nil, fmt.Errorf("invalid ls-tree output (invalid mode): %q, err: %w", line, err)
} }

View File

@ -4,7 +4,6 @@
package git package git
import ( import (
"fmt"
"strconv" "strconv"
) )
@ -55,21 +54,38 @@ func (e EntryMode) IsExecutable() bool {
return e == EntryModeExec return e == EntryModeExec
} }
func ParseEntryMode(mode string) (EntryMode, error) { func ParseEntryMode(mode string) EntryMode {
switch mode { switch mode {
case "000000": case "000000":
return EntryModeNoEntry, nil return EntryModeNoEntry
case "100644": case "100644":
return EntryModeBlob, nil return EntryModeBlob
case "100755": case "100755":
return EntryModeExec, nil return EntryModeExec
case "120000": case "120000":
return EntryModeSymlink, nil return EntryModeSymlink
case "160000": case "160000":
return EntryModeCommit, nil return EntryModeCommit
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons case "040000":
return EntryModeTree, nil return EntryModeTree
default: default:
return 0, fmt.Errorf("unparsable entry mode: %s", mode) // git uses 040000 for tree object, but some users may get 040755 from non-standard git implementations
m, _ := strconv.ParseInt(mode, 8, 32)
modeInt := EntryMode(m)
switch modeInt & 0o770000 {
case 0o040000:
return EntryModeTree
case 0o160000:
return EntryModeCommit
case 0o120000:
return EntryModeSymlink
case 0o100000:
if modeInt&0o777 == 0o755 {
return EntryModeExec
}
return EntryModeBlob
default:
return EntryModeNoEntry
}
} }
} }

View File

@ -27,3 +27,30 @@ func TestEntriesCustomSort(t *testing.T) {
entries.CustomSort(strings.Compare) entries.CustomSort(strings.Compare)
assert.Equal(t, expected, entries) assert.Equal(t, expected, entries)
} }
func TestParseEntryMode(t *testing.T) {
tests := []struct {
modeStr string
expectMod EntryMode
}{
{"000000", EntryModeNoEntry},
{"000755", EntryModeNoEntry},
{"100644", EntryModeBlob},
{"100755", EntryModeExec},
{"120000", EntryModeSymlink},
{"120755", EntryModeSymlink},
{"160000", EntryModeCommit},
{"160755", EntryModeCommit},
{"040000", EntryModeTree},
{"040755", EntryModeTree},
{"777777", EntryModeNoEntry}, // invalid mode
}
for _, test := range tests {
mod := ParseEntryMode(test.modeStr)
assert.Equal(t, test.expectMod, mod, "modeStr: %s", test.modeStr)
}
}

View File

@ -8,7 +8,9 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/git/gitcmd"
@ -16,7 +18,7 @@ import (
) )
// CreateArchive create archive content to the target path // CreateArchive create archive content to the target path
func CreateArchive(ctx context.Context, repo Repository, format string, target io.Writer, usePrefix bool, commitID string) error { func CreateArchive(ctx context.Context, repo Repository, format string, target io.Writer, usePrefix bool, commitID string, paths []string) error {
if format == "unknown" { if format == "unknown" {
return fmt.Errorf("unknown format: %v", format) return fmt.Errorf("unknown format: %v", format)
} }
@ -28,6 +30,13 @@ func CreateArchive(ctx context.Context, repo Repository, format string, target i
cmd.AddOptionFormat("--format=%s", format) cmd.AddOptionFormat("--format=%s", format)
cmd.AddDynamicArguments(commitID) cmd.AddDynamicArguments(commitID)
paths = slices.Clone(paths)
for i := range paths {
// although "git archive" already ensures the paths won't go outside the repo, we still clean them here for safety
paths[i] = path.Clean(paths[i])
}
cmd.AddDynamicArguments(paths...)
var stderr strings.Builder var stderr strings.Builder
if err := RunCmd(ctx, repo, cmd.WithStdout(target).WithStderr(&stderr)); err != nil { if err := RunCmd(ctx, repo, cmd.WithStdout(target).WithStderr(&stderr)); err != nil {
return gitcmd.ConcatenateError(err, stderr.String()) return gitcmd.ConcatenateError(err, stderr.String())

View File

@ -66,6 +66,21 @@ type Link struct {
ExpiresAt *time.Time `json:"expires_at,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"`
} }
func NewLink(href string) *Link {
return &Link{Href: href}
}
func (l *Link) WithHeader(k, v string) *Link {
if v == "" {
return l
}
if l.Header == nil {
l.Header = make(map[string]string)
}
l.Header[k] = v
return l
}
// ObjectError defines the JSON structure returned to the client in case of an error. // ObjectError defines the JSON structure returned to the client in case of an error.
type ObjectError struct { type ObjectError struct {
Code int `json:"code"` Code int `json:"code"`

View File

@ -4,6 +4,9 @@
package test package test
import ( import (
"archive/tar"
"compress/gzip"
"io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -71,3 +74,31 @@ func SetupGiteaRoot() string {
_ = os.Setenv("GITEA_ROOT", giteaRoot) _ = os.Setenv("GITEA_ROOT", giteaRoot)
return giteaRoot return giteaRoot
} }
func ReadAllTarGzContent(r io.Reader) (map[string]string, error) {
gzr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
content := make(map[string]string)
tr := tar.NewReader(gzr)
for {
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
buf, err := io.ReadAll(tr)
if err != nil {
return nil, err
}
content[hd.Name] = string(buf)
}
return content, nil
}

View File

@ -107,6 +107,17 @@ func detectFileTypeBox(data []byte) (brands []string, found bool) {
return brands, true return brands, true
} }
func isEmbeddedOpenType(data []byte) bool {
// https://www.w3.org/submissions/EOT
if len(data) < 80 {
return false
}
version := binary.LittleEndian.Uint32(data[8:]) // Actually this standard is abandoned (for IE6-IE11 only), there are only 3 versions defined
magic := binary.LittleEndian.Uint16(data[34:36]) // MagicNumber: 0x504C ("LP")
reserved := data[64:80] // Reserved 1-4 (each: unsigned long)
return (version == 0x00010000 || version == 0x00020001 || version == 0x00020002) && magic == 0x504C && bytes.Count(reserved, []byte{0}) == len(reserved)
}
// DetectContentType extends http.DetectContentType with more content types. Defaults to text/plain if input is empty. // DetectContentType extends http.DetectContentType with more content types. Defaults to text/plain if input is empty.
func DetectContentType(data []byte) SniffedType { func DetectContentType(data []byte) SniffedType {
if len(data) == 0 { if len(data) == 0 {
@ -119,6 +130,18 @@ func DetectContentType(data []byte) SniffedType {
data = data[:SniffContentSize] data = data[:SniffContentSize]
} }
const typeMsFontObject = "application/vnd.ms-fontobject"
if ct == typeMsFontObject {
// Stupid Golang blindly detects any content with 34th-35th bytes being "LP" as font.
// If it is not really for ".eot" content, we try to detect it again by hiding the "LP", see the test for more details.
if isEmbeddedOpenType(data) {
return SniffedType{typeMsFontObject}
}
data = slices.Clone(data)
data[34] = 'l'
ct = http.DetectContentType(data)
}
vars := globalVars() vars := globalVars()
// SVG is unsupported by http.DetectContentType, https://github.com/golang/go/issues/15888 // SVG is unsupported by http.DetectContentType, https://github.com/golang/go/issues/15888
detectByHTML := strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html") detectByHTML := strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html")

View File

@ -6,6 +6,7 @@ package typesniffer
import ( import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"net/http"
"strings" "strings"
"testing" "testing"
@ -154,3 +155,25 @@ func TestDetectContentTypeAvif(t *testing.T) {
st := DetectContentType(buf) st := DetectContentType(buf)
assert.Equal(t, MimeTypeImageAvif, st.contentType) assert.Equal(t, MimeTypeImageAvif, st.contentType)
} }
func TestDetectContentTypeIncorrectFont(t *testing.T) {
s := "Stupid Golang keep detecting 34th LP as font"
// They don't want to have any improvement to it: https://github.com/golang/go/issues/77172
golangDetected := http.DetectContentType([]byte(s))
assert.Equal(t, "application/vnd.ms-fontobject", golangDetected)
// We have to make our patch to make it work correctly
ourDetected := DetectContentType([]byte(s))
assert.Equal(t, "text/plain; charset=utf-8", ourDetected.contentType)
// For binary content, ensure it still detects as font. The content is from "opensans-regular.eot"
b := []byte{
0x3d, 0x30, 0x00, 0x00, 0x6b, 0x2f, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x0b, 0x06, 0x06, 0x03, 0x05, 0x04, 0x02, 0x02, 0x04, 0x01, 0x00, 0x90, 0x01, 0x00, 0x00,
0x04, 0x00, 0x4c, 0x50, 0xef, 0x02, 0x00, 0xe0, 0x5b, 0x20, 0x00, 0x40, 0x28, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x9f, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x63, 0xf4, 0x17, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x12, 0x00, 0x4f, 0x00, 0x70, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x53, 0x00,
}
assert.Equal(t, "application/vnd.ms-fontobject", http.DetectContentType(b))
assert.Equal(t, "application/vnd.ms-fontobject", DetectContentType(b).contentType)
}

View File

@ -460,6 +460,22 @@
".blog": "folder-docs", ".blog": "folder-docs",
"_blog": "folder-docs", "_blog": "folder-docs",
"__blog__": "folder-docs", "__blog__": "folder-docs",
"knowledge": "folder-docs",
".knowledge": "folder-docs",
"_knowledge": "folder-docs",
"__knowledge__": "folder-docs",
"diary": "folder-docs",
".diary": "folder-docs",
"_diary": "folder-docs",
"__diary__": "folder-docs",
"note": "folder-docs",
".note": "folder-docs",
"_note": "folder-docs",
"__note__": "folder-docs",
"notes": "folder-docs",
".notes": "folder-docs",
"_notes": "folder-docs",
"__notes__": "folder-docs",
"github/workflows": "folder-gh-workflows", "github/workflows": "folder-gh-workflows",
".github/workflows": "folder-gh-workflows", ".github/workflows": "folder-gh-workflows",
"_github/workflows": "folder-gh-workflows", "_github/workflows": "folder-gh-workflows",
@ -916,6 +932,14 @@
".sql": "folder-database", ".sql": "folder-database",
"_sql": "folder-database", "_sql": "folder-database",
"__sql__": "folder-database", "__sql__": "folder-database",
"migrations": "folder-migrations",
".migrations": "folder-migrations",
"_migrations": "folder-migrations",
"__migrations__": "folder-migrations",
"migration": "folder-migrations",
".migration": "folder-migrations",
"_migration": "folder-migrations",
"__migration__": "folder-migrations",
"log": "folder-log", "log": "folder-log",
".log": "folder-log", ".log": "folder-log",
"_log": "folder-log", "_log": "folder-log",
@ -1008,6 +1032,14 @@
".recordings": "folder-audio", ".recordings": "folder-audio",
"_recordings": "folder-audio", "_recordings": "folder-audio",
"__recordings__": "folder-audio", "__recordings__": "folder-audio",
"playlist": "folder-audio",
".playlist": "folder-audio",
"_playlist": "folder-audio",
"__playlist__": "folder-audio",
"playlists": "folder-audio",
".playlists": "folder-audio",
"_playlists": "folder-audio",
"__playlists__": "folder-audio",
"vid": "folder-video", "vid": "folder-video",
".vid": "folder-video", ".vid": "folder-video",
"_vid": "folder-video", "_vid": "folder-video",
@ -1544,6 +1576,22 @@
".backends": "folder-server", ".backends": "folder-server",
"_backends": "folder-server", "_backends": "folder-server",
"__backends__": "folder-server", "__backends__": "folder-server",
"inventory": "folder-server",
".inventory": "folder-server",
"_inventory": "folder-server",
"__inventory__": "folder-server",
"inventories": "folder-server",
".inventories": "folder-server",
"_inventories": "folder-server",
"__inventories__": "folder-server",
"infrastructure": "folder-server",
".infrastructure": "folder-server",
"_infrastructure": "folder-server",
"__infrastructure__": "folder-server",
"infra": "folder-server",
".infra": "folder-server",
"_infra": "folder-server",
"__infra__": "folder-server",
"client": "folder-client", "client": "folder-client",
".client": "folder-client", ".client": "folder-client",
"_client": "folder-client", "_client": "folder-client",
@ -1992,6 +2040,14 @@
".calculations": "folder-functions", ".calculations": "folder-functions",
"_calculations": "folder-functions", "_calculations": "folder-functions",
"__calculations__": "folder-functions", "__calculations__": "folder-functions",
"composable": "folder-functions",
".composable": "folder-functions",
"_composable": "folder-functions",
"__composable__": "folder-functions",
"composables": "folder-functions",
".composables": "folder-functions",
"_composables": "folder-functions",
"__composables__": "folder-functions",
"generator": "folder-generator", "generator": "folder-generator",
".generator": "folder-generator", ".generator": "folder-generator",
"_generator": "folder-generator", "_generator": "folder-generator",
@ -2936,6 +2992,14 @@
".projects": "folder-project", ".projects": "folder-project",
"_projects": "folder-project", "_projects": "folder-project",
"__projects__": "folder-project", "__projects__": "folder-project",
"proj": "folder-project",
".proj": "folder-project",
"_proj": "folder-project",
"__proj__": "folder-project",
"projs": "folder-project",
".projs": "folder-project",
"_projs": "folder-project",
"__projs__": "folder-project",
"prompt": "folder-prompts", "prompt": "folder-prompts",
".prompt": "folder-prompts", ".prompt": "folder-prompts",
"_prompt": "folder-prompts", "_prompt": "folder-prompts",
@ -3447,6 +3511,14 @@
".in": "folder-input", ".in": "folder-input",
"_in": "folder-input", "_in": "folder-input",
"__in__": "folder-input", "__in__": "folder-input",
"salt": "folder-salt",
".salt": "folder-salt",
"_salt": "folder-salt",
"__salt__": "folder-salt",
"saltstack": "folder-salt",
".saltstack": "folder-salt",
"_saltstack": "folder-salt",
"__saltstack__": "folder-salt",
"simulations": "folder-simulations", "simulations": "folder-simulations",
".simulations": "folder-simulations", ".simulations": "folder-simulations",
"_simulations": "folder-simulations", "_simulations": "folder-simulations",
@ -3961,6 +4033,22 @@
".blog": "folder-docs-open", ".blog": "folder-docs-open",
"_blog": "folder-docs-open", "_blog": "folder-docs-open",
"__blog__": "folder-docs-open", "__blog__": "folder-docs-open",
"knowledge": "folder-docs-open",
".knowledge": "folder-docs-open",
"_knowledge": "folder-docs-open",
"__knowledge__": "folder-docs-open",
"diary": "folder-docs-open",
".diary": "folder-docs-open",
"_diary": "folder-docs-open",
"__diary__": "folder-docs-open",
"note": "folder-docs-open",
".note": "folder-docs-open",
"_note": "folder-docs-open",
"__note__": "folder-docs-open",
"notes": "folder-docs-open",
".notes": "folder-docs-open",
"_notes": "folder-docs-open",
"__notes__": "folder-docs-open",
"github/workflows": "folder-gh-workflows-open", "github/workflows": "folder-gh-workflows-open",
".github/workflows": "folder-gh-workflows-open", ".github/workflows": "folder-gh-workflows-open",
"_github/workflows": "folder-gh-workflows-open", "_github/workflows": "folder-gh-workflows-open",
@ -4417,6 +4505,14 @@
".sql": "folder-database-open", ".sql": "folder-database-open",
"_sql": "folder-database-open", "_sql": "folder-database-open",
"__sql__": "folder-database-open", "__sql__": "folder-database-open",
"migrations": "folder-migrations-open",
".migrations": "folder-migrations-open",
"_migrations": "folder-migrations-open",
"__migrations__": "folder-migrations-open",
"migration": "folder-migrations-open",
".migration": "folder-migrations-open",
"_migration": "folder-migrations-open",
"__migration__": "folder-migrations-open",
"log": "folder-log-open", "log": "folder-log-open",
".log": "folder-log-open", ".log": "folder-log-open",
"_log": "folder-log-open", "_log": "folder-log-open",
@ -4509,6 +4605,14 @@
".recordings": "folder-audio-open", ".recordings": "folder-audio-open",
"_recordings": "folder-audio-open", "_recordings": "folder-audio-open",
"__recordings__": "folder-audio-open", "__recordings__": "folder-audio-open",
"playlist": "folder-audio-open",
".playlist": "folder-audio-open",
"_playlist": "folder-audio-open",
"__playlist__": "folder-audio-open",
"playlists": "folder-audio-open",
".playlists": "folder-audio-open",
"_playlists": "folder-audio-open",
"__playlists__": "folder-audio-open",
"vid": "folder-video-open", "vid": "folder-video-open",
".vid": "folder-video-open", ".vid": "folder-video-open",
"_vid": "folder-video-open", "_vid": "folder-video-open",
@ -5045,6 +5149,22 @@
".backends": "folder-server-open", ".backends": "folder-server-open",
"_backends": "folder-server-open", "_backends": "folder-server-open",
"__backends__": "folder-server-open", "__backends__": "folder-server-open",
"inventory": "folder-server-open",
".inventory": "folder-server-open",
"_inventory": "folder-server-open",
"__inventory__": "folder-server-open",
"inventories": "folder-server-open",
".inventories": "folder-server-open",
"_inventories": "folder-server-open",
"__inventories__": "folder-server-open",
"infrastructure": "folder-server-open",
".infrastructure": "folder-server-open",
"_infrastructure": "folder-server-open",
"__infrastructure__": "folder-server-open",
"infra": "folder-server-open",
".infra": "folder-server-open",
"_infra": "folder-server-open",
"__infra__": "folder-server-open",
"client": "folder-client-open", "client": "folder-client-open",
".client": "folder-client-open", ".client": "folder-client-open",
"_client": "folder-client-open", "_client": "folder-client-open",
@ -5493,6 +5613,14 @@
".calculations": "folder-functions-open", ".calculations": "folder-functions-open",
"_calculations": "folder-functions-open", "_calculations": "folder-functions-open",
"__calculations__": "folder-functions-open", "__calculations__": "folder-functions-open",
"composable": "folder-functions-open",
".composable": "folder-functions-open",
"_composable": "folder-functions-open",
"__composable__": "folder-functions-open",
"composables": "folder-functions-open",
".composables": "folder-functions-open",
"_composables": "folder-functions-open",
"__composables__": "folder-functions-open",
"generator": "folder-generator-open", "generator": "folder-generator-open",
".generator": "folder-generator-open", ".generator": "folder-generator-open",
"_generator": "folder-generator-open", "_generator": "folder-generator-open",
@ -6437,6 +6565,14 @@
".projects": "folder-project-open", ".projects": "folder-project-open",
"_projects": "folder-project-open", "_projects": "folder-project-open",
"__projects__": "folder-project-open", "__projects__": "folder-project-open",
"proj": "folder-project-open",
".proj": "folder-project-open",
"_proj": "folder-project-open",
"__proj__": "folder-project-open",
"projs": "folder-project-open",
".projs": "folder-project-open",
"_projs": "folder-project-open",
"__projs__": "folder-project-open",
"prompt": "folder-prompts-open", "prompt": "folder-prompts-open",
".prompt": "folder-prompts-open", ".prompt": "folder-prompts-open",
"_prompt": "folder-prompts-open", "_prompt": "folder-prompts-open",
@ -6948,6 +7084,14 @@
".in": "folder-input-open", ".in": "folder-input-open",
"_in": "folder-input-open", "_in": "folder-input-open",
"__in__": "folder-input-open", "__in__": "folder-input-open",
"salt": "folder-salt-open",
".salt": "folder-salt-open",
"_salt": "folder-salt-open",
"__salt__": "folder-salt-open",
"saltstack": "folder-salt-open",
".saltstack": "folder-salt-open",
"_saltstack": "folder-salt-open",
"__saltstack__": "folder-salt-open",
"simulations": "folder-simulations-open", "simulations": "folder-simulations-open",
".simulations": "folder-simulations-open", ".simulations": "folder-simulations-open",
"_simulations": "folder-simulations-open", "_simulations": "folder-simulations-open",
@ -7213,6 +7357,7 @@
"csproj": "visualstudio", "csproj": "visualstudio",
"ruleset": "visualstudio", "ruleset": "visualstudio",
"sln": "visualstudio", "sln": "visualstudio",
"slnf": "visualstudio",
"slnx": "visualstudio", "slnx": "visualstudio",
"suo": "visualstudio", "suo": "visualstudio",
"vb": "visualstudio", "vb": "visualstudio",
@ -8162,6 +8307,7 @@
"toc": "toc", "toc": "toc",
"cue": "cue", "cue": "cue",
"lean": "lean", "lean": "lean",
"sls": "salt",
"cljx": "clojure", "cljx": "clojure",
"clojure": "clojure", "clojure": "clojure",
"edn": "clojure", "edn": "clojure",
@ -10150,6 +10296,30 @@
"esbuild.config.ts": "esbuild", "esbuild.config.ts": "esbuild",
"esbuild.config.mts": "esbuild", "esbuild.config.mts": "esbuild",
"esbuild.config.cts": "esbuild", "esbuild.config.cts": "esbuild",
"esbuild.dev.js": "esbuild",
"esbuild.dev.mjs": "esbuild",
"esbuild.dev.cjs": "esbuild",
"esbuild.dev.ts": "esbuild",
"esbuild.dev.mts": "esbuild",
"esbuild.dev.cts": "esbuild",
"esbuild.stage.js": "esbuild",
"esbuild.stage.mjs": "esbuild",
"esbuild.stage.cjs": "esbuild",
"esbuild.stage.ts": "esbuild",
"esbuild.stage.mts": "esbuild",
"esbuild.stage.cts": "esbuild",
"esbuild.prod.js": "esbuild",
"esbuild.prod.mjs": "esbuild",
"esbuild.prod.cjs": "esbuild",
"esbuild.prod.ts": "esbuild",
"esbuild.prod.mts": "esbuild",
"esbuild.prod.cts": "esbuild",
"esbuild.test.js": "esbuild",
"esbuild.test.mjs": "esbuild",
"esbuild.test.cjs": "esbuild",
"esbuild.test.ts": "esbuild",
"esbuild.test.mts": "esbuild",
"esbuild.test.cts": "esbuild",
"drizzle.config.ts": "drizzle", "drizzle.config.ts": "drizzle",
"drizzle.config.dev.ts": "drizzle", "drizzle.config.dev.ts": "drizzle",
"drizzle.config.prod.ts": "drizzle", "drizzle.config.prod.ts": "drizzle",

View File

@ -151,8 +151,8 @@
"database": "<svg viewBox='0 0 32 32'><path fill='#ffca28' d='M16 24c-5.525 0-10-.9-10-2v4c0 1.1 4.475 2 10 2s10-.9 10-2v-4c0 1.1-4.475 2-10 2m0-8c-5.525 0-10-.9-10-2v4c0 1.1 4.475 2 10 2s10-.9 10-2v-4c0 1.1-4.475 2-10 2m0-12C10.477 4 6 4.895 6 6v4c0 1.1 4.475 2 10 2s10-.9 10-2V6c0-1.105-4.477-2-10-2'/></svg>", "database": "<svg viewBox='0 0 32 32'><path fill='#ffca28' d='M16 24c-5.525 0-10-.9-10-2v4c0 1.1 4.475 2 10 2s10-.9 10-2v-4c0 1.1-4.475 2-10 2m0-8c-5.525 0-10-.9-10-2v4c0 1.1 4.475 2 10 2s10-.9 10-2v-4c0 1.1-4.475 2-10 2m0-12C10.477 4 6 4.895 6 6v4c0 1.1 4.475 2 10 2s10-.9 10-2V6c0-1.105-4.477-2-10-2'/></svg>",
"deepsource": "<svg viewBox='0 0 16 16'><path fill='#1de9b6' d='M2 2h9a1 1 0 0 1 1 .992A1 1 0 0 1 11 4H2z'/><path fill='#f44336' d='M2 12h11a1 1 0 0 1 1 1 1 1 0 0 1-1 1H2z'/><path fill='#ffb300' d='M2 9h7a1 1 0 0 0 1-1 1 1 0 0 0-1-1H2z'/></svg>", "deepsource": "<svg viewBox='0 0 16 16'><path fill='#1de9b6' d='M2 2h9a1 1 0 0 1 1 .992A1 1 0 0 1 11 4H2z'/><path fill='#f44336' d='M2 12h11a1 1 0 0 1 1 1 1 1 0 0 1-1 1H2z'/><path fill='#ffb300' d='M2 9h7a1 1 0 0 0 1-1 1 1 0 0 0-1-1H2z'/></svg>",
"denizenscript": "<svg viewBox='0 0 32 32'><path fill='#ffd54f' d='M6 4h8.804a17 17 0 0 1 4.54.459 8 8 0 0 1 3.597 2.21 10.5 10.5 0 0 1 2.278 3.887A17.8 17.8 0 0 1 26 16.23a15.8 15.8 0 0 1-.733 5.108 10.6 10.6 0 0 1-2.554 4.24 8.45 8.45 0 0 1-3.385 1.915 14.5 14.5 0 0 1-4.264.508H6Zm4 4.06v15.896h4.413a13 13 0 0 0 2.913-.228 4.45 4.45 0 0 0 1.945-1 5.1 5.1 0 0 0 1.261-2.316 15.8 15.8 0 0 0 .488-4.395 14.5 14.5 0 0 0-.488-4.274 5.5 5.5 0 0 0-1.367-2.324 4.57 4.57 0 0 0-2.23-1.13 21.7 21.7 0 0 0-3.954-.229Z' style='isolation:isolate'/></svg>", "denizenscript": "<svg viewBox='0 0 32 32'><path fill='#ffd54f' d='M6 4h8.804a17 17 0 0 1 4.54.459 8 8 0 0 1 3.597 2.21 10.5 10.5 0 0 1 2.278 3.887A17.8 17.8 0 0 1 26 16.23a15.8 15.8 0 0 1-.733 5.108 10.6 10.6 0 0 1-2.554 4.24 8.45 8.45 0 0 1-3.385 1.915 14.5 14.5 0 0 1-4.264.508H6Zm4 4.06v15.896h4.413a13 13 0 0 0 2.913-.228 4.45 4.45 0 0 0 1.945-1 5.1 5.1 0 0 0 1.261-2.316 15.8 15.8 0 0 0 .488-4.395 14.5 14.5 0 0 0-.488-4.274 5.5 5.5 0 0 0-1.367-2.324 4.57 4.57 0 0 0-2.23-1.13 21.7 21.7 0 0 0-3.954-.229Z' style='isolation:isolate'/></svg>",
"deno": "<svg viewBox='0 0 32 32'><path fill='#cfd8dc' d='M3.069 10.688C3.069 5.873 7.859 2 14 2a11.9 11.9 0 0 1 7.49 2.378 10.64 10.64 0 0 1 3.593 5.236l.015.049.017.057.034.108.048.198.134.463.14.529.238.875.38 1.386.613 2.28.692 2.593 1.116 4.168.42 1.571-.09.1A18.98 18.98 0 0 1 17.337 30l-.04-.273-.074-.545-.066-.395-.076-.52-.097-.634-.042-.25-.091-.602-.057-.356-.074-.462-.076-.444-.074-.432-.074-.422-.066-.413-.074-.395-.068-.38-.048-.281-.057-.271-.034-.173-.066-.35-.05-.246-.057-.305-.049-.215-.042-.205-.043-.2-.023-.132-.059-.248-.042-.181-.04-.182-.032-.114-.042-.167-.032-.157-.042-.156-.043-.148-.023-.091-.042-.142-.032-.13-.025-.092-.034-.084-.023-.072-.034-.116-.025-.085-.017-.049q-.067-.196-.148-.386l-.026-.051.19-.495-.75.026-.207.008c-6.82.14-11.222-2.759-11.222-7.301Zm14.345-4.101a2 2 0 1 0 0 2.827 2 2 0 0 0 0-2.827'/><path fill='#cfd8dc' d='M3.069 10.688c.95-12.027 21.388-11.423 22.64 1.205 1.027 3.74 2.21 8.244 3.222 11.998A18.98 18.98 0 0 1 17.337 30c-.407-2.79-.84-5.602-1.41-8.364a27 27 0 0 0-.505-2.123c-.104-.536-.523-1.043-.173-1.56-6.665.529-12.374-2.428-12.18-7.267Zm14.345-4.101c-1.807-1.861-4.689 1.02-2.827 2.828 1.807 1.86 4.689-1.021 2.827-2.828'/></svg>", "deno": "<svg viewBox='0 0 32 32'><path fill='#cfd8dc' d='M3.288 23.102A13.937 14.098 0 0 1 2 17.16c0-.553.028-1.09.091-1.622a14.049 14.211 0 0 1 .273-1.586 14 14.162 0 0 1 4.333-7.37 14.007 14.169 0 0 1 6.37-3.272 14.028 14.19 0 0 1 3.997-.27 13.958 14.12 0 0 1 4.76 1.24 14.021 14.183 0 0 1 3.241 2.089 14.028 14.19 0 0 1 4.732 8.355A14.063 14.225 0 0 1 30 17.161a14.175 14.339 0 0 1-.042 1.076 13.986 14.147 0 0 1-.847 3.902 14.007 14.169 0 0 1-2.779 4.574A7.245 7.329 0 0 1 21.166 29a5.054 5.112 0 0 1-4.816-6.444c.119-.453.434-1.317.889-1.699a5.334 5.396 0 0 1-1.435-.977c-.049-.057-.042-.156.007-.22a.182.184 0 0 1 .203-.056 10.206 10.324 0 0 0 1.596.41c.777.128 1.736.298 2.709.34 2.366.12 4.844-.956 5.614-3.094.763-2.146.469-4.263-2.289-5.53-2.758-1.275-4.032-2.783-6.258-3.696-1.456-.595-3.073-.241-4.732.694-4.48 2.5-8.491 10.408-6.643 17.737a.224.227 0 0 1-.364.22 14.077 14.24 0 0 1-1.463-1.927 14.021 14.183 0 0 1-.896-1.656M15.503 9.018c.749-.057 1.414.595 1.526 1.459.147 1.154-.273 2.35-1.645 2.379-1.183.02-1.54-1.176-1.456-1.905.077-.73.665-1.862 1.568-1.933z'/></svg>",
"deno_light": "<svg viewBox='0 0 32 32'><path fill='#455a64' d='M3.07 10.688C4.02-1.34 24.46-.735 25.713 11.893c1.027 3.74 2.21 8.244 3.222 11.998A18.98 18.98 0 0 1 17.339 30c-.407-2.79-.839-5.602-1.41-8.364a27 27 0 0 0-.505-2.123c-.103-.536-.522-1.043-.173-1.56-6.665.529-12.374-2.428-12.18-7.267Zm14.347-4.101c-1.808-1.861-4.69 1.02-2.828 2.828 1.808 1.86 4.69-1.021 2.828-2.828'/></svg>", "deno_light": "<svg viewBox='0 0 32 32'><path fill='#455a64' d='M3.288 23.102A13.937 14.098 0 0 1 2 17.16c0-.553.028-1.09.091-1.622a14.049 14.211 0 0 1 .273-1.586 14 14.162 0 0 1 4.333-7.37 14.007 14.169 0 0 1 6.37-3.272 14.028 14.19 0 0 1 3.997-.27 13.958 14.12 0 0 1 4.76 1.24 14.021 14.183 0 0 1 3.241 2.089 14.028 14.19 0 0 1 4.732 8.355A14.063 14.225 0 0 1 30 17.161a14.175 14.339 0 0 1-.042 1.076 13.986 14.147 0 0 1-.847 3.902 14.007 14.169 0 0 1-2.779 4.574A7.245 7.329 0 0 1 21.166 29a5.054 5.112 0 0 1-4.816-6.444c.119-.453.434-1.317.889-1.699a5.334 5.396 0 0 1-1.435-.977c-.049-.057-.042-.156.007-.22a.182.184 0 0 1 .203-.056 10.206 10.324 0 0 0 1.596.41c.777.128 1.736.298 2.709.34 2.366.12 4.844-.956 5.614-3.094.763-2.146.469-4.263-2.289-5.53-2.758-1.275-4.032-2.783-6.258-3.696-1.456-.595-3.073-.241-4.732.694-4.48 2.5-8.491 10.408-6.643 17.737a.224.227 0 0 1-.364.22 14.077 14.24 0 0 1-1.463-1.927 14.021 14.183 0 0 1-.896-1.656M15.503 9.018c.749-.057 1.414.595 1.526 1.459.147 1.154-.273 2.35-1.645 2.379-1.183.02-1.54-1.176-1.456-1.905.077-.73.665-1.862 1.568-1.933z'/></svg>",
"dependabot": "<svg viewBox='0 0 32 32'><path fill='#448aff' d='M29.5 16H28v-4a2 2 0 0 0-2-2h-6V2.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H18v4H6a2 2 0 0 0-2 2v4H2.5a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5H4v2a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-2h1.5a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.5-.5m-15.533 2.647-3.106 3.106a.6.6 0 0 1-.84 0l-1.867-1.866a.6.6 0 0 1 0-.84l.627-.64a.6.6 0 0 1 .848-.005l.005.005.8.8 2.053-2.04a.6.6 0 0 1 .84 0l.64.64a.58.58 0 0 1 0 .84m9.88 0-3.106 3.106a.6.6 0 0 1-.84 0l-1.867-1.866a.6.6 0 0 1 0-.84l.627-.64a.6.6 0 0 1 .84 0l.813.8 2.053-2.04a.6.6 0 0 1 .84 0l.64.64a.604.604 0 0 1 0 .84'/></svg>", "dependabot": "<svg viewBox='0 0 32 32'><path fill='#448aff' d='M29.5 16H28v-4a2 2 0 0 0-2-2h-6V2.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H18v4H6a2 2 0 0 0-2 2v4H2.5a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5H4v2a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-2h1.5a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.5-.5m-15.533 2.647-3.106 3.106a.6.6 0 0 1-.84 0l-1.867-1.866a.6.6 0 0 1 0-.84l.627-.64a.6.6 0 0 1 .848-.005l.005.005.8.8 2.053-2.04a.6.6 0 0 1 .84 0l.64.64a.58.58 0 0 1 0 .84m9.88 0-3.106 3.106a.6.6 0 0 1-.84 0l-1.867-1.866a.6.6 0 0 1 0-.84l.627-.64a.6.6 0 0 1 .84 0l.813.8 2.053-2.04a.6.6 0 0 1 .84 0l.64.64a.604.604 0 0 1 0 .84'/></svg>",
"dependencies-update": "<svg fill='none' viewBox='0 0 16 16'><path fill='#8bc34a' d='m10.484 3.635-2.5 2.546-.875-.891 1-1.018H8q-1.563 0-2.656 1.121Q4.249 6.515 4.25 8.122a3.5 3.5 0 0 0 .375 1.59l-.937.955a5.156 5.25 0 0 1-.516-1.24A4.81 4.897 0 0 1 3 8.121Q3 5.99 4.453 4.494T8 2.999h.11l-1-1.018.874-.891zm-4.968 8.747 2.5-2.546.875.891-1 1.018H8q1.563 0 2.656-1.12 1.095-1.123 1.094-2.73a3.5 3.5 0 0 0-.375-1.59l.938-.955q.343.604.515 1.24.172.638.172 1.305 0 2.131-1.453 3.628Q10.094 13.018 8 13.018h-.11l1 1.018-.874.891z'/></svg>", "dependencies-update": "<svg fill='none' viewBox='0 0 16 16'><path fill='#8bc34a' d='m10.484 3.635-2.5 2.546-.875-.891 1-1.018H8q-1.563 0-2.656 1.121Q4.249 6.515 4.25 8.122a3.5 3.5 0 0 0 .375 1.59l-.937.955a5.156 5.25 0 0 1-.516-1.24A4.81 4.897 0 0 1 3 8.121Q3 5.99 4.453 4.494T8 2.999h.11l-1-1.018.874-.891zm-4.968 8.747 2.5-2.546.875.891-1 1.018H8q1.563 0 2.656-1.12 1.095-1.123 1.094-2.73a3.5 3.5 0 0 0-.375-1.59l.938-.955q.343.604.515 1.24.172.638.172 1.305 0 2.131-1.453 3.628Q10.094 13.018 8 13.018h-.11l1 1.018-.874.891z'/></svg>",
"dhall": "<svg viewBox='0 0 24 24'><path fill='#78909c' d='M15.81 2.853 21.148 8.2l-1.54 1.518-5.326-5.327 1.528-1.54M2.853 20.373l6.995-6.962a1.04 1.04 0 0 1 .247-1.033c.42-.42 1.109-.42 1.528 0 .42.43.42 1.108 0 1.528-.28.28-.7.355-1.033.248l-6.962 6.995 11.418-3.82 3.799-6.845-5.317-5.327-6.855 3.799z'/></svg>", "dhall": "<svg viewBox='0 0 24 24'><path fill='#78909c' d='M15.81 2.853 21.148 8.2l-1.54 1.518-5.326-5.327 1.528-1.54M2.853 20.373l6.995-6.962a1.04 1.04 0 0 1 .247-1.033c.42-.42 1.109-.42 1.528 0 .42.43.42 1.108 0 1.528-.28.28-.7.355-1.033.248l-6.962 6.995 11.418-3.82 3.799-6.845-5.317-5.327-6.855 3.799z'/></svg>",
@ -495,6 +495,8 @@
"folder-metro": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M24 10a2.917 2.92 0 0 0-1.305.308l-5.39 2.7A2.36 2.363 0 0 0 16 15.12v7.759a2.36 2.363 0 0 0 1.304 2.111l5.39 2.701a2.917 2.92 0 0 0 2.61 0l5.39-2.7A2.36 2.363 0 0 0 32 22.878V15.12a2.36 2.363 0 0 0-1.304-2.112l-5.391-2.7A2.917 2.92 0 0 0 23.999 10m0 1.33a2.875 2.878 0 0 1 1.286.305l4.047 2.025-1.667.834-3.667-1.835-3.666 1.835-1.667-.834 4.047-2.025A2.875 2.878 0 0 1 24 11.329zm-5.735 3.545a.882.883 0 0 1 .344.091l5.39 2.699 5.39-2.699a.882.883 0 0 1 .313-.088.882.883 0 0 1 .964.878v6.153a2.308 2.31 0 0 1-1.276 2.067l-4.114 2.06a2.853 2.856 0 0 1-2.553 0l-4.114-2.06a2.308 2.31 0 0 1-1.276-2.067v-6.153a.882.883 0 0 1 .932-.881m.401 1.788v5.673L24 25.006l5.334-2.67v-5.673l-4.133 2.07a2.683 2.686 0 0 1-2.401 0z'/></svg>", "folder-metro": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M24 10a2.917 2.92 0 0 0-1.305.308l-5.39 2.7A2.36 2.363 0 0 0 16 15.12v7.759a2.36 2.363 0 0 0 1.304 2.111l5.39 2.701a2.917 2.92 0 0 0 2.61 0l5.39-2.7A2.36 2.363 0 0 0 32 22.878V15.12a2.36 2.363 0 0 0-1.304-2.112l-5.391-2.7A2.917 2.92 0 0 0 23.999 10m0 1.33a2.875 2.878 0 0 1 1.286.305l4.047 2.025-1.667.834-3.667-1.835-3.666 1.835-1.667-.834 4.047-2.025A2.875 2.878 0 0 1 24 11.329zm-5.735 3.545a.882.883 0 0 1 .344.091l5.39 2.699 5.39-2.699a.882.883 0 0 1 .313-.088.882.883 0 0 1 .964.878v6.153a2.308 2.31 0 0 1-1.276 2.067l-4.114 2.06a2.853 2.856 0 0 1-2.553 0l-4.114-2.06a2.308 2.31 0 0 1-1.276-2.067v-6.153a.882.883 0 0 1 .932-.881m.401 1.788v5.673L24 25.006l5.334-2.67v-5.673l-4.133 2.07a2.683 2.686 0 0 1-2.401 0z'/></svg>",
"folder-middleware-open": "<svg viewBox='0 0 32 32'><path fill='#5c6bc0' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#c5cae9' d='M30.107 20H32v-4a2 2 0 0 0-2-2h-4v-2a2 2 0 0 0-4 0v2h-4a2 2 0 0 0-2 2v4h-2a2 2 0 0 0 0 4h2v4a2 2 0 0 0 2 2h4v-1.893a2.074 2.074 0 0 1 1.664-2.08A2 2 0 0 1 26 28v2h4a2 2 0 0 0 2-2v-4h-2a2 2 0 0 1-1.973-2.336A2.074 2.074 0 0 1 30.107 20'/></svg>", "folder-middleware-open": "<svg viewBox='0 0 32 32'><path fill='#5c6bc0' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#c5cae9' d='M30.107 20H32v-4a2 2 0 0 0-2-2h-4v-2a2 2 0 0 0-4 0v2h-4a2 2 0 0 0-2 2v4h-2a2 2 0 0 0 0 4h2v4a2 2 0 0 0 2 2h4v-1.893a2.074 2.074 0 0 1 1.664-2.08A2 2 0 0 1 26 28v2h4a2 2 0 0 0 2-2v-4h-2a2 2 0 0 1-1.973-2.336A2.074 2.074 0 0 1 30.107 20'/></svg>",
"folder-middleware": "<svg viewBox='0 0 32 32'><path fill='#5c6bc0' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#c5cae9' d='M30.107 20H32v-4a2 2 0 0 0-2-2h-4v-2a2 2 0 0 0-4 0v2h-4a2 2 0 0 0-2 2v4h-2a2 2 0 0 0 0 4h2v4a2 2 0 0 0 2 2h4v-1.893a2.074 2.074 0 0 1 1.664-2.08A2 2 0 0 1 26 28v2h4a2 2 0 0 0 2-2v-4h-2a2 2 0 0 1-1.973-2.336A2.074 2.074 0 0 1 30.107 20'/></svg>", "folder-middleware": "<svg viewBox='0 0 32 32'><path fill='#5c6bc0' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#c5cae9' d='M30.107 20H32v-4a2 2 0 0 0-2-2h-4v-2a2 2 0 0 0-4 0v2h-4a2 2 0 0 0-2 2v4h-2a2 2 0 0 0 0 4h2v4a2 2 0 0 0 2 2h4v-1.893a2.074 2.074 0 0 1 1.664-2.08A2 2 0 0 1 26 28v2h4a2 2 0 0 0 2-2v-4h-2a2 2 0 0 1-1.973-2.336A2.074 2.074 0 0 1 30.107 20'/></svg>",
"folder-migrations-open": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f8bbd0' d='M24 10c-3.41 0-6.31 1.07-7.46 2.57l5.25 5.25c.71.11 1.43.18 2.21.18 4.42 0 8-1.79 8-4s-3.58-4-8-4m-8 4v4h-4v2h4v4l4.84-5M32 16c0 2.21-3.58 4-8 4-.66 0-1.3-.05-1.91-.13l-2.47 2.47c1.26.41 2.76.66 4.38.66 4.42 0 8-1.79 8-4m0 2c0 2.21-3.58 4-8 4-2.28 0-4.33-.5-5.79-1.25l-1.68 1.68C17.68 26.93 20.59 28 24 28c4.42 0 8-1.79 8-4'/></svg>",
"folder-migrations": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f8bbd0' d='M24 10c-3.41 0-6.31 1.07-7.46 2.57l5.25 5.25c.71.11 1.43.18 2.21.18 4.42 0 8-1.79 8-4s-3.58-4-8-4m-8 4v4h-4v2h4v4l4.84-5M32 16c0 2.21-3.58 4-8 4-.66 0-1.3-.05-1.91-.13l-2.47 2.47c1.26.41 2.76.66 4.38.66 4.42 0 8-1.79 8-4m0 2c0 2.21-3.58 4-8 4-2.28 0-4.33-.5-5.79-1.25l-1.68 1.68C17.68 26.93 20.59 28 24 28c4.42 0 8-1.79 8-4'/></svg>",
"folder-mjml-open": "<svg viewBox='0 0 32 32'><path fill='#ff5722' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><rect width='12' height='4' x='14' y='24' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='20' y='18' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='14' y='12' fill='#ffccbc' rx='2'/><circle cx='30' cy='26' r='2' fill='#ffccbc'/><circle cx='16' cy='20' r='2' fill='#ffccbc'/><circle cx='30' cy='14' r='2' fill='#ffccbc'/></svg>", "folder-mjml-open": "<svg viewBox='0 0 32 32'><path fill='#ff5722' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><rect width='12' height='4' x='14' y='24' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='20' y='18' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='14' y='12' fill='#ffccbc' rx='2'/><circle cx='30' cy='26' r='2' fill='#ffccbc'/><circle cx='16' cy='20' r='2' fill='#ffccbc'/><circle cx='30' cy='14' r='2' fill='#ffccbc'/></svg>",
"folder-mjml": "<svg viewBox='0 0 32 32'><path fill='#ff5722' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><rect width='12' height='4' x='14' y='24' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='20' y='18' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='14' y='12' fill='#ffccbc' rx='2'/><circle cx='30' cy='26' r='2' fill='#ffccbc'/><circle cx='16' cy='20' r='2' fill='#ffccbc'/><circle cx='30' cy='14' r='2' fill='#ffccbc'/></svg>", "folder-mjml": "<svg viewBox='0 0 32 32'><path fill='#ff5722' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><rect width='12' height='4' x='14' y='24' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='20' y='18' fill='#ffccbc' rx='2'/><rect width='12' height='4' x='14' y='12' fill='#ffccbc' rx='2'/><circle cx='30' cy='26' r='2' fill='#ffccbc'/><circle cx='16' cy='20' r='2' fill='#ffccbc'/><circle cx='30' cy='14' r='2' fill='#ffccbc'/></svg>",
"folder-mobile-open": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M12 14v10a2 2 0 0 0 2 2h8v-2h-6V14h12v2h4v-2a2 2 0 0 0-2-2H14a2 2 0 0 0-2 2'/><path fill='#ffcdd2' d='M24 19v8a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1m6 7h-4v-6h4Z'/></svg>", "folder-mobile-open": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M12 14v10a2 2 0 0 0 2 2h8v-2h-6V14h12v2h4v-2a2 2 0 0 0-2-2H14a2 2 0 0 0-2 2'/><path fill='#ffcdd2' d='M24 19v8a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1m6 7h-4v-6h4Z'/></svg>",
@ -604,6 +606,8 @@
"folder-rules": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M30 14h-2a3 3 0 0 0-6 0h-2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2m-5-1a1 1 0 1 1-1 1 1.003 1.003 0 0 1 1-1m-1.547 11.597-3.093-3.093 1.09-1.09 2.003 1.995 5.096-5.096 1.091 1.099Z'/></svg>", "folder-rules": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M30 14h-2a3 3 0 0 0-6 0h-2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2m-5-1a1 1 0 1 1-1 1 1.003 1.003 0 0 1 1-1m-1.547 11.597-3.093-3.093 1.09-1.09 2.003 1.995 5.096-5.096 1.091 1.099Z'/></svg>",
"folder-rust-open": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>", "folder-rust-open": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>",
"folder-rust": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>", "folder-rust": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>",
"folder-salt-open": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M12 20v8h10l4-8h6v-8H22l-4 8z'/></svg>",
"folder-salt": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M12 20v8h10l4-8h6v-8H22l-4 8z'/></svg>",
"folder-sandbox-open": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='M28.965 12.001H9.44a2 2 0 0 0-1.898 1.368L3.998 24.001v-14h24a2 2 0 0 0-2-2H15.122a2 2 0 0 1-1.28-.464l-1.288-1.072a2 2 0 0 0-1.28-.464H3.998a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212a2 2 0 0 0-1.838-2.788'/><path fill='#bbdefb' d='M21 20h-6a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1m7-8a4 4 0 1 0 4 4 4 4 0 0 0-4-4m.707 17.707 3-3a1 1 0 0 0 0-1.414l-3-3a1 1 0 0 0-1.414 0l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 0 0 1.414 0m-11.581-7.193-2.999 6A1 1 0 0 0 15.001 30H21a1 1 0 0 0 .874-1.486l-2.999-6a1 1 0 0 0-1.748 0'/></svg>", "folder-sandbox-open": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='M28.965 12.001H9.44a2 2 0 0 0-1.898 1.368L3.998 24.001v-14h24a2 2 0 0 0-2-2H15.122a2 2 0 0 1-1.28-.464l-1.288-1.072a2 2 0 0 0-1.28-.464H3.998a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212a2 2 0 0 0-1.838-2.788'/><path fill='#bbdefb' d='M21 20h-6a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1m7-8a4 4 0 1 0 4 4 4 4 0 0 0-4-4m.707 17.707 3-3a1 1 0 0 0 0-1.414l-3-3a1 1 0 0 0-1.414 0l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 0 0 1.414 0m-11.581-7.193-2.999 6A1 1 0 0 0 15.001 30H21a1 1 0 0 0 .874-1.486l-2.999-6a1 1 0 0 0-1.748 0'/></svg>",
"folder-sandbox": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M21 20h-6a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1m7-8a4 4 0 1 0 4 4 4 4 0 0 0-4-4m.707 17.707 3-3a1 1 0 0 0 0-1.414l-3-3a1 1 0 0 0-1.414 0l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 0 0 1.414 0m-11.581-7.193-2.999 6A1 1 0 0 0 15.001 30H21a1 1 0 0 0 .874-1.486l-2.999-6a1 1 0 0 0-1.748 0'/></svg>", "folder-sandbox": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M21 20h-6a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1m7-8a4 4 0 1 0 4 4 4 4 0 0 0-4-4m.707 17.707 3-3a1 1 0 0 0 0-1.414l-3-3a1 1 0 0 0-1.414 0l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 0 0 1.414 0m-11.581-7.193-2.999 6A1 1 0 0 0 15.001 30H21a1 1 0 0 0 .874-1.486l-2.999-6a1 1 0 0 0-1.748 0'/></svg>",
"folder-sass-open": "<svg viewBox='0 0 32 32'><path fill='#f06292' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fce4ec' d='M31.897 12.592a3 3 0 0 0-1.53-1.912 7.95 7.95 0 0 0-6.348-.05 17.4 17.4 0 0 0-5.864 3.557c-1.83 1.81-2.288 3.496-2.124 4.39.346 1.89 2.181 3.227 3.658 4.3.314.23.618.45.876.657-.92.513-2.916 1.749-3.483 3.074a2.9 2.9 0 0 0-.074 2.347 1.57 1.57 0 0 0 .874.903 3.5 3.5 0 0 0 .986.142 4.14 4.14 0 0 0 3.438-2.025 5.03 5.03 0 0 0 .55-3.886 4.5 4.5 0 0 1 1.46-.034 2.64 2.64 0 0 1 1.927.96 1.44 1.44 0 0 1 .304.968 1.2 1.2 0 0 1-.55.805c-.159.104-.356.233-.31.504.028.151.13.393.532.313a1.99 1.99 0 0 0 1.392-1.841 2.9 2.9 0 0 0-.801-2.051 3.9 3.9 0 0 0-2.897-1.135 6.5 6.5 0 0 0-1.813.226 13 13 0 0 0-1.498-1.346c-1.165-.947-2.265-1.842-2.2-3.125.085-1.654 1.672-3.306 4.716-4.909 2.7-1.422 4.894-1.47 6.04-1.041a1.44 1.44 0 0 1 .858.674 2.23 2.23 0 0 1-.257 1.866 6.57 6.57 0 0 1-5.023 3.105 2.56 2.56 0 0 1-2.225-.565c-.189-.219-.37-.423-.65-.263-.332.196-.175.625-.123.768a2.6 2.6 0 0 0 1.578 1.342 7.32 7.32 0 0 0 4.752-.482c2.631-1.078 4.384-3.933 3.83-6.236ZM21.301 26.118a3 3 0 0 1-.13.345 3.4 3.4 0 0 1-.517.795c-.648.743-1.499.978-1.776.808a.27.27 0 0 1-.088-.187 2.5 2.5 0 0 1 .742-1.704 7.8 7.8 0 0 1 1.865-1.445 3.05 3.05 0 0 1-.096 1.388'/></svg>", "folder-sass-open": "<svg viewBox='0 0 32 32'><path fill='#f06292' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fce4ec' d='M31.897 12.592a3 3 0 0 0-1.53-1.912 7.95 7.95 0 0 0-6.348-.05 17.4 17.4 0 0 0-5.864 3.557c-1.83 1.81-2.288 3.496-2.124 4.39.346 1.89 2.181 3.227 3.658 4.3.314.23.618.45.876.657-.92.513-2.916 1.749-3.483 3.074a2.9 2.9 0 0 0-.074 2.347 1.57 1.57 0 0 0 .874.903 3.5 3.5 0 0 0 .986.142 4.14 4.14 0 0 0 3.438-2.025 5.03 5.03 0 0 0 .55-3.886 4.5 4.5 0 0 1 1.46-.034 2.64 2.64 0 0 1 1.927.96 1.44 1.44 0 0 1 .304.968 1.2 1.2 0 0 1-.55.805c-.159.104-.356.233-.31.504.028.151.13.393.532.313a1.99 1.99 0 0 0 1.392-1.841 2.9 2.9 0 0 0-.801-2.051 3.9 3.9 0 0 0-2.897-1.135 6.5 6.5 0 0 0-1.813.226 13 13 0 0 0-1.498-1.346c-1.165-.947-2.265-1.842-2.2-3.125.085-1.654 1.672-3.306 4.716-4.909 2.7-1.422 4.894-1.47 6.04-1.041a1.44 1.44 0 0 1 .858.674 2.23 2.23 0 0 1-.257 1.866 6.57 6.57 0 0 1-5.023 3.105 2.56 2.56 0 0 1-2.225-.565c-.189-.219-.37-.423-.65-.263-.332.196-.175.625-.123.768a2.6 2.6 0 0 0 1.578 1.342 7.32 7.32 0 0 0 4.752-.482c2.631-1.078 4.384-3.933 3.83-6.236ZM21.301 26.118a3 3 0 0 1-.13.345 3.4 3.4 0 0 1-.517.795c-.648.743-1.499.978-1.776.808a.27.27 0 0 1-.088-.187 2.5 2.5 0 0 1 .742-1.704 7.8 7.8 0 0 1 1.865-1.445 3.05 3.05 0 0 1-.096 1.388'/></svg>",
@ -1024,6 +1028,7 @@
"ruff": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M26 16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4v24h10v-8h2v8h12V18h-6v-2Zm-8-2h-6v-2h6Z'/></svg>", "ruff": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M26 16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4v24h10v-8h2v8h12V18h-6v-2Zm-8-2h-6v-2h6Z'/></svg>",
"rust": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m30 12-4-2V6h-4l-2-4-4 2-4-2-2 4H6v4l-4 2 2 4-2 4 4 2v4h4l2 4 4-2 4 2 2-4h4v-4l4-2-2-4ZM6 16a9.9 9.9 0 0 1 .842-4H10v8H6.842A9.9 9.9 0 0 1 6 16m10 10a9.98 9.98 0 0 1-7.978-4H16v-2h-2v-2h4c.819.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63h3.34A9.98 9.98 0 0 1 16 26m-2-12v-2h4a1 1 0 0 1 0 2Zm11.158 6H24a2.006 2.006 0 0 1-2-2 2 2 0 0 0-2-2 3 3 0 0 0 3-3q0-.08-.004-.161A3.115 3.115 0 0 0 19.83 10H8.022a9.986 9.986 0 0 1 17.136 10'/></svg>", "rust": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m30 12-4-2V6h-4l-2-4-4 2-4-2-2 4H6v4l-4 2 2 4-2 4 4 2v4h4l2 4 4-2 4 2 2-4h4v-4l4-2-2-4ZM6 16a9.9 9.9 0 0 1 .842-4H10v8H6.842A9.9 9.9 0 0 1 6 16m10 10a9.98 9.98 0 0 1-7.978-4H16v-2h-2v-2h4c.819.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63h3.34A9.98 9.98 0 0 1 16 26m-2-12v-2h4a1 1 0 0 1 0 2Zm11.158 6H24a2.006 2.006 0 0 1-2-2 2 2 0 0 0-2-2 3 3 0 0 0 3-3q0-.08-.004-.161A3.115 3.115 0 0 0 19.83 10H8.022a9.986 9.986 0 0 1 17.136 10'/></svg>",
"salesforce": "<svg viewBox='0 0 24 24'><path fill='#039be5' d='M18.206 6.522c-.681 0-1.275.204-1.858.399-.681-1.177-1.956-1.956-3.328-1.956-1.07 0-2.043.487-2.734 1.168-.779-.973-1.956-1.664-3.318-1.664-2.267 0-4.213 1.858-4.213 4.126 0 .574.204 1.157.399 1.741a3.58 3.58 0 0 0-1.859 3.124c0 1.946 1.567 3.62 3.523 3.62.292 0 .583 0 .778-.098.39 1.469 1.858 2.55 3.62 2.55 1.654 0 3.026-.984 3.512-2.356.496.205.983.4 1.47.4 1.274 0 2.442-.71 3.025-1.762.302.078.613.078.886.078 2.54 0 4.592-2.034 4.592-4.67.098-2.627-1.946-4.7-4.495-4.7'/></svg>", "salesforce": "<svg viewBox='0 0 24 24'><path fill='#039be5' d='M18.206 6.522c-.681 0-1.275.204-1.858.399-.681-1.177-1.956-1.956-3.328-1.956-1.07 0-2.043.487-2.734 1.168-.779-.973-1.956-1.664-3.318-1.664-2.267 0-4.213 1.858-4.213 4.126 0 .574.204 1.157.399 1.741a3.58 3.58 0 0 0-1.859 3.124c0 1.946 1.567 3.62 3.523 3.62.292 0 .583 0 .778-.098.39 1.469 1.858 2.55 3.62 2.55 1.654 0 3.026-.984 3.512-2.356.496.205.983.4 1.47.4 1.274 0 2.442-.71 3.025-1.762.302.078.613.078.886.078 2.54 0 4.592-2.034 4.592-4.67.098-2.627-1.946-4.7-4.495-4.7'/></svg>",
"salt": "<svg viewBox='0 0 16 16'><path fill='#03a9f4' d='M1 8v6h7l3-6h4V2H8L5 8z'/></svg>",
"san": "<svg viewBox='0 0 32 32'><path fill='#01579b' d='M28 17.898 4 23.316V30l24-5.418Zm0-10.623L4 12.694v6.683l24-5.418Z'/><path fill='#b3e5fc' d='M28 13.926 4 8.684V2l24 5.242Zm0 10.623L4 19.307v-6.684l24 5.242Z'/></svg>", "san": "<svg viewBox='0 0 32 32'><path fill='#01579b' d='M28 17.898 4 23.316V30l24-5.418Zm0-10.623L4 12.694v6.683l24-5.418Z'/><path fill='#b3e5fc' d='M28 13.926 4 8.684V2l24 5.242Zm0 10.623L4 19.307v-6.684l24 5.242Z'/></svg>",
"sas": "<svg viewBox='0 0 6.35 6.35'><path fill='#039be5' d='M3.16.546a1.9 1.9 0 0 0-.667.13c-.378.157-.678.4-.846.74-.255.452-.296 1.032.04 1.536.31.474.72.908 1.148 1.407.382.139.572-.272.515-.42-.276-.35-.57-.695-.804-1.06a1.1 1.1 0 0 1-.07-.906c.075-.198.503-.84 1.372-.836.345.04.658.059 1.086.53a1.6 1.6 0 0 0-.536-.753A2.05 2.05 0 0 0 3.159.546zm.245 1.4c-.277.002-.406.227-.348.437.253.365.578.748.869 1.122.316.609.092 1.077-.512 1.472-.82.458-1.576.116-2.026-.298.234.489.423.636.471.679.31.27.81.527 1.62.423.456-.102 1.06-.256 1.396-1.097.093-.265.125-.54.03-.843-.09-.338-.274-.58-.466-.829-.3-.351-.77-.979-.906-1.053a.6.6 0 0 0-.128-.013'/></svg>", "sas": "<svg viewBox='0 0 6.35 6.35'><path fill='#039be5' d='M3.16.546a1.9 1.9 0 0 0-.667.13c-.378.157-.678.4-.846.74-.255.452-.296 1.032.04 1.536.31.474.72.908 1.148 1.407.382.139.572-.272.515-.42-.276-.35-.57-.695-.804-1.06a1.1 1.1 0 0 1-.07-.906c.075-.198.503-.84 1.372-.836.345.04.658.059 1.086.53a1.6 1.6 0 0 0-.536-.753A2.05 2.05 0 0 0 3.159.546zm.245 1.4c-.277.002-.406.227-.348.437.253.365.578.748.869 1.122.316.609.092 1.077-.512 1.472-.82.458-1.576.116-2.026-.298.234.489.423.636.471.679.31.27.81.527 1.62.423.456-.102 1.06-.256 1.396-1.097.093-.265.125-.54.03-.843-.09-.338-.274-.58-.466-.829-.3-.351-.77-.979-.906-1.053a.6.6 0 0 0-.128-.013'/></svg>",
"sass": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M27.837 5.673a4.33 4.33 0 0 0-2.293-2.701c-2.362-1.261-6.11-1.298-9.548-.092a26.3 26.3 0 0 0-8.76 4.966c-2.752 2.542-3.438 4.925-3.189 6.194.523 2.668 3.274 4.539 5.485 6.042.418.284.822.559 1.175.816-1.429.76-4.261 2.444-5.088 4.248a3.88 3.88 0 0 0-.118 3.332A2.37 2.37 0 0 0 6.869 29.8a5.6 5.6 0 0 0 1.49.2 6.35 6.35 0 0 0 5.19-2.856 6.74 6.74 0 0 0 .864-5.382 7.3 7.3 0 0 1 2.044-.03 3.92 3.92 0 0 1 2.816 1.311 1.82 1.82 0 0 1 .423 1.262 1.55 1.55 0 0 1-.772 1.05c-.234.14-.586.355-.504.803.036.194.198.633.894.512a2.93 2.93 0 0 0 2.145-2.651 4 4 0 0 0-1.197-2.904 5.94 5.94 0 0 0-4.396-1.626 10.6 10.6 0 0 0-2.672.304 20 20 0 0 0-2.203-1.846c-1.712-1.3-3.33-2.529-3.235-4.26.125-2.263 2.468-4.532 6.964-6.744 4.016-1.976 7.254-2.037 8.944-1.438a2 2 0 0 1 1.204.883 2.77 2.77 0 0 1-.36 2.47 9.71 9.71 0 0 1-7.425 4.304 3.86 3.86 0 0 1-3.238-.757c-.278-.302-.593-.645-1.074-.383q-.565.31-.225 1.189a3.9 3.9 0 0 0 2.407 1.92 11.7 11.7 0 0 0 7.128-.671c3.527-1.35 6.681-5.202 5.756-8.787M11.895 24.475a4 4 0 0 1-.192.468 4.5 4.5 0 0 1-.753 1.081 2.83 2.83 0 0 1-2.533 1.107c-.056-.032-.078-.146-.085-.193a3.28 3.28 0 0 1 1.076-2.284 11.3 11.3 0 0 1 2.644-1.933 3.85 3.85 0 0 1-.157 1.754'/></svg>", "sass": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M27.837 5.673a4.33 4.33 0 0 0-2.293-2.701c-2.362-1.261-6.11-1.298-9.548-.092a26.3 26.3 0 0 0-8.76 4.966c-2.752 2.542-3.438 4.925-3.189 6.194.523 2.668 3.274 4.539 5.485 6.042.418.284.822.559 1.175.816-1.429.76-4.261 2.444-5.088 4.248a3.88 3.88 0 0 0-.118 3.332A2.37 2.37 0 0 0 6.869 29.8a5.6 5.6 0 0 0 1.49.2 6.35 6.35 0 0 0 5.19-2.856 6.74 6.74 0 0 0 .864-5.382 7.3 7.3 0 0 1 2.044-.03 3.92 3.92 0 0 1 2.816 1.311 1.82 1.82 0 0 1 .423 1.262 1.55 1.55 0 0 1-.772 1.05c-.234.14-.586.355-.504.803.036.194.198.633.894.512a2.93 2.93 0 0 0 2.145-2.651 4 4 0 0 0-1.197-2.904 5.94 5.94 0 0 0-4.396-1.626 10.6 10.6 0 0 0-2.672.304 20 20 0 0 0-2.203-1.846c-1.712-1.3-3.33-2.529-3.235-4.26.125-2.263 2.468-4.532 6.964-6.744 4.016-1.976 7.254-2.037 8.944-1.438a2 2 0 0 1 1.204.883 2.77 2.77 0 0 1-.36 2.47 9.71 9.71 0 0 1-7.425 4.304 3.86 3.86 0 0 1-3.238-.757c-.278-.302-.593-.645-1.074-.383q-.565.31-.225 1.189a3.9 3.9 0 0 0 2.407 1.92 11.7 11.7 0 0 0 7.128-.671c3.527-1.35 6.681-5.202 5.756-8.787M11.895 24.475a4 4 0 0 1-.192.468 4.5 4.5 0 0 1-.753 1.081 2.83 2.83 0 0 1-2.533 1.107c-.056-.032-.078-.146-.085-.193a3.28 3.28 0 0 1 1.076-2.284 11.3 11.3 0 0 1 2.644-1.933 3.85 3.85 0 0 1-.157 1.754'/></svg>",

View File

@ -978,6 +978,7 @@
"repo.fork.blocked_user": "Cannot fork the repository because you are blocked by the repository owner.", "repo.fork.blocked_user": "Cannot fork the repository because you are blocked by the repository owner.",
"repo.use_template": "Use this template", "repo.use_template": "Use this template",
"repo.open_with_editor": "Open with %s", "repo.open_with_editor": "Open with %s",
"repo.download_directory_as": "Download directory as %s",
"repo.download_zip": "Download ZIP", "repo.download_zip": "Download ZIP",
"repo.download_tar": "Download TAR.GZ", "repo.download_tar": "Download TAR.GZ",
"repo.download_bundle": "Download BUNDLE", "repo.download_bundle": "Download BUNDLE",
@ -1848,7 +1849,8 @@
"repo.pulls.status_checking": "Some checks are pending", "repo.pulls.status_checking": "Some checks are pending",
"repo.pulls.status_checks_success": "All checks were successful", "repo.pulls.status_checks_success": "All checks were successful",
"repo.pulls.status_checks_warning": "Some checks reported warnings", "repo.pulls.status_checks_warning": "Some checks reported warnings",
"repo.pulls.status_checks_failure": "Some checks failed", "repo.pulls.status_checks_failure_required": "Some required checks failed",
"repo.pulls.status_checks_failure_optional": "Some optional checks failed",
"repo.pulls.status_checks_error": "Some checks reported errors", "repo.pulls.status_checks_error": "Some checks reported errors",
"repo.pulls.status_checks_requested": "Required", "repo.pulls.status_checks_requested": "Required",
"repo.pulls.status_checks_details": "Details", "repo.pulls.status_checks_details": "Details",
@ -2543,8 +2545,8 @@
"repo.diff.too_many_files": "Some files were not shown because too many files have changed in this diff", "repo.diff.too_many_files": "Some files were not shown because too many files have changed in this diff",
"repo.diff.show_more": "Show More", "repo.diff.show_more": "Show More",
"repo.diff.load": "Load Diff", "repo.diff.load": "Load Diff",
"repo.diff.generated": "generated", "repo.diff.generated": "Generated",
"repo.diff.vendored": "vendored", "repo.diff.vendored": "Vendored",
"repo.diff.comment.add_line_comment": "Add line comment", "repo.diff.comment.add_line_comment": "Add line comment",
"repo.diff.comment.placeholder": "Leave a comment", "repo.diff.comment.placeholder": "Leave a comment",
"repo.diff.comment.add_single_comment": "Add single comment", "repo.diff.comment.add_single_comment": "Add single comment",
@ -3725,9 +3727,9 @@
"projects.exit_fullscreen": "Exit Fullscreen", "projects.exit_fullscreen": "Exit Fullscreen",
"git.filemode.changed_filemode": "%[1]s → %[2]s", "git.filemode.changed_filemode": "%[1]s → %[2]s",
"git.filemode.directory": "Directory", "git.filemode.directory": "Directory",
"git.filemode.normal_file": "Normal file", "git.filemode.normal_file": "Regular",
"git.filemode.executable_file": "Executable file", "git.filemode.executable_file": "Executable",
"git.filemode.symbolic_link": "Symbolic link", "git.filemode.symbolic_link": "Symlink",
"git.filemode.submodule": "Submodule", "git.filemode.submodule": "Submodule",
"actions.general.token_permissions.title": "Action Token Permissions", "actions.general.token_permissions.title": "Action Token Permissions",
"actions.general.token_permissions.desc": "Configure the default permissions for the GITEA_TOKEN running in this repository.", "actions.general.token_permissions.desc": "Configure the default permissions for the GITEA_TOKEN running in this repository.",

View File

@ -32,6 +32,7 @@
"password": "Pasfhocal", "password": "Pasfhocal",
"access_token": "Comhartha Rochtana", "access_token": "Comhartha Rochtana",
"re_type": "Deimhnigh Pasfhocal", "re_type": "Deimhnigh Pasfhocal",
"captcha": "CAPTCHA",
"twofa": "Fíordheimhniú Dhá-Fhachtóir", "twofa": "Fíordheimhniú Dhá-Fhachtóir",
"twofa_scratch": "Cód Scratch Dhá-Fhachtóra", "twofa_scratch": "Cód Scratch Dhá-Fhachtóra",
"passcode": "Paschód", "passcode": "Paschód",
@ -132,6 +133,7 @@
"confirm_delete_selected": "Deimhnigh chun gach earra roghnaithe a scriosadh?", "confirm_delete_selected": "Deimhnigh chun gach earra roghnaithe a scriosadh?",
"name": "Ainm", "name": "Ainm",
"value": "Luach", "value": "Luach",
"readme": "Léigh-mé",
"filter_title": "Scagaire", "filter_title": "Scagaire",
"filter.clear": "Scagaire Soiléir", "filter.clear": "Scagaire Soiléir",
"filter.is_archived": "Cartlannaithe", "filter.is_archived": "Cartlannaithe",
@ -229,6 +231,7 @@
"install.db_name": "Ainm Bunachar Sonraí", "install.db_name": "Ainm Bunachar Sonraí",
"install.db_schema": "Scéim", "install.db_schema": "Scéim",
"install.db_schema_helper": "Fág bán le haghaidh réamhshocraithe bunachar sonraí (\"poiblí\").", "install.db_schema_helper": "Fág bán le haghaidh réamhshocraithe bunachar sonraí (\"poiblí\").",
"install.ssl_mode": "SSL",
"install.path": "Cosán", "install.path": "Cosán",
"install.sqlite_helper": "Conair comhad don bhunachar sonraí SQLite3. Cuir <br>isteach cosán iomlán má reáchtáil tú Gitea mar sheirbhís.", "install.sqlite_helper": "Conair comhad don bhunachar sonraí SQLite3. Cuir <br>isteach cosán iomlán má reáchtáil tú Gitea mar sheirbhís.",
"install.reinstall_error": "Tá tú ag iarraidh a shuiteáil i mbunachar sonraí Gitea atá ann cheana", "install.reinstall_error": "Tá tú ag iarraidh a shuiteáil i mbunachar sonraí Gitea atá ann cheana",
@ -406,6 +409,7 @@
"auth.twofa_scratch_token_incorrect": "Tá do chód scratch mícheart.", "auth.twofa_scratch_token_incorrect": "Tá do chód scratch mícheart.",
"auth.twofa_required": "Ní mór duit fíordheimhniú dhá fhachtóir a shocrú chun rochtain a fháil ar stórtha, nó iarracht a dhéanamh logáil isteach arís.", "auth.twofa_required": "Ní mór duit fíordheimhniú dhá fhachtóir a shocrú chun rochtain a fháil ar stórtha, nó iarracht a dhéanamh logáil isteach arís.",
"auth.login_userpass": "Sínigh isteach", "auth.login_userpass": "Sínigh isteach",
"auth.login_openid": "OpenID",
"auth.oauth_signup_tab": "Cláraigh Cuntas Nua", "auth.oauth_signup_tab": "Cláraigh Cuntas Nua",
"auth.oauth_signup_title": "Comhlánaigh Cuntas Nua", "auth.oauth_signup_title": "Comhlánaigh Cuntas Nua",
"auth.oauth_signup_submit": "Cuntas Comhlánaigh", "auth.oauth_signup_submit": "Cuntas Comhlánaigh",
@ -654,6 +658,7 @@
"settings.twofa": "Fíordheimhniú Dhá Fachtóir (TOTP)", "settings.twofa": "Fíordheimhniú Dhá Fachtóir (TOTP)",
"settings.account_link": "Cuntais Nasctha", "settings.account_link": "Cuntais Nasctha",
"settings.organization": "Eagraíochtaí", "settings.organization": "Eagraíochtaí",
"settings.uid": "UID",
"settings.webauthn": "Fíordheimhniú Dhá-Fachtóir (Eochracha Slándála)", "settings.webauthn": "Fíordheimhniú Dhá-Fachtóir (Eochracha Slándála)",
"settings.public_profile": "Próifíl Phoiblí", "settings.public_profile": "Próifíl Phoiblí",
"settings.biography_placeholder": "Inis dúinn beagán fút féin! (Is féidir leat Markdown a úsáid)", "settings.biography_placeholder": "Inis dúinn beagán fút féin! (Is féidir leat Markdown a úsáid)",
@ -991,6 +996,7 @@
"repo.multiple_licenses": "Ceadúnais Iolracha", "repo.multiple_licenses": "Ceadúnais Iolracha",
"repo.object_format": "Formáid Oibiacht", "repo.object_format": "Formáid Oibiacht",
"repo.object_format_helper": "Formáid oibiacht an stór. Ní féidir é a athrú níos déanaí. Is é SHA1 an comhoiriúnacht is fearr.", "repo.object_format_helper": "Formáid oibiacht an stór. Ní féidir é a athrú níos déanaí. Is é SHA1 an comhoiriúnacht is fearr.",
"repo.readme": "LÉIGHMÉ",
"repo.readme_helper": "Roghnaigh comhad teimpléad README.", "repo.readme_helper": "Roghnaigh comhad teimpléad README.",
"repo.readme_helper_desc": "Seo an áit inar féidir leat cur síos iomlán a scríobh do thionscadal.", "repo.readme_helper_desc": "Seo an áit inar féidir leat cur síos iomlán a scríobh do thionscadal.",
"repo.auto_init": "Taisce a thionscnamh (Cuireann sé .gitignore, Ceadúnas agus README)", "repo.auto_init": "Taisce a thionscnamh (Cuireann sé .gitignore, Ceadúnas agus README)",
@ -1055,6 +1061,7 @@
"repo.desc.template": "Teimpléad", "repo.desc.template": "Teimpléad",
"repo.desc.internal": "Inmheánach", "repo.desc.internal": "Inmheánach",
"repo.desc.archived": "Cartlannaithe", "repo.desc.archived": "Cartlannaithe",
"repo.desc.sha256": "SHA256",
"repo.template.items": "Míreanna Teimpléad", "repo.template.items": "Míreanna Teimpléad",
"repo.template.git_content": "Ábhar Git (Brainse Réamhshocraithe)", "repo.template.git_content": "Ábhar Git (Brainse Réamhshocraithe)",
"repo.template.git_hooks": "Crúcanna Git", "repo.template.git_hooks": "Crúcanna Git",
@ -1083,6 +1090,7 @@
"repo.migrate_options_lfs_endpoint.description.local": "Tacaítear le cosán freastalaí áitiúil freisin.", "repo.migrate_options_lfs_endpoint.description.local": "Tacaítear le cosán freastalaí áitiúil freisin.",
"repo.migrate_options_lfs_endpoint.placeholder": "Mura bhfágtar bán é, díorthófar an críochphointe ón URL clónála.", "repo.migrate_options_lfs_endpoint.placeholder": "Mura bhfágtar bán é, díorthófar an críochphointe ón URL clónála.",
"repo.migrate_items": "Míreanna Imirce", "repo.migrate_items": "Míreanna Imirce",
"repo.migrate_items_wiki": "Vicí",
"repo.migrate_items_milestones": "Clocha míle", "repo.migrate_items_milestones": "Clocha míle",
"repo.migrate_items_labels": "Lipéid", "repo.migrate_items_labels": "Lipéid",
"repo.migrate_items_issues": "Saincheisteanna", "repo.migrate_items_issues": "Saincheisteanna",
@ -1728,8 +1736,11 @@
"repo.issues.reference_link": "Tagairt: %s", "repo.issues.reference_link": "Tagairt: %s",
"repo.compare.compare_base": "bonn", "repo.compare.compare_base": "bonn",
"repo.compare.compare_head": "déan comparáid", "repo.compare.compare_head": "déan comparáid",
"repo.compare.title": "Athruithe a chur i gcomparáid",
"repo.compare.description": "Roghnaigh dhá bhrainse nó clib chun a fheiceáil cad atá athraithe nó chun iarratas tarraingthe nua a thosú.",
"repo.pulls.desc": "Cumasaigh iarratais tarraingthe agus athbhreithnithe cód.", "repo.pulls.desc": "Cumasaigh iarratais tarraingthe agus athbhreithnithe cód.",
"repo.pulls.new": "Iarratas Tarraingthe Nua", "repo.pulls.new": "Iarratas Tarraingthe Nua",
"repo.pulls.new.description": "Pléigh agus athbhreithnigh na hathruithe sa chomparáid seo le daoine eile.",
"repo.pulls.new.blocked_user": "Ní féidir iarratas tarraingthe a chruthú toisc go bhfuil úinéir an stórais bac ort.", "repo.pulls.new.blocked_user": "Ní féidir iarratas tarraingthe a chruthú toisc go bhfuil úinéir an stórais bac ort.",
"repo.pulls.new.must_collaborator": "Caithfidh tú a bheith ina chomhoibritheoir chun iarratas tarraingthe a chruthú.", "repo.pulls.new.must_collaborator": "Caithfidh tú a bheith ina chomhoibritheoir chun iarratas tarraingthe a chruthú.",
"repo.pulls.new.already_existed": "Tá iarratas tarraingthe idir na brainsí seo ann cheana féin", "repo.pulls.new.already_existed": "Tá iarratas tarraingthe idir na brainsí seo ann cheana féin",
@ -1739,7 +1750,6 @@
"repo.pulls.allow_edits_from_maintainers": "Ceadaigh eagarthóirí ó chothabhálaí", "repo.pulls.allow_edits_from_maintainers": "Ceadaigh eagarthóirí ó chothabhálaí",
"repo.pulls.allow_edits_from_maintainers_desc": "Is féidir le húsáideoirí a bhfuil rochtain scríofa acu ar an mbunbhrainse brú chuig an bhrainse", "repo.pulls.allow_edits_from_maintainers_desc": "Is féidir le húsáideoirí a bhfuil rochtain scríofa acu ar an mbunbhrainse brú chuig an bhrainse",
"repo.pulls.allow_edits_from_maintainers_err": "Theip ar nuashonrú", "repo.pulls.allow_edits_from_maintainers_err": "Theip ar nuashonrú",
"repo.pulls.compare_changes_desc": "Roghnaigh an brainse le cumasc isteach agus an brainse le tarraingt uaidh.",
"repo.pulls.has_viewed_file": "Breathnaithe", "repo.pulls.has_viewed_file": "Breathnaithe",
"repo.pulls.has_changed_since_last_review": "Athraithe ó d'athbhreithniú deire", "repo.pulls.has_changed_since_last_review": "Athraithe ó d'athbhreithniú deire",
"repo.pulls.viewed_files_label": "Breathnaíodh ar %[1]d / %[2]d comhaid", "repo.pulls.viewed_files_label": "Breathnaíodh ar %[1]d / %[2]d comhaid",
@ -2313,8 +2323,19 @@
"repo.settings.slack_domain": "Fearann", "repo.settings.slack_domain": "Fearann",
"repo.settings.slack_channel": "Cainéal", "repo.settings.slack_channel": "Cainéal",
"repo.settings.add_web_hook_desc": "Comhtháthaigh <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">%s</a> isteach i do stóras.", "repo.settings.add_web_hook_desc": "Comhtháthaigh <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">%s</a> isteach i do stóras.",
"repo.settings.web_hook_name_gitea": "Gitea",
"repo.settings.web_hook_name_gogs": "Gogs",
"repo.settings.web_hook_name_slack": "Slack",
"repo.settings.web_hook_name_discord": "Discord",
"repo.settings.web_hook_name_dingtalk": "DingTalk",
"repo.settings.web_hook_name_telegram": "Teileagram", "repo.settings.web_hook_name_telegram": "Teileagram",
"repo.settings.web_hook_name_matrix": "Maitrís", "repo.settings.web_hook_name_matrix": "Maitrís",
"repo.settings.web_hook_name_msteams": "Microsoft Teams",
"repo.settings.web_hook_name_feishu_or_larksuite": "Feishu / Lark Suite",
"repo.settings.web_hook_name_feishu": "Feishu",
"repo.settings.web_hook_name_larksuite": "Lark Suite",
"repo.settings.web_hook_name_wechatwork": "WeCom (Wechat Work)",
"repo.settings.web_hook_name_packagist": "Packagist",
"repo.settings.packagist_username": "Ainm úsáideora Pacagist", "repo.settings.packagist_username": "Ainm úsáideora Pacagist",
"repo.settings.packagist_api_token": "Comhartha API", "repo.settings.packagist_api_token": "Comhartha API",
"repo.settings.packagist_package_url": "URL pacáiste Packagist", "repo.settings.packagist_package_url": "URL pacáiste Packagist",
@ -2460,6 +2481,7 @@
"repo.settings.unarchive.success": "Rinneadh an stóras a dhíchartlann go rathúil.", "repo.settings.unarchive.success": "Rinneadh an stóras a dhíchartlann go rathúil.",
"repo.settings.unarchive.error": "Tharla earráid agus tú ag iarraidh an stóras a dhíchartlannú. Féach an logáil le haghaidh tuilleadh sonraí.", "repo.settings.unarchive.error": "Tharla earráid agus tú ag iarraidh an stóras a dhíchartlannú. Féach an logáil le haghaidh tuilleadh sonraí.",
"repo.settings.update_avatar_success": "Nuashonraíodh avatar an stóras.", "repo.settings.update_avatar_success": "Nuashonraíodh avatar an stóras.",
"repo.settings.lfs": "LFS",
"repo.settings.lfs_filelist": "Comhaid LFS a stóráiltear sa stóras seo", "repo.settings.lfs_filelist": "Comhaid LFS a stóráiltear sa stóras seo",
"repo.settings.lfs_no_lfs_files": "Níl aon chomhaid LFS stóráilte sa stóras seo", "repo.settings.lfs_no_lfs_files": "Níl aon chomhaid LFS stóráilte sa stóras seo",
"repo.settings.lfs_findcommits": "Aimsigh gealltanais", "repo.settings.lfs_findcommits": "Aimsigh gealltanais",
@ -2479,6 +2501,7 @@
"repo.settings.lfs_force_unlock": "Díghlasáil Fórsa", "repo.settings.lfs_force_unlock": "Díghlasáil Fórsa",
"repo.settings.lfs_pointers.found": "Fuarthas %d pointeoir(í) bloba — %d gaolmhar, %d neamhghaolmhar (%d ar iarraidh ón stóras)", "repo.settings.lfs_pointers.found": "Fuarthas %d pointeoir(í) bloba — %d gaolmhar, %d neamhghaolmhar (%d ar iarraidh ón stóras)",
"repo.settings.lfs_pointers.sha": "SHA Blob", "repo.settings.lfs_pointers.sha": "SHA Blob",
"repo.settings.lfs_pointers.oid": "OID",
"repo.settings.lfs_pointers.inRepo": "I Stóras", "repo.settings.lfs_pointers.inRepo": "I Stóras",
"repo.settings.lfs_pointers.exists": "Ann sa siopa", "repo.settings.lfs_pointers.exists": "Ann sa siopa",
"repo.settings.lfs_pointers.accessible": "Inrochtana don Úsáideoir", "repo.settings.lfs_pointers.accessible": "Inrochtana don Úsáideoir",
@ -2844,6 +2867,7 @@
"admin.dashboard.task.finished": "Tasc: Tá %[1]s tosaithe ag %[2]s críochnaithe", "admin.dashboard.task.finished": "Tasc: Tá %[1]s tosaithe ag %[2]s críochnaithe",
"admin.dashboard.task.unknown": "Tasc anaithnid: %[1]s", "admin.dashboard.task.unknown": "Tasc anaithnid: %[1]s",
"admin.dashboard.cron.started": "Cron tosaithe: %[1]s", "admin.dashboard.cron.started": "Cron tosaithe: %[1]s",
"admin.dashboard.cron.process": "Cron: %[1]s",
"admin.dashboard.cron.cancelled": "Cron: %[1]s cealaithe: %[3]s", "admin.dashboard.cron.cancelled": "Cron: %[1]s cealaithe: %[3]s",
"admin.dashboard.cron.error": "Earráid i gCron: %s: %[3]s", "admin.dashboard.cron.error": "Earráid i gCron: %s: %[3]s",
"admin.dashboard.cron.finished": "Cron: %[1]s críochnaithe", "admin.dashboard.cron.finished": "Cron: %[1]s críochnaithe",
@ -2923,6 +2947,7 @@
"admin.users.reserved": "In áirithe", "admin.users.reserved": "In áirithe",
"admin.users.bot": "Bota", "admin.users.bot": "Bota",
"admin.users.remote": "Iargúlta", "admin.users.remote": "Iargúlta",
"admin.users.2fa": "2FA",
"admin.users.repos": "Stórais", "admin.users.repos": "Stórais",
"admin.users.created": "Cruthaithe", "admin.users.created": "Cruthaithe",
"admin.users.last_login": "Sínigh Isteach Deiridh", "admin.users.last_login": "Sínigh Isteach Deiridh",
@ -3044,6 +3069,7 @@
"admin.auths.attribute_mail": "Tréith ríomhphoist", "admin.auths.attribute_mail": "Tréith ríomhphoist",
"admin.auths.attribute_ssh_public_key": "Tréith Eochair SSH Phoiblí", "admin.auths.attribute_ssh_public_key": "Tréith Eochair SSH Phoiblí",
"admin.auths.attribute_avatar": "Tréith Avatar", "admin.auths.attribute_avatar": "Tréith Avatar",
"admin.auths.ssh_keys_are_verified": "Meastar gur fíoraithe iad eochracha SSH in LDAP",
"admin.auths.attributes_in_bind": "Faigh tréithe i gComhthéacs Bind DN", "admin.auths.attributes_in_bind": "Faigh tréithe i gComhthéacs Bind DN",
"admin.auths.allow_deactivate_all": "Lig do thoradh cuardaigh folamh gach úsáideoir a dhíghníomhachtú", "admin.auths.allow_deactivate_all": "Lig do thoradh cuardaigh folamh gach úsáideoir a dhíghníomhachtú",
"admin.auths.use_paged_search": "Úsáid Cuardach Leathanaigh", "admin.auths.use_paged_search": "Úsáid Cuardach Leathanaigh",
@ -3177,6 +3203,7 @@
"admin.config.db_name": "Ainm", "admin.config.db_name": "Ainm",
"admin.config.db_user": "Ainm úsáideora", "admin.config.db_user": "Ainm úsáideora",
"admin.config.db_schema": "Scéim", "admin.config.db_schema": "Scéim",
"admin.config.db_ssl_mode": "SSL",
"admin.config.db_path": "Cosán", "admin.config.db_path": "Cosán",
"admin.config.service_config": "Cumraíocht Seirbhíse", "admin.config.service_config": "Cumraíocht Seirbhíse",
"admin.config.register_email_confirm": "Deimhniú Ríomhphost a éileamh chun Clárú", "admin.config.register_email_confirm": "Deimhniú Ríomhphost a éileamh chun Clárú",
@ -3430,6 +3457,7 @@
"packages.assets": "Sócmhainní", "packages.assets": "Sócmhainní",
"packages.versions": "Leaganacha", "packages.versions": "Leaganacha",
"packages.versions.view_all": "Féach ar gach", "packages.versions.view_all": "Féach ar gach",
"packages.dependency.id": "ID",
"packages.dependency.version": "Leagan", "packages.dependency.version": "Leagan",
"packages.search_in_external_registry": "Cuardaigh i %s", "packages.search_in_external_registry": "Cuardaigh i %s",
"packages.alpine.registry": "Socraigh an clárlann seo tríd an URL a chur i do chomhad <code>/etc/apk/repositories</code>:", "packages.alpine.registry": "Socraigh an clárlann seo tríd an URL a chur i do chomhad <code>/etc/apk/repositories</code>:",
@ -3594,6 +3622,7 @@
"actions.runners.new": "Cruthaigh reathaí nua", "actions.runners.new": "Cruthaigh reathaí nua",
"actions.runners.new_notice": "Conas reathaí a thosú", "actions.runners.new_notice": "Conas reathaí a thosú",
"actions.runners.status": "Stádas", "actions.runners.status": "Stádas",
"actions.runners.id": "ID",
"actions.runners.name": "Ainm", "actions.runners.name": "Ainm",
"actions.runners.owner_type": "Cineál", "actions.runners.owner_type": "Cineál",
"actions.runners.description": "Cur síos", "actions.runners.description": "Cur síos",
@ -3693,6 +3722,7 @@
"projects.type-3.display_name": "Tionscadal Eagrúcháin", "projects.type-3.display_name": "Tionscadal Eagrúcháin",
"projects.enter_fullscreen": "Lánscáileán", "projects.enter_fullscreen": "Lánscáileán",
"projects.exit_fullscreen": "Scoir Lánscáileáin", "projects.exit_fullscreen": "Scoir Lánscáileáin",
"git.filemode.changed_filemode": "%[1]s → %[2]s",
"git.filemode.directory": "Eolaire", "git.filemode.directory": "Eolaire",
"git.filemode.normal_file": "Comhad gnáth", "git.filemode.normal_file": "Comhad gnáth",
"git.filemode.executable_file": "Comhad infheidhmithe", "git.filemode.executable_file": "Comhad infheidhmithe",

View File

@ -1,6 +1,6 @@
{ {
"type": "module", "type": "module",
"packageManager": "pnpm@10.26.0", "packageManager": "pnpm@10.28.0",
"engines": { "engines": {
"node": ">= 22.6.0", "node": ">= 22.6.0",
"pnpm": ">= 10.0.0" "pnpm": ">= 10.0.0"
@ -8,20 +8,20 @@
"dependencies": { "dependencies": {
"@citation-js/core": "0.7.21", "@citation-js/core": "0.7.21",
"@citation-js/plugin-bibtex": "0.7.21", "@citation-js/plugin-bibtex": "0.7.21",
"@citation-js/plugin-csl": "0.7.21", "@citation-js/plugin-csl": "0.7.22",
"@citation-js/plugin-software-formats": "0.6.1", "@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.3", "@github/markdown-toolbar-element": "2.2.3",
"@github/paste-markdown": "1.5.3", "@github/paste-markdown": "1.5.3",
"@github/relative-time-element": "5.0.0", "@github/relative-time-element": "5.0.0",
"@github/text-expander-element": "2.9.2", "@github/text-expander-element": "2.9.4",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.21.1", "@primer/octicons": "19.21.2",
"@resvg/resvg-wasm": "2.6.2", "@resvg/resvg-wasm": "2.6.2",
"@silverwind/vue3-calendar-heatmap": "2.1.1", "@silverwind/vue3-calendar-heatmap": "2.1.1",
"@techknowlogick/license-checker-webpack-plugin": "0.3.0", "@techknowlogick/license-checker-webpack-plugin": "0.3.0",
"add-asset-webpack-plugin": "3.1.1", "add-asset-webpack-plugin": "3.1.1",
"ansi_up": "6.0.6", "ansi_up": "6.0.6",
"asciinema-player": "3.13.5", "asciinema-player": "3.14.0",
"chart.js": "4.5.1", "chart.js": "4.5.1",
"chartjs-adapter-dayjs-4": "1.0.4", "chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0", "chartjs-plugin-zoom": "2.2.0",
@ -32,7 +32,7 @@
"dayjs": "1.11.19", "dayjs": "1.11.19",
"dropzone": "6.0.0-beta.2", "dropzone": "6.0.0-beta.2",
"easymde": "2.20.0", "easymde": "2.20.0",
"esbuild-loader": "4.4.0", "esbuild-loader": "4.4.2",
"htmx.org": "2.0.8", "htmx.org": "2.0.8",
"idiomorph": "0.7.4", "idiomorph": "0.7.4",
"jquery": "3.7.1", "jquery": "3.7.1",
@ -41,7 +41,7 @@
"mini-css-extract-plugin": "2.9.4", "mini-css-extract-plugin": "2.9.4",
"monaco-editor": "0.55.1", "monaco-editor": "0.55.1",
"monaco-editor-webpack-plugin": "7.1.1", "monaco-editor-webpack-plugin": "7.1.1",
"online-3d-viewer": "0.17.0", "online-3d-viewer": "0.18.0",
"pdfobject": "2.3.1", "pdfobject": "2.3.1",
"perfect-debounce": "2.0.0", "perfect-debounce": "2.0.0",
"postcss": "8.5.6", "postcss": "8.5.6",
@ -56,65 +56,65 @@
"tributejs": "5.1.3", "tributejs": "5.1.3",
"uint8-to-base64": "0.2.1", "uint8-to-base64": "0.2.1",
"vanilla-colorful": "0.7.2", "vanilla-colorful": "0.7.2",
"vue": "3.5.25", "vue": "3.5.26",
"vue-bar-graph": "2.2.0", "vue-bar-graph": "2.2.0",
"vue-chartjs": "5.3.3", "vue-chartjs": "5.3.3",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"webpack": "5.104.0", "webpack": "5.104.1",
"webpack-cli": "6.0.1", "webpack-cli": "6.0.1",
"wrap-ansi": "9.0.2" "wrap-ansi": "9.0.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@eslint-community/eslint-plugin-eslint-comments": "4.6.0",
"@eslint/json": "0.14.0", "@eslint/json": "0.14.0",
"@playwright/test": "1.57.0", "@playwright/test": "1.57.0",
"@stylistic/eslint-plugin": "5.6.1", "@stylistic/eslint-plugin": "5.7.0",
"@stylistic/stylelint-plugin": "4.0.0", "@stylistic/stylelint-plugin": "4.0.1",
"@types/codemirror": "5.60.17", "@types/codemirror": "5.60.17",
"@types/dropzone": "5.7.9", "@types/dropzone": "5.7.9",
"@types/jquery": "3.5.33", "@types/jquery": "3.5.33",
"@types/katex": "0.16.7", "@types/katex": "0.16.8",
"@types/pdfobject": "2.2.5", "@types/pdfobject": "2.2.5",
"@types/sortablejs": "1.15.9", "@types/sortablejs": "1.15.9",
"@types/swagger-ui-dist": "3.30.6", "@types/swagger-ui-dist": "3.30.6",
"@types/throttle-debounce": "5.0.2", "@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/toastify-js": "1.12.4", "@types/toastify-js": "1.12.4",
"@typescript-eslint/parser": "8.50.0", "@typescript-eslint/parser": "8.53.0",
"@vitejs/plugin-vue": "6.0.3", "@vitejs/plugin-vue": "6.0.3",
"@vitest/eslint-plugin": "1.5.2", "@vitest/eslint-plugin": "1.6.6",
"eslint": "9.39.2", "eslint": "9.39.2",
"eslint-import-resolver-typescript": "4.4.4", "eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-array-func": "5.1.0", "eslint-plugin-array-func": "5.1.0",
"eslint-plugin-github": "6.0.0", "eslint-plugin-github": "6.0.0",
"eslint-plugin-import-x": "4.16.1", "eslint-plugin-import-x": "4.16.1",
"eslint-plugin-playwright": "2.4.0", "eslint-plugin-playwright": "2.5.0",
"eslint-plugin-regexp": "2.10.0", "eslint-plugin-regexp": "2.10.0",
"eslint-plugin-sonarjs": "3.0.5", "eslint-plugin-sonarjs": "3.0.5",
"eslint-plugin-unicorn": "62.0.0", "eslint-plugin-unicorn": "62.0.0",
"eslint-plugin-vue": "10.6.2", "eslint-plugin-vue": "10.6.2",
"eslint-plugin-vue-scoped-css": "2.12.0", "eslint-plugin-vue-scoped-css": "2.12.0",
"eslint-plugin-wc": "3.0.2", "eslint-plugin-wc": "3.0.2",
"globals": "16.5.0", "globals": "17.0.0",
"happy-dom": "20.0.11", "happy-dom": "20.3.0",
"jiti": "2.6.1", "jiti": "2.6.1",
"markdownlint-cli": "0.47.0", "markdownlint-cli": "0.47.0",
"material-icon-theme": "5.29.0", "material-icon-theme": "5.30.0",
"nolyfill": "1.0.44", "nolyfill": "1.0.44",
"postcss-html": "1.8.0", "postcss-html": "1.8.1",
"spectral-cli-bundle": "1.0.3", "spectral-cli-bundle": "1.0.3",
"stylelint": "16.26.1", "stylelint": "17.0.0",
"stylelint-config-recommended": "17.0.0", "stylelint-config-recommended": "18.0.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.11", "stylelint-declaration-strict-value": "1.10.11",
"stylelint-value-no-unknown-custom-properties": "6.0.1", "stylelint-value-no-unknown-custom-properties": "6.1.0",
"svgo": "4.0.0", "svgo": "4.0.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"typescript-eslint": "8.50.0", "typescript-eslint": "8.53.0",
"updates": "17.0.7", "updates": "17.0.7",
"vite-string-plugin": "1.4.9", "vite-string-plugin": "1.4.9",
"vitest": "4.0.16", "vitest": "4.0.17",
"vue-tsc": "3.1.8" "vue-tsc": "3.2.2"
}, },
"browserslist": [ "browserslist": [
"defaults" "defaults"

1848
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 16" class="svg octicon-logo-github" width="16" height="16" aria-hidden="true"><path d="M8.81 7.35v5.74c0 .04-.01.11-.06.13 0 0-1.25.89-3.31.89-2.49 0-5.44-.78-5.44-5.92S2.58 1.99 5.1 2c2.18 0 3.06.49 3.2.58.04.05.06.09.06.14L7.94 4.5c0 .09-.09.2-.2.17-.36-.11-.9-.33-2.17-.33-1.47 0-3.05.42-3.05 3.73s1.5 3.7 2.58 3.7c.92 0 1.25-.11 1.25-.11v-2.3H4.88c-.11 0-.19-.08-.19-.17V7.35c0-.09.08-.17.19-.17h3.74c.11 0 .19.08.19.17m35.85 2.33c0 3.43-1.11 4.41-3.05 4.41-1.64 0-2.52-.83-2.52-.83s-.04.46-.09.52c-.03.06-.08.08-.14.08h-1.48c-.1 0-.19-.08-.19-.17l.02-11.11c0-.09.08-.17.17-.17h2.13c.09 0 .17.08.17.17v3.77s.82-.53 2.02-.53l-.01-.02c1.2 0 2.97.45 2.97 3.88M27.68 2.43c.09 0 .17.08.17.17v11.11c0 .09-.08.17-.17.17h-2.13c-.09 0-.17-.08-.17-.17l.02-4.75h-3.31v4.75c0 .09-.08.17-.17.17h-2.13c-.08 0-.17-.08-.17-.17V2.6c0-.09.08-.17.17-.17h2.13c.09 0 .17.08.17.17v4.09h3.31V2.6c0-.09.08-.17.17-.17Zm8.26 3.64c.11 0 .19.08.19.17l-.02 7.47c0 .09-.06.17-.17.17H34.6c-.07 0-.14-.04-.16-.09-.03-.06-.08-.45-.08-.45s-1.13.77-2.52.77c-1.69 0-2.92-.55-2.92-2.75V6.25c0-.09.08-.17.17-.17h2.14c.09 0 .17.08.17.17V11c0 .75.22 1.09.97 1.09s1.3-.39 1.3-.39V6.26c0-.11.06-.19.17-.19Zm-17.406 5.971h.005a.18.18 0 0 1 .141.179v1.5c0 .07-.03.14-.09.16-.1.05-.74.22-1.27.22-1.16 0-2.86-.25-2.86-2.69V8.13h-1.11c-.09 0-.17-.08-.17-.19V6.58c0-.08.05-.15.13-.17.07-.01 1.16-.28 1.16-.28V3.96c0-.08.05-.13.14-.13h2.16c.09 0 .14.05.14.13v2.11h1.59c.08 0 .16.08.16.17v1.7c0 .11-.07.19-.16.19h-1.59v3.131c0 .47.27.83 1.05.83.247 0 .481-.049.574-.05M12.24 6.06c.09 0 .17.08.17.17v7.37c0 .18-.05.27-.25.27h-1.92c-.17 0-.3-.07-.3-.27V6.26c0-.11.08-.2.17-.2Zm29.99 3.78c0-1.81-.73-2.05-1.5-1.97-.6.04-1.08.34-1.08.34v3.52s.49.34 1.22.36c1.03.03 1.36-.34 1.36-2.25M11.19 2.68c.75 0 1.36.61 1.36 1.38s-.61 1.38-1.36 1.38c-.77 0-1.38-.61-1.38-1.38s.61-1.38 1.38-1.38m7.34 9.35zl.01.01h-.001l-.005-.001v.001c-.009-.001-.015-.011-.024-.011Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 16" class="svg octicon-logo-github" width="16" height="16" aria-hidden="true"><path d="M10.906 13.298V5.741h2.125v7.557zm6.28 0c-1.412 0-1.963-.605-1.963-1.882v-3.9h-1.345V5.741h1.345V4.235l2.124-.498v2.004h1.574v1.775h-1.574v3.429c0 .417.188.578.605.578h.969v1.775zm14.711.135c-1.654 0-2.555-.915-2.555-2.582v-5.11h2.138v4.545c0 .928.417 1.439 1.211 1.439.914 0 1.613-.847 1.613-2.004v-3.98h2.139v7.557h-2.139v-1.277c-.403.82-1.371 1.412-2.407 1.412m10.126 0c-1.103 0-2.044-.592-2.42-1.439v1.304h-2.125V2.756h2.138v4.37c.363-.915 1.345-1.56 2.407-1.56 1.991 0 3.08 1.425 3.08 3.94 0 2.488-1.13 3.927-3.08 3.927m-.753-1.789c1.036 0 1.641-.82 1.641-2.138 0-1.331-.605-2.151-1.641-2.151-.955 0-1.654.86-1.654 2.044v.121c0 1.223.699 2.124 1.654 2.124M26.075 2.756v4.276h-4.008V2.756h-2.286v10.542h2.286V9.076h4.008v4.222h2.286V2.756zM5.083 13.5C1.963 13.5 0 11.362 0 8.013c0-3.348 2.004-5.459 5.177-5.459 2.582 0 4.142 1.102 4.64 2.958l-2.313.552c-.283-1.009-1.09-1.56-2.327-1.56-1.842 0-2.837 1.21-2.837 3.509s.968 3.537 2.783 3.537c1.668 0 2.663-1.022 2.663-2.757V8.39l.592.82H4.935V7.274h5.164v1.224c0 3.213-1.869 5.002-5.016 5.002m6.885-8.472c.713 0 1.264-.551 1.264-1.264S12.681 2.5 11.968 2.5s-1.264.551-1.264 1.264.551 1.264 1.264 1.264"/></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-mark-github" width="16" height="16" aria-hidden="true"><path d="M8 0c4.42 0 8 3.58 8 8a8.01 8.01 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27s-1.36.09-2 .27c-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-mark-github" width="16" height="16" aria-hidden="true"><path d="M6.766 11.695C4.703 11.437 3.25 9.904 3.25 7.92c0-.806.281-1.677.75-2.258-.203-.532-.172-1.662.062-2.129.626-.081 1.469.258 1.969.726.594-.194 1.219-.291 1.985-.291.765 0 1.39.097 1.953.274.484-.451 1.343-.79 1.969-.709.218.435.25 1.564.046 2.113.5.613.766 1.436.766 2.274 0 1.984-1.453 3.485-3.547 3.759.531.355.891 1.129.891 2.016v1.678c0 .484.39.758.859.564C13.781 14.824 16 11.905 16 8.291 16 3.726 12.406 0 7.984 0S0 3.726 0 8.291c0 3.581 2.203 6.55 5.172 7.663A.595.595 0 0 0 6 15.389v-1.291c-.219.097-.5.162-.75.162-1.031 0-1.641-.581-2.078-1.662-.172-.435-.36-.693-.719-.742-.187-.016-.25-.097-.25-.193 0-.194.313-.339.625-.339.453 0 .844.29 1.25.887.313.468.641.678 1.031.678.391 0 .641-.146 1-.516.266-.275.469-.517.657-.678"/></svg>

Before

Width:  |  Height:  |  Size: 716 B

After

Width:  |  Height:  |  Size: 886 B

View File

@ -6,7 +6,7 @@ requires-python = ">=3.10"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"djlint==1.36.4", "djlint==1.36.4",
"yamllint==1.37.1", "yamllint==1.38.0",
] ]
[tool.djlint] [tool.djlint]

View File

@ -8,25 +8,35 @@ import (
"net/http" "net/http"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
archiver_service "code.gitea.io/gitea/services/repository/archiver" archiver_service "code.gitea.io/gitea/services/repository/archiver"
) )
func serveRepoArchive(ctx *context.APIContext, reqFileName string) { func serveRepoArchive(ctx *context.APIContext, reqFileName string, paths []string) {
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, reqFileName) aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, reqFileName, paths)
if err != nil { if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { if errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err) ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) { } else if errors.Is(err, util.ErrNotExist) {
ctx.APIError(http.StatusNotFound, err) ctx.APIError(http.StatusNotFound, err)
} else { } else {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
} }
return return
} }
archiver_service.ServeRepoArchive(ctx.Base, aReq) err = archiver_service.ServeRepoArchive(ctx.Base, aReq)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err)
} else {
ctx.APIErrorInternal(err)
}
}
} }
// DownloadArchive is the GitHub-compatible endpoint to download repository archives
// TODO: The API document is missing: Add github compatible tarball download API endpoints (#32572)
func DownloadArchive(ctx *context.APIContext) { func DownloadArchive(ctx *context.APIContext) {
var tp repo_model.ArchiveType var tp repo_model.ArchiveType
switch ballType := ctx.PathParam("ball_type"); ballType { switch ballType := ctx.PathParam("ball_type"); ballType {
@ -40,5 +50,5 @@ func DownloadArchive(ctx *context.APIContext) {
ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType) ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType)
return return
} }
serveRepoArchive(ctx, ctx.PathParam("*")+"."+tp.String()) serveRepoArchive(ctx, ctx.PathParam("*")+"."+tp.String(), ctx.FormStrings("path"))
} }

View File

@ -273,13 +273,19 @@ func GetArchive(ctx *context.APIContext) {
// description: the git reference for download with attached archive format (e.g. master.zip) // description: the git reference for download with attached archive format (e.g. master.zip)
// type: string // type: string
// required: true // required: true
// - name: path
// in: query
// type: array
// items:
// type: string
// description: subpath of the repository to download
// collectionFormat: multi
// responses: // responses:
// 200: // 200:
// description: success // description: success
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
serveRepoArchive(ctx, ctx.PathParam("*"), ctx.FormStrings("path"))
serveRepoArchive(ctx, ctx.PathParam("*"))
} }
// GetEditorconfig get editor config of a repository // GetEditorconfig get editor config of a repository

View File

@ -224,7 +224,7 @@ func GetStopwatches(ctx *context.APIContext) {
return return
} }
apiSWs, err := convert.ToStopWatches(ctx, sws) apiSWs, err := convert.ToStopWatches(ctx, ctx.Doer, sws)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return

View File

@ -1322,7 +1322,7 @@ func CancelScheduledAutoMerge(ctx *context.APIContext) {
} }
if ctx.Doer.ID != autoMerge.DoerID { if ctx.Doer.ID != autoMerge.DoerID {
allowed, err := access_model.IsUserRepoAdmin(ctx, ctx.Repo.Repository, ctx.Doer) allowed, err := pull_service.IsUserAllowedToMerge(ctx, pull, ctx.Repo.Permission, ctx.Doer)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
@ -1532,9 +1532,9 @@ func GetPullRequestFiles(ctx *context.APIContext) {
var compareInfo *git_service.CompareInfo var compareInfo *git_service.CompareInfo
if pr.HasMerged { if pr.HasMerged {
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefName(pr.MergeBase), git.RefName(pr.GetGitHeadRefName()), true, false) compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefName(pr.MergeBase), git.RefName(pr.GetGitHeadRefName()), false, false)
} else { } else {
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefNameFromBranch(pr.BaseBranch), git.RefName(pr.GetGitHeadRefName()), true, false) compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefNameFromBranch(pr.BaseBranch), git.RefName(pr.GetGitHeadRefName()), false, false)
} }
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)

View File

@ -398,7 +398,6 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
ctx.APIErrorNotFound() ctx.APIErrorNotFound()
return return
} }
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil { if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)

View File

@ -205,22 +205,24 @@ func ChangeProjectStatus(ctx *context.Context) {
} }
id := ctx.PathParamInt64("id") id := ctx.PathParamInt64("id")
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil { project, err := project_model.GetProjectByIDAndOwner(ctx, id, ctx.ContextUser.ID)
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return
}
ctx.JSONRedirect(project_model.ProjectLinkForOrg(ctx.ContextUser, id))
}
// DeleteProject delete a project
func DeleteProject(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
} }
if p.OwnerID != ctx.ContextUser.ID {
ctx.NotFound(nil) if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, project.ID, toClose); err != nil {
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return
}
ctx.JSONRedirect(project_model.ProjectLinkForOrg(ctx.ContextUser, project.ID))
}
// DeleteProject delete a project
func DeleteProject(ctx *context.Context) {
p, err := project_model.GetProjectByIDAndOwner(ctx, ctx.PathParamInt64("id"), ctx.ContextUser.ID)
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
} }
@ -246,15 +248,11 @@ func RenderEditProject(ctx *context.Context) {
return return
} }
p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) p, err := project_model.GetProjectByIDAndOwner(ctx, ctx.PathParamInt64("id"), ctx.ContextUser.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
} }
if p.OwnerID != ctx.ContextUser.ID {
ctx.NotFound(nil)
return
}
ctx.Data["projectID"] = p.ID ctx.Data["projectID"] = p.ID
ctx.Data["title"] = p.Title ctx.Data["title"] = p.Title
@ -288,15 +286,11 @@ func EditProjectPost(ctx *context.Context) {
return return
} }
p, err := project_model.GetProjectByID(ctx, projectID) p, err := project_model.GetProjectByIDAndOwner(ctx, projectID, ctx.ContextUser.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
} }
if p.OwnerID != ctx.ContextUser.ID {
ctx.NotFound(nil)
return
}
p.Title = form.Title p.Title = form.Title
p.Description = form.Content p.Description = form.Content
@ -316,15 +310,12 @@ func EditProjectPost(ctx *context.Context) {
// ViewProject renders the project with board view for a project // ViewProject renders the project with board view for a project
func ViewProject(ctx *context.Context) { func ViewProject(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) project, err := project_model.GetProjectByIDAndOwner(ctx, ctx.PathParamInt64("id"), ctx.ContextUser.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
} }
if project.OwnerID != ctx.ContextUser.ID {
ctx.NotFound(nil)
return
}
if err := project.LoadOwner(ctx); err != nil { if err := project.LoadOwner(ctx); err != nil {
ctx.ServerError("LoadOwner", err) ctx.ServerError("LoadOwner", err)
return return
@ -455,28 +446,15 @@ func DeleteProjectColumn(ctx *context.Context) {
return return
} }
project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) project, err := project_model.GetProjectByIDAndOwner(ctx, ctx.PathParamInt64("id"), ctx.ContextUser.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
} }
pb, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID")) _, err = project_model.GetColumnByIDAndProjectID(ctx, ctx.PathParamInt64("columnID"), project.ID)
if err != nil { if err != nil {
ctx.ServerError("GetProjectColumn", err) ctx.NotFoundOrServerError("GetColumnByIDAndProjectID", project_model.IsErrProjectColumnNotExist, err)
return
}
if pb.ProjectID != ctx.PathParamInt64("id") {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Project[%d] as expected", pb.ID, project.ID),
})
return
}
if project.OwnerID != ctx.ContextUser.ID {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Owner[%d] as expected", pb.ID, ctx.ContextUser.ID),
})
return return
} }
@ -492,7 +470,7 @@ func DeleteProjectColumn(ctx *context.Context) {
func AddColumnToProjectPost(ctx *context.Context) { func AddColumnToProjectPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.EditProjectColumnForm) form := web.GetForm(ctx).(*forms.EditProjectColumnForm)
project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) project, err := project_model.GetProjectByIDAndOwner(ctx, ctx.PathParamInt64("id"), ctx.ContextUser.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
@ -520,30 +498,18 @@ func CheckProjectColumnChangePermissions(ctx *context.Context) (*project_model.P
return nil, nil return nil, nil
} }
project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) project, err := project_model.GetProjectByIDAndOwner(ctx, ctx.PathParamInt64("id"), ctx.ContextUser.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return nil, nil return nil, nil
} }
column, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID")) column, err := project_model.GetColumnByIDAndProjectID(ctx, ctx.PathParamInt64("columnID"), project.ID)
if err != nil { if err != nil {
ctx.ServerError("GetProjectColumn", err) ctx.NotFoundOrServerError("GetColumnByIDAndProjectID", project_model.IsErrProjectColumnNotExist, err)
return nil, nil
}
if column.ProjectID != ctx.PathParamInt64("id") {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Project[%d] as expected", column.ID, project.ID),
})
return nil, nil return nil, nil
} }
if project.OwnerID != ctx.ContextUser.ID {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Repository[%d] as expected", column.ID, project.ID),
})
return nil, nil
}
return project, column return project, column
} }
@ -595,24 +561,15 @@ func MoveIssues(ctx *context.Context) {
return return
} }
project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) project, err := project_model.GetProjectByIDAndOwner(ctx, ctx.PathParamInt64("id"), ctx.ContextUser.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return return
} }
if project.OwnerID != ctx.ContextUser.ID {
ctx.NotFound(nil)
return
}
column, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID")) column, err := project_model.GetColumnByIDAndProjectID(ctx, ctx.PathParamInt64("columnID"), project.ID)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetProjectColumn", project_model.IsErrProjectColumnNotExist, err) ctx.NotFoundOrServerError("GetColumnByIDAndProjectID", project_model.IsErrProjectColumnNotExist, err)
return
}
if column.ProjectID != project.ID {
ctx.NotFound(nil)
return return
} }

View File

@ -4,11 +4,14 @@
package org_test package org_test
import ( import (
"net/http"
"testing" "testing"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/org" "code.gitea.io/gitea/routers/web/org"
"code.gitea.io/gitea/services/contexttest" "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -26,3 +29,30 @@ func TestCheckProjectColumnChangePermissions(t *testing.T) {
assert.NotNil(t, column) assert.NotNil(t, column)
assert.False(t, ctx.Written()) assert.False(t, ctx.Written())
} }
func TestChangeProjectStatusRejectsForeignProjects(t *testing.T) {
unittest.PrepareTestEnv(t)
// project 4 is owned by user2 not user1
ctx, _ := contexttest.MockContext(t, "user1/-/projects/4/close")
contexttest.LoadUser(t, ctx, 1)
ctx.ContextUser = ctx.Doer
ctx.SetPathParam("action", "close")
ctx.SetPathParam("id", "4")
org.ChangeProjectStatus(ctx)
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
}
func TestAddColumnToProjectPostRejectsForeignProjects(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user1/-/projects/4/columns/new")
contexttest.LoadUser(t, ctx, 1)
ctx.ContextUser = ctx.Doer
ctx.SetPathParam("id", "4")
web.SetForm(ctx, &forms.EditProjectColumnForm{Title: "foreign"})
org.AddColumnToProjectPost(ctx)
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
}

View File

@ -4,11 +4,12 @@
package repo package repo
import ( import (
"fmt"
"net/http" "net/http"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -40,7 +41,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
file, header, err := ctx.Req.FormFile("file") file, header, err := ctx.Req.FormFile("file")
if err != nil { if err != nil {
ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err)) ctx.ServerError("FormFile", err)
return return
} }
defer file.Close() defer file.Close()
@ -56,7 +57,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
ctx.HTTPError(http.StatusBadRequest, err.Error()) ctx.HTTPError(http.StatusBadRequest, err.Error())
return return
} }
ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("NewAttachment: %v", err)) ctx.ServerError("UploadAttachmentGeneralSizeLimit", err)
return return
} }
@ -74,13 +75,44 @@ func DeleteAttachment(ctx *context.Context) {
ctx.HTTPError(http.StatusBadRequest, err.Error()) ctx.HTTPError(http.StatusBadRequest, err.Error())
return return
} }
if !ctx.IsSigned || (ctx.Doer.ID != attach.UploaderID) {
if !ctx.IsSigned {
ctx.HTTPError(http.StatusForbidden) ctx.HTTPError(http.StatusForbidden)
return return
} }
if attach.RepoID != ctx.Repo.Repository.ID {
ctx.HTTPError(http.StatusBadRequest, "attachment does not belong to this repository")
return
}
if ctx.Doer.ID != attach.UploaderID {
if attach.IssueID > 0 {
issue, err := issues_model.GetIssueByID(ctx, attach.IssueID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.HTTPError(http.StatusForbidden)
return
}
} else if attach.ReleaseID > 0 {
if !ctx.Repo.Permission.CanWrite(unit.TypeReleases) {
ctx.HTTPError(http.StatusForbidden)
return
}
} else {
if !ctx.Repo.Permission.IsAdmin() && !ctx.Repo.Permission.IsOwner() {
ctx.HTTPError(http.StatusForbidden)
return
}
}
}
err = repo_model.DeleteAttachment(ctx, attach, true) err = repo_model.DeleteAttachment(ctx, attach, true)
if err != nil { if err != nil {
ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("DeleteAttachment: %v", err)) ctx.ServerError("DeleteAttachment", err)
return return
} }
ctx.JSON(http.StatusOK, map[string]string{ ctx.JSON(http.StatusOK, map[string]string{
@ -100,23 +132,40 @@ func ServeAttachment(ctx *context.Context, uuid string) {
return return
} }
repository, unitType, err := repo_service.LinkedRepository(ctx, attach) // prevent visiting attachment from other repository directly
if err != nil { if ctx.Repo.Repository != nil && ctx.Repo.Repository.ID != attach.RepoID {
ctx.ServerError("LinkedRepository", err) ctx.HTTPError(http.StatusNotFound)
return return
} }
if repository == nil { // If not linked unitType, err := repo_service.GetAttachmentLinkedType(ctx, attach)
if err != nil {
ctx.ServerError("GetAttachmentLinkedType", err)
return
}
if unitType == unit.TypeInvalid { // unlinked attachment can only be accessed by the uploader
if !(ctx.IsSigned && attach.UploaderID == ctx.Doer.ID) { // We block if not the uploader if !(ctx.IsSigned && attach.UploaderID == ctx.Doer.ID) { // We block if not the uploader
ctx.HTTPError(http.StatusNotFound) ctx.HTTPError(http.StatusNotFound)
return return
} }
} else { // If we have the repository we check access } else { // If we have the linked type, we need to check access
perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer) var perm access_model.Permission
if err != nil { if ctx.Repo.Repository == nil {
ctx.HTTPError(http.StatusInternalServerError, "GetUserRepoPermission", err.Error()) repo, err := repo_model.GetRepositoryByID(ctx, attach.RepoID)
return if err != nil {
ctx.ServerError("GetRepositoryByID", err)
return
}
perm, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
} else {
perm = ctx.Repo.Permission
} }
if !perm.CanRead(unitType) { if !perm.CanRead(unitType) {
ctx.HTTPError(http.StatusNotFound) ctx.HTTPError(http.StatusNotFound)
return return

View File

@ -23,6 +23,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/fileicon"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -35,6 +36,7 @@ import (
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/routers/utils"
@ -320,6 +322,26 @@ type pullCommitStatusCheckData struct {
RequireApprovalRunCount int // number of workflow runs that require approval RequireApprovalRunCount int // number of workflow runs that require approval
CanApprove bool // whether the user can approve workflow runs CanApprove bool // whether the user can approve workflow runs
ApproveLink string // link to approve all checks ApproveLink string // link to approve all checks
RequiredChecksState commitstatus.CommitStatusState
LatestCommitStatus *git_model.CommitStatus
}
func (d *pullCommitStatusCheckData) CommitStatusCheckPrompt(locale translation.Locale) string {
if d.RequiredChecksState.IsPending() || len(d.MissingRequiredChecks) > 0 {
return locale.TrString("repo.pulls.status_checking")
} else if d.RequiredChecksState.IsSuccess() {
if d.LatestCommitStatus != nil && d.LatestCommitStatus.State.IsFailure() {
return locale.TrString("repo.pulls.status_checks_failure_optional")
}
return locale.TrString("repo.pulls.status_checks_success")
} else if d.RequiredChecksState.IsWarning() {
return locale.TrString("repo.pulls.status_checks_warning")
} else if d.RequiredChecksState.IsFailure() {
return locale.TrString("repo.pulls.status_checks_failure_required")
} else if d.RequiredChecksState.IsError() {
return locale.TrString("repo.pulls.status_checks_error")
}
return locale.TrString("repo.pulls.status_checking")
} }
// prepareViewPullInfo show meta information for a pull request preview page // prepareViewPullInfo show meta information for a pull request preview page
@ -360,6 +382,8 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
defer baseGitRepo.Close() defer baseGitRepo.Close()
} }
statusCheckData := &pullCommitStatusCheckData{}
if exist, _ := git_model.IsBranchExist(ctx, pull.BaseRepo.ID, pull.BaseBranch); !exist { if exist, _ := git_model.IsBranchExist(ctx, pull.BaseRepo.ID, pull.BaseBranch); !exist {
ctx.Data["BaseBranchNotExist"] = true ctx.Data["BaseBranchNotExist"] = true
ctx.Data["IsPullRequestBroken"] = true ctx.Data["IsPullRequestBroken"] = true
@ -380,9 +404,10 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
} }
statusCheckData.LatestCommitStatus = git_model.CalcCommitStatus(commitStatuses)
if len(commitStatuses) > 0 { if len(commitStatuses) > 0 {
ctx.Data["LatestCommitStatuses"] = commitStatuses ctx.Data["LatestCommitStatuses"] = commitStatuses
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses) ctx.Data["LatestCommitStatus"] = statusCheckData.LatestCommitStatus
} }
compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
@ -467,10 +492,8 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
return nil return nil
} }
statusCheckData := &pullCommitStatusCheckData{
ApproveLink: fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s", repo.Link(), sha),
}
ctx.Data["StatusCheckData"] = statusCheckData ctx.Data["StatusCheckData"] = statusCheckData
statusCheckData.ApproveLink = fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s", repo.Link(), sha)
commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
@ -495,9 +518,10 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
statusCheckData.CanApprove = ctx.Repo.CanWrite(unit.TypeActions) statusCheckData.CanApprove = ctx.Repo.CanWrite(unit.TypeActions)
} }
statusCheckData.LatestCommitStatus = git_model.CalcCommitStatus(commitStatuses)
if len(commitStatuses) > 0 { if len(commitStatuses) > 0 {
ctx.Data["LatestCommitStatuses"] = commitStatuses ctx.Data["LatestCommitStatuses"] = commitStatuses
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses) ctx.Data["LatestCommitStatus"] = statusCheckData.LatestCommitStatus
} }
if pb != nil && pb.EnableStatusCheck { if pb != nil && pb.EnableStatusCheck {
@ -534,7 +558,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
} }
return false return false
} }
ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pb.StatusCheckContexts) statusCheckData.RequiredChecksState = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pb.StatusCheckContexts)
} }
ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
@ -1265,6 +1289,28 @@ func CancelAutoMergePullRequest(ctx *context.Context) {
return return
} }
exist, autoMerge, err := pull_model.GetScheduledMergeByPullID(ctx, issue.PullRequest.ID)
if err != nil {
ctx.ServerError("GetScheduledMergeByPullID", err)
return
}
if !exist {
ctx.NotFound(nil)
return
}
if ctx.Doer.ID != autoMerge.DoerID {
allowed, err := pull_service.IsUserAllowedToMerge(ctx, issue.PullRequest, ctx.Repo.Permission, ctx.Doer)
if err != nil {
ctx.ServerError("IsUserAllowedToMerge", err)
return
}
if !allowed {
ctx.HTTPError(http.StatusForbidden, "user has no permission to cancel the scheduled auto merge")
return
}
}
if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, issue.PullRequest); err != nil { if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, issue.PullRequest); err != nil {
if db.IsErrNotExist(err) { if db.IsErrNotExist(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.auto_merge_not_scheduled")) ctx.Flash.Error(ctx.Tr("repo.pulls.auto_merge_not_scheduled"))

View File

@ -364,31 +364,39 @@ func RedirectDownload(ctx *context.Context) {
// Download an archive of a repository // Download an archive of a repository
func Download(ctx *context.Context) { func Download(ctx *context.Context) {
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*")) aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"), ctx.FormStrings("path"))
if err != nil { if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { if errors.Is(err, util.ErrInvalidArgument) {
ctx.HTTPError(http.StatusBadRequest, err.Error()) ctx.HTTPError(http.StatusBadRequest, err.Error())
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) { } else if errors.Is(err, util.ErrNotExist) {
ctx.HTTPError(http.StatusNotFound, err.Error()) ctx.HTTPError(http.StatusNotFound, err.Error())
} else { } else {
ctx.ServerError("archiver_service.NewRequest", err) ctx.ServerError("archiver_service.NewRequest", err)
} }
return return
} }
archiver_service.ServeRepoArchive(ctx.Base, aReq) err = archiver_service.ServeRepoArchive(ctx.Base, aReq)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.HTTPError(http.StatusBadRequest, err.Error())
} else {
ctx.ServerError("archiver_service.ServeRepoArchive", err)
}
}
} }
// InitiateDownload will enqueue an archival request, as needed. It may submit // InitiateDownload will enqueue an archival request, as needed. It may submit
// a request that's already in-progress, but the archiver service will just // a request that's already in-progress, but the archiver service will just
// kind of drop it on the floor if this is the case. // kind of drop it on the floor if this is the case.
func InitiateDownload(ctx *context.Context) { func InitiateDownload(ctx *context.Context) {
if setting.Repository.StreamArchives { paths := ctx.FormStrings("path")
if setting.Repository.StreamArchives || len(paths) > 0 {
ctx.JSON(http.StatusOK, map[string]any{ ctx.JSON(http.StatusOK, map[string]any{
"complete": true, "complete": true,
}) })
return return
} }
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*")) aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"), paths)
if err != nil { if err != nil {
ctx.HTTPError(http.StatusBadRequest, "invalid archive request") ctx.HTTPError(http.StatusBadRequest, "invalid archive request")
return return

View File

@ -15,6 +15,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -128,7 +129,9 @@ func prepareUserNotificationsData(ctx *context.Context) {
ctx.Data["Notifications"] = notifications ctx.Data["Notifications"] = notifications
ctx.Data["Link"] = setting.AppSubURL + "/notifications" ctx.Data["Link"] = setting.AppSubURL + "/notifications"
ctx.Data["SequenceNumber"] = ctx.FormString("sequence-number") ctx.Data["SequenceNumber"] = ctx.FormString("sequence-number")
pager.AddParamFromRequest(ctx.Req) pager.AddParamFromRequest(ctx.Req)
pager.RemoveParam(container.SetOf("div-only", "sequence-number"))
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
} }

View File

@ -0,0 +1,14 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package security
import (
"testing"
"code.gitea.io/gitea/models/unittest"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}

View File

@ -4,12 +4,14 @@
package security package security
import ( import (
"errors"
"net/http" "net/http"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
@ -116,7 +118,11 @@ func DeleteOpenID(ctx *context.Context) {
} }
if err := user_model.DeleteUserOpenID(ctx, &user_model.UserOpenID{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil { if err := user_model.DeleteUserOpenID(ctx, &user_model.UserOpenID{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil {
ctx.ServerError("DeleteUserOpenID", err) if errors.Is(err, util.ErrNotExist) {
ctx.HTTPError(http.StatusNotFound)
} else {
ctx.ServerError("DeleteUserOpenID", err)
}
return return
} }
log.Trace("OpenID address deleted: %s", ctx.Doer.Name) log.Trace("OpenID address deleted: %s", ctx.Doer.Name)
@ -132,8 +138,12 @@ func ToggleOpenIDVisibility(ctx *context.Context) {
return return
} }
if err := user_model.ToggleUserOpenIDVisibility(ctx, ctx.FormInt64("id")); err != nil { if err := user_model.ToggleUserOpenIDVisibility(ctx, ctx.FormInt64("id"), ctx.Doer); err != nil {
ctx.ServerError("ToggleUserOpenIDVisibility", err) if errors.Is(err, util.ErrNotExist) {
ctx.HTTPError(http.StatusNotFound)
} else {
ctx.ServerError("ToggleUserOpenIDVisibility", err)
}
return return
} }

View File

@ -0,0 +1,36 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package security
import (
"net/http"
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
func TestDeleteOpenIDReturnsNotFoundForOtherUsersAddress(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "POST /user/settings/security")
contexttest.LoadUser(t, ctx, 2)
ctx.SetFormString("id", "1")
DeleteOpenID(ctx)
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
}
func TestToggleOpenIDVisibilityReturnsNotFoundForOtherUsersAddress(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "POST /user/settings/security")
contexttest.LoadUser(t, ctx, 2)
ctx.SetFormString("id", "1")
ToggleOpenIDVisibility(ctx)
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
}

View File

@ -29,7 +29,7 @@ func GetStopwatches(ctx *context.Context) {
return return
} }
apiSWs, err := convert.ToStopWatches(ctx, sws) apiSWs, err := convert.ToStopWatches(ctx, ctx.Doer, sws)
if err != nil { if err != nil {
ctx.HTTPError(http.StatusInternalServerError, err.Error()) ctx.HTTPError(http.StatusInternalServerError, err.Error())
return return

View File

@ -8,8 +8,10 @@ import (
"html/template" "html/template"
"net/http" "net/http"
"net/url" "net/url"
"slices"
"strings" "strings"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/paginator" "code.gitea.io/gitea/modules/paginator"
) )
@ -49,6 +51,14 @@ func (p *Pagination) AddParamFromRequest(req *http.Request) {
p.AddParamFromQuery(req.URL.Query()) p.AddParamFromQuery(req.URL.Query())
} }
func (p *Pagination) RemoveParam(keys container.Set[string]) {
p.urlParams = slices.DeleteFunc(p.urlParams, func(s string) bool {
k, _, _ := strings.Cut(s, "=")
k, _ = url.QueryUnescape(k)
return keys.Contains(k)
})
}
// GetParams returns the configured URL params // GetParams returns the configured URL params
func (p *Pagination) GetParams() template.URL { func (p *Pagination) GetParams() template.URL {
return template.URL(strings.Join(p.urlParams, "&")) return template.URL(strings.Join(p.urlParams, "&"))

View File

@ -0,0 +1,35 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"net/url"
"testing"
"code.gitea.io/gitea/modules/container"
"github.com/stretchr/testify/assert"
)
func TestPagination(t *testing.T) {
p := NewPagination(1, 1, 1, 1)
params := url.Values{}
params.Add("k1", "11")
params.Add("k1", "12")
params.Add("k", "a")
params.Add("k", "b")
params.Add("k2", "21")
params.Add("k2", "22")
params.Add("foo", "bar")
p.AddParamFromQuery(params)
v, _ := url.ParseQuery(string(p.GetParams()))
assert.Equal(t, params, v)
p.RemoveParam(container.SetOf("k", "foo"))
params.Del("k")
params.Del("foo")
v, _ = url.ParseQuery(string(p.GetParams()))
assert.Equal(t, params, v)
}

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/label"
@ -163,11 +164,12 @@ func ToTrackedTime(ctx context.Context, doer *user_model.User, t *issues_model.T
} }
// ToStopWatches convert Stopwatch list to api.StopWatches // ToStopWatches convert Stopwatch list to api.StopWatches
func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.StopWatches, error) { func ToStopWatches(ctx context.Context, doer *user_model.User, sws []*issues_model.Stopwatch) (api.StopWatches, error) {
result := api.StopWatches(make([]api.StopWatch, 0, len(sws))) result := api.StopWatches(make([]api.StopWatch, 0, len(sws)))
issueCache := make(map[int64]*issues_model.Issue) issueCache := make(map[int64]*issues_model.Issue)
repoCache := make(map[int64]*repo_model.Repository) repoCache := make(map[int64]*repo_model.Repository)
permCache := make(map[int64]access_model.Permission)
var ( var (
issue *issues_model.Issue issue *issues_model.Issue
repo *repo_model.Repository repo *repo_model.Repository
@ -182,13 +184,30 @@ func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.Stop
if err != nil { if err != nil {
return nil, err return nil, err
} }
issueCache[sw.IssueID] = issue
} }
repo, ok = repoCache[issue.RepoID] repo, ok = repoCache[issue.RepoID]
if !ok { if !ok {
repo, err = repo_model.GetRepositoryByID(ctx, issue.RepoID) repo, err = repo_model.GetRepositoryByID(ctx, issue.RepoID)
if err != nil { if err != nil {
return nil, err log.Error("GetRepositoryByID(%d): %v", issue.RepoID, err)
continue
} }
repoCache[issue.RepoID] = repo
}
// ADD: Check user permissions
perm, ok := permCache[repo.ID]
if !ok {
perm, err = access_model.GetUserRepoPermission(ctx, repo, doer)
if err != nil {
continue
}
permCache[repo.ID] = perm
}
if !perm.CanReadIssuesOrPulls(issue.IsPull) {
continue
} }
result = append(result, api.StopWatch{ result = append(result, api.StopWatch{

View File

@ -8,9 +8,11 @@ import (
"testing" "testing"
"time" "time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -55,3 +57,29 @@ func TestMilestone_APIFormat(t *testing.T) {
Deadline: milestone.DeadlineUnix.AsTimePtr(), Deadline: milestone.DeadlineUnix.AsTimePtr(),
}, *ToAPIMilestone(milestone)) }, *ToAPIMilestone(milestone))
} }
func TestToStopWatchesRespectsPermissions(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
publicSW := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{ID: 1})
privateIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: 3})
privateSW := &issues_model.Stopwatch{IssueID: privateIssue.ID, UserID: 5}
assert.NoError(t, db.Insert(ctx, privateSW))
assert.NotZero(t, privateSW.ID)
regularUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
sws := []*issues_model.Stopwatch{publicSW, privateSW}
visible, err := ToStopWatches(ctx, regularUser, sws)
assert.NoError(t, err)
assert.Len(t, visible, 1)
assert.Equal(t, "repo1", visible[0].RepoName)
visibleAdmin, err := ToStopWatches(ctx, adminUser, sws)
assert.NoError(t, err)
assert.Len(t, visibleAdmin, 2)
assert.ElementsMatch(t, []string{"repo1", "repo3"}, []string{visibleAdmin[0].RepoName, visibleAdmin[1].RepoName})
}

View File

@ -8,8 +8,8 @@ import (
"net/url" "net/url"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@ -25,11 +25,17 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
// since user only get notifications when he has access to use minimal access mode // since user only get notifications when he has access to use minimal access mode
if n.Repository != nil { if n.Repository != nil {
result.Repository = ToRepo(ctx, n.Repository, access_model.Permission{AccessMode: perm.AccessModeRead}) perm, err := access_model.GetUserRepoPermission(ctx, n.Repository, n.User)
if err != nil {
// This permission is not correct and we should not be reporting it log.Error("GetUserRepoPermission failed: %v", err)
for repository := result.Repository; repository != nil; repository = repository.Parent { return result
repository.Permissions = nil }
if perm.HasAnyUnitAccessOrPublicAccess() { // if user has been revoked access to repo, do not show repo info
result.Repository = ToRepo(ctx, n.Repository, perm)
// This permission is not correct and we should not be reporting it
for repository := result.Repository; repository != nil; repository = repository.Parent {
repository.Permissions = nil
}
} }
} }

View File

@ -0,0 +1,57 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package convert
import (
"testing"
activities_model "code.gitea.io/gitea/models/activities"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
)
func TestToNotificationThreadIncludesRepoForAccessibleUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
n := newRepoNotification(t, 1, 4)
thread := ToNotificationThread(t.Context(), n)
if assert.NotNil(t, thread.Repository) {
assert.Equal(t, n.Repository.FullName(), thread.Repository.FullName)
assert.Nil(t, thread.Repository.Permissions)
}
}
func TestToNotificationThreadOmitsRepoWhenAccessRevoked(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
n := newRepoNotification(t, 2, 4)
thread := ToNotificationThread(t.Context(), n)
assert.Nil(t, thread.Repository)
}
func newRepoNotification(t *testing.T, repoID, userID int64) *activities_model.Notification {
t.Helper()
ctx := t.Context()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
assert.NoError(t, repo.LoadOwner(ctx))
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
return &activities_model.Notification{
ID: repoID*1000 + userID,
UserID: user.ID,
RepoID: repo.ID,
Status: activities_model.NotificationStatusUnread,
Source: activities_model.NotificationSourceRepository,
UpdatedUnix: timeutil.TimeStampNow(),
Repository: repo,
User: user,
}
}

View File

@ -127,20 +127,10 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
projectsMode = config.ProjectsMode projectsMode = config.ProjectsMode
} }
hasReleases := false hasCode := repo.UnitEnabled(ctx, unit_model.TypeCode)
if _, err := repo.GetUnit(ctx, unit_model.TypeReleases); err == nil { hasReleases := repo.UnitEnabled(ctx, unit_model.TypeReleases)
hasReleases = true hasPackages := repo.UnitEnabled(ctx, unit_model.TypePackages)
} hasActions := repo.UnitEnabled(ctx, unit_model.TypeActions)
hasPackages := false
if _, err := repo.GetUnit(ctx, unit_model.TypePackages); err == nil {
hasPackages = true
}
hasActions := false
if _, err := repo.GetUnit(ctx, unit_model.TypeActions); err == nil {
hasActions = true
}
if err := repo.LoadOwner(ctx); err != nil { if err := repo.LoadOwner(ctx); err != nil {
return nil return nil
@ -221,6 +211,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
Updated: repo.UpdatedUnix.AsTime(), Updated: repo.UpdatedUnix.AsTime(),
ArchivedAt: repo.ArchivedUnix.AsTime(), ArchivedAt: repo.ArchivedUnix.AsTime(),
Permissions: permission, Permissions: permission,
HasCode: hasCode,
HasIssues: hasIssues, HasIssues: hasIssues,
ExternalTracker: externalTracker, ExternalTracker: externalTracker,
InternalTracker: internalTracker, InternalTracker: internalTracker,

View File

@ -166,16 +166,6 @@ func parseGitDiffTreeLine(line string) (*DiffTreeRecord, error) {
return nil, fmt.Errorf("unparsable output for diff-tree --raw: `%s`, expected 5 space delimited values got %d)", line, len(fields)) return nil, fmt.Errorf("unparsable output for diff-tree --raw: `%s`, expected 5 space delimited values got %d)", line, len(fields))
} }
baseMode, err := git.ParseEntryMode(fields[0])
if err != nil {
return nil, err
}
headMode, err := git.ParseEntryMode(fields[1])
if err != nil {
return nil, err
}
baseBlobID := fields[2] baseBlobID := fields[2]
headBlobID := fields[3] headBlobID := fields[3]
@ -201,8 +191,8 @@ func parseGitDiffTreeLine(line string) (*DiffTreeRecord, error) {
return &DiffTreeRecord{ return &DiffTreeRecord{
Status: status, Status: status,
Score: score, Score: score,
BaseMode: baseMode, BaseMode: git.ParseEntryMode(fields[0]),
HeadMode: headMode, HeadMode: git.ParseEntryMode(fields[1]),
BaseBlobID: baseBlobID, BaseBlobID: baseBlobID,
HeadBlobID: headBlobID, HeadBlobID: headBlobID,
BasePath: basePath, BasePath: basePath,

View File

@ -399,20 +399,20 @@ type DiffFile struct {
isAmbiguous bool isAmbiguous bool
// basic fields (parsed from diff result) // basic fields (parsed from diff result)
Name string Name string
NameHash string NameHash string
OldName string OldName string
Addition int Addition int
Deletion int Deletion int
Type DiffFileType Type DiffFileType
Mode string EntryMode string
OldMode string OldEntryMode string
IsCreated bool IsCreated bool
IsDeleted bool IsDeleted bool
IsBin bool IsBin bool
IsLFSFile bool IsLFSFile bool
IsRenamed bool IsRenamed bool
IsSubmodule bool IsSubmodule bool
// basic fields but for render purpose only // basic fields but for render purpose only
Sections []*DiffSection Sections []*DiffSection
IsIncomplete bool IsIncomplete bool
@ -501,21 +501,36 @@ func (diffFile *DiffFile) ShouldBeHidden() bool {
return diffFile.IsGenerated || diffFile.IsViewed return diffFile.IsGenerated || diffFile.IsViewed
} }
func (diffFile *DiffFile) ModeTranslationKey(mode string) string { func (diffFile *DiffFile) TranslateDiffEntryMode(locale translation.Locale) string {
switch mode { entryModeTr := func(mode string) string {
case "040000": entryMode := git.ParseEntryMode(mode)
return "git.filemode.directory" switch {
case "100644": case entryMode.IsDir():
return "git.filemode.normal_file" return locale.TrString("git.filemode.directory")
case "100755": case entryMode.IsRegular():
return "git.filemode.executable_file" return locale.TrString("git.filemode.normal_file")
case "120000": case entryMode.IsExecutable():
return "git.filemode.symbolic_link" return locale.TrString("git.filemode.executable_file")
case "160000": case entryMode.IsLink():
return "git.filemode.submodule" return locale.TrString("git.filemode.symbolic_link")
default: case entryMode.IsSubModule():
return mode return locale.TrString("git.filemode.submodule")
default:
return mode
}
} }
if diffFile.EntryMode != "" && diffFile.OldEntryMode != "" {
oldMode := entryModeTr(diffFile.OldEntryMode)
newMode := entryModeTr(diffFile.EntryMode)
return locale.TrString("git.filemode.changed_filemode", oldMode, newMode)
}
if diffFile.EntryMode != "" {
if entryMode := git.ParseEntryMode(diffFile.EntryMode); !entryMode.IsRegular() {
return entryModeTr(diffFile.EntryMode)
}
}
return ""
} }
type limitByteWriter struct { type limitByteWriter struct {
@ -695,10 +710,10 @@ parsingLoop:
strings.HasPrefix(line, "new mode "): strings.HasPrefix(line, "new mode "):
if strings.HasPrefix(line, "old mode ") { if strings.HasPrefix(line, "old mode ") {
curFile.OldMode = prepareValue(line, "old mode ") curFile.OldEntryMode = prepareValue(line, "old mode ")
} }
if strings.HasPrefix(line, "new mode ") { if strings.HasPrefix(line, "new mode ") {
curFile.Mode = prepareValue(line, "new mode ") curFile.EntryMode = prepareValue(line, "new mode ")
} }
if strings.HasSuffix(line, " 160000\n") { if strings.HasSuffix(line, " 160000\n") {
curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{}
@ -733,7 +748,7 @@ parsingLoop:
curFile.Type = DiffFileAdd curFile.Type = DiffFileAdd
curFile.IsCreated = true curFile.IsCreated = true
if strings.HasPrefix(line, "new file mode ") { if strings.HasPrefix(line, "new file mode ") {
curFile.Mode = prepareValue(line, "new file mode ") curFile.EntryMode = prepareValue(line, "new file mode ")
} }
if strings.HasSuffix(line, " 160000\n") { if strings.HasSuffix(line, " 160000\n") {
curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{} curFile.IsSubmodule, curFile.SubmoduleDiffInfo = true, &SubmoduleDiffInfo{}

View File

@ -11,7 +11,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"maps"
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
@ -487,40 +486,32 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa
rep.Error = err rep.Error = err
} else { } else {
rep.Actions = make(map[string]*lfs_module.Link) rep.Actions = make(map[string]*lfs_module.Link)
header := make(map[string]string)
if len(rc.Authorization) > 0 {
header["Authorization"] = rc.Authorization
}
if download { if download {
var link *lfs_module.Link var link *lfs_module.Link
if setting.LFS.Storage.ServeDirect() { if setting.LFS.Storage.ServeDirect() {
// If we have a signed url (S3, object storage), redirect to this directly. // If we have a signed url (S3, object storage), redirect to this directly.
u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid, rc.Method, nil) u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid, rc.Method, nil)
if u != nil && err == nil { if u != nil && err == nil {
// Presigned url does not need the Authorization header link = lfs_module.NewLink(u.String()) // Presigned url does not need the Authorization header
// https://github.com/go-gitea/gitea/issues/21525
delete(header, "Authorization")
link = &lfs_module.Link{Href: u.String(), Header: header}
} }
} }
if link == nil { if link == nil {
link = &lfs_module.Link{Href: rc.DownloadLink(pointer), Header: header} link = lfs_module.NewLink(rc.DownloadLink(pointer)).WithHeader("Authorization", rc.Authorization)
} }
rep.Actions["download"] = link rep.Actions["download"] = link
} }
if upload { if upload {
rep.Actions["upload"] = &lfs_module.Link{Href: rc.UploadLink(pointer), Header: header} // Set Transfer-Encoding header to enable chunked uploads. Required by git-lfs client to do chunked transfer.
// See: https://github.com/git-lfs/git-lfs/blob/main/tq/basic_upload.go#L58-59
rep.Actions["upload"] = lfs_module.NewLink(rc.UploadLink(pointer)).
WithHeader("Authorization", rc.Authorization).
WithHeader("Transfer-Encoding", "chunked")
verifyHeader := make(map[string]string) // "Accept" header is the workaround for git-lfs < 2.8.0 (before 2019).
maps.Copy(verifyHeader, header) // This workaround could be removed in the future: https://github.com/git-lfs/git-lfs/issues/3662
rep.Actions["verify"] = lfs_module.NewLink(rc.VerifyLink(pointer)).
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662 WithHeader("Authorization", rc.Authorization).
verifyHeader["Accept"] = lfs_module.AcceptHeader WithHeader("Accept", lfs_module.AcceptHeader)
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer), Header: verifyHeader}
} }
} }
return rep return rep

View File

@ -7,9 +7,12 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"slices"
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/renderhelper" "code.gitea.io/gitea/models/renderhelper"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
@ -44,6 +47,16 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) {
return return
} }
if err := rel.LoadRepo(ctx); err != nil {
log.Error("rel.LoadRepo: %v", err)
return
}
// delete publisher or any users with no permission
recipients = slices.DeleteFunc(recipients, func(u *user_model.User) bool {
return u.ID == rel.PublisherID || !access_model.CheckRepoUnitUser(ctx, rel.Repo, u, unit.TypeReleases)
})
langMap := make(map[string][]*user_model.User) langMap := make(map[string][]*user_model.User)
for _, user := range recipients { for _, user := range recipients {
if user.ID != rel.PublisherID { if user.ID != rel.PublisherID {

View File

@ -0,0 +1,71 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package mailer
import (
"testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
sender_service "code.gitea.io/gitea/services/mailer/sender"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMailNewReleaseFiltersUnauthorizedWatchers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
origMailService := setting.MailService
origDomain := setting.Domain
origAppName := setting.AppName
origAppURL := setting.AppURL
origTemplates := LoadedTemplates()
defer func() {
setting.MailService = origMailService
setting.Domain = origDomain
setting.AppName = origAppName
setting.AppURL = origAppURL
loadedTemplates.Store(origTemplates)
}()
setting.MailService = &setting.Mailer{
From: "Gitea",
FromEmail: "noreply@example.com",
}
setting.Domain = "example.com"
setting.AppName = "Gitea"
setting.AppURL = "https://example.com/"
prepareMailTemplates(string(tplNewReleaseMail), "{{.Subject}}", "<p>{{.Release.TagName}}</p>")
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
require.True(t, repo.IsPrivate)
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
unauthorized := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
assert.NoError(t, repo_model.WatchRepo(t.Context(), admin, repo, true))
assert.NoError(t, repo_model.WatchRepo(t.Context(), unauthorized, repo, true))
rel := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: 11})
rel.Repo = nil
rel.Publisher = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: rel.PublisherID})
var sent []*sender_service.Message
origSend := SendAsync
SendAsync = func(msgs ...*sender_service.Message) {
sent = append(sent, msgs...)
}
defer func() {
SendAsync = origSend
}()
MailNewRelease(t.Context(), rel)
require.Len(t, sent, 1)
assert.Equal(t, admin.EmailTo(), sent[0].To)
assert.NotEqual(t, unauthorized.EmailTo(), sent[0].To)
}

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
@ -62,6 +63,36 @@ func TestTeam_RemoveMember(t *testing.T) {
assert.True(t, organization.IsErrLastOrgOwner(err)) assert.True(t, organization.IsErrLastOrgOwner(err))
} }
func TestRemoveTeamMemberRemovesSubscriptionsAndStopwatches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
assert.NoError(t, repo_model.WatchRepo(ctx, user, repo, true))
assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, issue.ID, true))
ok, err := issues_model.CreateIssueStopwatch(ctx, user, issue)
assert.NoError(t, err)
assert.True(t, ok)
assert.NoError(t, RemoveTeamMember(ctx, team, user))
watch, err := repo_model.GetWatch(ctx, user.ID, repo.ID)
assert.NoError(t, err)
assert.False(t, repo_model.IsWatchMode(watch.Mode))
_, exists, err := issues_model.GetIssueWatch(ctx, user.ID, issue.ID)
assert.NoError(t, err)
assert.False(t, exists)
hasStopwatch, _, _, err := issues_model.HasUserStopwatch(ctx, user.ID)
assert.NoError(t, err)
assert.False(t, hasStopwatch)
}
func TestNewTeam(t *testing.T) { func TestNewTeam(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())

View File

@ -6,6 +6,8 @@ package pull
import ( import (
"context" "context"
"errors"
"fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
@ -14,8 +16,6 @@ import (
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/glob" "code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/pkg/errors"
) )
// MergeRequiredContextsCommitStatus returns a commit status state for given required contexts // MergeRequiredContextsCommitStatus returns a commit status state for given required contexts
@ -69,7 +69,7 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) { func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
if err != nil { if err != nil {
return false, errors.Wrap(err, "GetLatestCommitStatus") return false, fmt.Errorf("GetLatestCommitStatus: %w", err)
} }
if pb == nil || !pb.EnableStatusCheck { if pb == nil || !pb.EnableStatusCheck {
return true, nil return true, nil
@ -86,19 +86,19 @@ func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (
func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (commitstatus.CommitStatusState, error) { func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (commitstatus.CommitStatusState, error) {
// Ensure HeadRepo is loaded // Ensure HeadRepo is loaded
if err := pr.LoadHeadRepo(ctx); err != nil { if err := pr.LoadHeadRepo(ctx); err != nil {
return "", errors.Wrap(err, "LoadHeadRepo") return "", fmt.Errorf("LoadHeadRepo: %w", err)
} }
// check if all required status checks are successful // check if all required status checks are successful
headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.HeadRepo) headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.HeadRepo)
if err != nil { if err != nil {
return "", errors.Wrap(err, "OpenRepository") return "", fmt.Errorf("OpenRepository: %w", err)
} }
defer closer.Close() defer closer.Close()
if pr.Flow == issues_model.PullRequestFlowGithub { if pr.Flow == issues_model.PullRequestFlowGithub {
if exist, err := git_model.IsBranchExist(ctx, pr.HeadRepo.ID, pr.HeadBranch); err != nil { if exist, err := git_model.IsBranchExist(ctx, pr.HeadRepo.ID, pr.HeadBranch); err != nil {
return "", errors.Wrap(err, "IsBranchExist") return "", fmt.Errorf("IsBranchExist: %w", err)
} else if !exist { } else if !exist {
return "", errors.New("Head branch does not exist, can not merge") return "", errors.New("Head branch does not exist, can not merge")
} }
@ -118,17 +118,17 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
} }
if err := pr.LoadBaseRepo(ctx); err != nil { if err := pr.LoadBaseRepo(ctx); err != nil {
return "", errors.Wrap(err, "LoadBaseRepo") return "", fmt.Errorf("LoadBaseRepo: %w", err)
} }
commitStatuses, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll) commitStatuses, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
return "", errors.Wrap(err, "GetLatestCommitStatus") return "", fmt.Errorf("GetLatestCommitStatus: %w", err)
} }
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
if err != nil { if err != nil {
return "", errors.Wrap(err, "LoadProtectedBranch") return "", fmt.Errorf("LoadProtectedBranch: %w", err)
} }
var requiredContexts []string var requiredContexts []string
if pb != nil { if pb != nil {

View File

@ -1077,7 +1077,7 @@ func GetPullCommits(ctx context.Context, baseGitRepo *git.Repository, doer *user
if pull.HasMerged { if pull.HasMerged {
baseBranch = pull.MergeBase baseBranch = pull.MergeBase
} }
compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, git.RefNameFromBranch(baseBranch), git.RefName(pull.GetGitHeadRefName()), true, false) compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, git.RefNameFromBranch(baseBranch), git.RefName(pull.GetGitHeadRefName()), false, false)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }

View File

@ -8,7 +8,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"strings" "strings"
"time" "time"
@ -16,6 +15,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/httplib"
@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
gitea_context "code.gitea.io/gitea/services/context" gitea_context "code.gitea.io/gitea/services/context"
) )
@ -36,58 +37,31 @@ type ArchiveRequest struct {
Repo *repo_model.Repository Repo *repo_model.Repository
Type repo_model.ArchiveType Type repo_model.ArchiveType
CommitID string CommitID string
Paths []string
archiveRefShortName string // the ref short name to download the archive, for example: "master", "v1.0.0", "commit id" archiveRefShortName string // the ref short name to download the archive, for example: "master", "v1.0.0", "commit id"
} }
// ErrUnknownArchiveFormat request archive format is not supported
type ErrUnknownArchiveFormat struct {
RequestNameType string
}
// Error implements error
func (err ErrUnknownArchiveFormat) Error() string {
return "unknown format: " + err.RequestNameType
}
// Is implements error
func (ErrUnknownArchiveFormat) Is(err error) bool {
_, ok := err.(ErrUnknownArchiveFormat)
return ok
}
// RepoRefNotFoundError is returned when a requested reference (commit, tag) was not found.
type RepoRefNotFoundError struct {
RefShortName string
}
// Error implements error.
func (e RepoRefNotFoundError) Error() string {
return "unrecognized repository reference: " + e.RefShortName
}
func (e RepoRefNotFoundError) Is(err error) bool {
_, ok := err.(RepoRefNotFoundError)
return ok
}
// NewRequest creates an archival request, based on the URI. The // NewRequest creates an archival request, based on the URI. The
// resulting ArchiveRequest is suitable for being passed to Await() // resulting ArchiveRequest is suitable for being passed to Await()
// if it's determined that the request still needs to be satisfied. // if it's determined that the request still needs to be satisfied.
func NewRequest(repo *repo_model.Repository, gitRepo *git.Repository, archiveRefExt string) (*ArchiveRequest, error) { func NewRequest(repo *repo_model.Repository, gitRepo *git.Repository, archiveRefExt string, paths []string) (*ArchiveRequest, error) {
// here the archiveRefShortName is not a clear ref, it could be a tag, branch or commit id // here the archiveRefShortName is not a clear ref, it could be a tag, branch or commit id
archiveRefShortName, archiveType := repo_model.SplitArchiveNameType(archiveRefExt) archiveRefShortName, archiveType := repo_model.SplitArchiveNameType(archiveRefExt)
if archiveType == repo_model.ArchiveUnknown { if archiveType == repo_model.ArchiveUnknown {
return nil, ErrUnknownArchiveFormat{archiveRefExt} return nil, util.NewInvalidArgumentErrorf("unknown format: %s", archiveRefExt)
}
if archiveType == repo_model.ArchiveBundle && len(paths) != 0 {
return nil, util.NewInvalidArgumentErrorf("cannot specify paths when requesting a bundle")
} }
// Get corresponding commit. // Get corresponding commit.
commitID, err := gitRepo.ConvertToGitID(archiveRefShortName) commitID, err := gitRepo.ConvertToGitID(archiveRefShortName)
if err != nil { if err != nil {
return nil, RepoRefNotFoundError{RefShortName: archiveRefShortName} return nil, util.NewNotExistErrorf("unrecognized repository reference: %s", archiveRefShortName)
} }
r := &ArchiveRequest{Repo: repo, archiveRefShortName: archiveRefShortName, Type: archiveType} r := &ArchiveRequest{Repo: repo, archiveRefShortName: archiveRefShortName, Type: archiveType, Paths: paths}
r.CommitID = commitID.String() r.CommitID = commitID.String()
return r, nil return r, nil
} }
@ -159,6 +133,7 @@ func (aReq *ArchiveRequest) Stream(ctx context.Context, w io.Writer) error {
w, w,
setting.Repository.PrefixArchiveFiles, setting.Repository.PrefixArchiveFiles,
aReq.CommitID, aReq.CommitID,
aReq.Paths,
) )
} }
@ -339,7 +314,7 @@ func DeleteRepositoryArchives(ctx context.Context) error {
return storage.Clean(storage.RepoArchives) return storage.Clean(storage.RepoArchives)
} }
func ServeRepoArchive(ctx *gitea_context.Base, archiveReq *ArchiveRequest) { func ServeRepoArchive(ctx *gitea_context.Base, archiveReq *ArchiveRequest) error {
// Add nix format link header so tarballs lock correctly: // Add nix format link header so tarballs lock correctly:
// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md // https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.%s?rev=%s>; rel="immutable"`, ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.%s?rev=%s>; rel="immutable"`,
@ -350,20 +325,22 @@ func ServeRepoArchive(ctx *gitea_context.Base, archiveReq *ArchiveRequest) {
)) ))
downloadName := archiveReq.Repo.Name + "-" + archiveReq.GetArchiveName() downloadName := archiveReq.Repo.Name + "-" + archiveReq.GetArchiveName()
if setting.Repository.StreamArchives { if setting.Repository.StreamArchives || len(archiveReq.Paths) > 0 {
// the header must be set before starting streaming even an error would occur,
// because errors may happen in git command and such cases aren't in our control.
httplib.ServeSetHeaders(ctx.Resp, &httplib.ServeHeaderOptions{Filename: downloadName}) httplib.ServeSetHeaders(ctx.Resp, &httplib.ServeHeaderOptions{Filename: downloadName})
if err := archiveReq.Stream(ctx, ctx.Resp); err != nil && !ctx.Written() { if err := archiveReq.Stream(ctx, ctx.Resp); err != nil && !ctx.Written() {
log.Error("Archive %v streaming failed: %v", archiveReq, err) if gitcmd.StderrHasPrefix(err, "fatal: pathspec") {
ctx.HTTPError(http.StatusInternalServerError) return util.NewInvalidArgumentErrorf("path doesn't exist or is invalid")
}
return fmt.Errorf("archive repo %s: failed to stream: %w", archiveReq.Repo.FullName(), err)
} }
return return nil
} }
archiver, err := archiveReq.Await(ctx) archiver, err := archiveReq.Await(ctx)
if err != nil { if err != nil {
log.Error("Archive %v await failed: %v", archiveReq, err) return fmt.Errorf("archive repo %s: failed to await: %w", archiveReq.Repo.FullName(), err)
ctx.HTTPError(http.StatusInternalServerError)
return
} }
rPath := archiver.RelativePath() rPath := archiver.RelativePath()
@ -372,15 +349,13 @@ func ServeRepoArchive(ctx *gitea_context.Base, archiveReq *ArchiveRequest) {
u, err := storage.RepoArchives.URL(rPath, downloadName, ctx.Req.Method, nil) u, err := storage.RepoArchives.URL(rPath, downloadName, ctx.Req.Method, nil)
if u != nil && err == nil { if u != nil && err == nil {
ctx.Redirect(u.String()) ctx.Redirect(u.String())
return return nil
} }
} }
fr, err := storage.RepoArchives.Open(rPath) fr, err := storage.RepoArchives.Open(rPath)
if err != nil { if err != nil {
log.Error("Archive %v open file failed: %v", archiveReq, err) return fmt.Errorf("archive repo %s: failed to open archive file: %w", archiveReq.Repo.FullName(), err)
ctx.HTTPError(http.StatusInternalServerError)
return
} }
defer fr.Close() defer fr.Close()
@ -388,4 +363,5 @@ func ServeRepoArchive(ctx *gitea_context.Base, archiveReq *ArchiveRequest) {
Filename: downloadName, Filename: downloadName,
LastModified: archiver.CreatedUnix.AsLocalTime(), LastModified: archiver.CreatedUnix.AsLocalTime(),
}) })
return nil
} }

View File

@ -8,11 +8,13 @@ import (
"time" "time"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/contexttest" "code.gitea.io/gitea/services/contexttest"
_ "code.gitea.io/gitea/models/actions" _ "code.gitea.io/gitea/models/actions"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -29,47 +31,47 @@ func TestArchive_Basic(t *testing.T) {
contexttest.LoadGitRepo(t, ctx) contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close() defer ctx.Repo.GitRepo.Close()
bogusReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") bogusReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, bogusReq) assert.NotNil(t, bogusReq)
assert.Equal(t, firstCommit+".zip", bogusReq.GetArchiveName()) assert.Equal(t, firstCommit+".zip", bogusReq.GetArchiveName())
// Check a series of bogus requests. // Check a series of bogus requests.
// Step 1, valid commit with a bad extension. // Step 1, valid commit with a bad extension.
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".unknown") bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".unknown", nil)
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, bogusReq) assert.Nil(t, bogusReq)
// Step 2, missing commit. // Step 2, missing commit.
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "dbffff.zip") bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "dbffff.zip", nil)
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, bogusReq) assert.Nil(t, bogusReq)
// Step 3, doesn't look like branch/tag/commit. // Step 3, doesn't look like branch/tag/commit.
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "db.zip") bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "db.zip", nil)
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, bogusReq) assert.Nil(t, bogusReq)
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "master.zip") bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "master.zip", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, bogusReq) assert.NotNil(t, bogusReq)
assert.Equal(t, "master.zip", bogusReq.GetArchiveName()) assert.Equal(t, "master.zip", bogusReq.GetArchiveName())
bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "test/archive.zip") bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "test/archive.zip", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, bogusReq) assert.NotNil(t, bogusReq)
assert.Equal(t, "test-archive.zip", bogusReq.GetArchiveName()) assert.Equal(t, "test-archive.zip", bogusReq.GetArchiveName())
// Now two valid requests, firstCommit with valid extensions. // Now two valid requests, firstCommit with valid extensions.
zipReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") zipReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, zipReq) assert.NotNil(t, zipReq)
tgzReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".tar.gz") tgzReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".tar.gz", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, tgzReq) assert.NotNil(t, tgzReq)
secondReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".bundle") secondReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".bundle", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, secondReq) assert.NotNil(t, secondReq)
@ -89,7 +91,7 @@ func TestArchive_Basic(t *testing.T) {
// Sleep two seconds to make sure the queue doesn't change. // Sleep two seconds to make sure the queue doesn't change.
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
zipReq2, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") zipReq2, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
assert.NoError(t, err) assert.NoError(t, err)
// This zipReq should match what's sitting in the queue, as we haven't // This zipReq should match what's sitting in the queue, as we haven't
// let it release yet. From the consumer's point of view, this looks like // let it release yet. From the consumer's point of view, this looks like
@ -104,12 +106,12 @@ func TestArchive_Basic(t *testing.T) {
// Now we'll submit a request and TimedWaitForCompletion twice, before and // Now we'll submit a request and TimedWaitForCompletion twice, before and
// after we release it. We should trigger both the timeout and non-timeout // after we release it. We should trigger both the timeout and non-timeout
// cases. // cases.
timedReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".tar.gz") timedReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".tar.gz", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, timedReq) assert.NotNil(t, timedReq)
doArchive(t.Context(), timedReq) doArchive(t.Context(), timedReq)
zipReq2, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") zipReq2, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip", nil)
assert.NoError(t, err) assert.NoError(t, err)
// Now, we're guaranteed to have released the original zipReq from the queue. // Now, we're guaranteed to have released the original zipReq from the queue.
// Ensure that we don't get handed back the released entry somehow, but they // Ensure that we don't get handed back the released entry somehow, but they
@ -124,9 +126,13 @@ func TestArchive_Basic(t *testing.T) {
// Ideally, the extension would match what we originally requested. // Ideally, the extension would match what we originally requested.
assert.NotEqual(t, zipReq.GetArchiveName(), tgzReq.GetArchiveName()) assert.NotEqual(t, zipReq.GetArchiveName(), tgzReq.GetArchiveName())
assert.NotEqual(t, zipReq.GetArchiveName(), secondReq.GetArchiveName()) assert.NotEqual(t, zipReq.GetArchiveName(), secondReq.GetArchiveName())
}
func TestErrUnknownArchiveFormat(t *testing.T) { t.Run("BadPath", func(t *testing.T) {
err := ErrUnknownArchiveFormat{RequestNameType: "xxx"} badRequest, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".tar.gz", []string{"not-a-path"})
assert.ErrorIs(t, err, ErrUnknownArchiveFormat{}) require.NoError(t, err)
err = ServeRepoArchive(ctx.Base, badRequest)
require.Error(t, err)
assert.ErrorIs(t, err, util.ErrInvalidArgument)
assert.ErrorContains(t, err, "path doesn't exist or is invalid")
})
} }

View File

@ -120,6 +120,11 @@ func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *u
return err return err
} }
// Remove all stopwatches a user has running in the repository
if err := issues_model.RemoveStopwatchesByRepoID(ctx, user.ID, repo.ID); err != nil {
return err
}
// Remove all IssueWatches a user has subscribed to in the repository // Remove all IssueWatches a user has subscribed to in the repository
return issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID) return issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID)
} }

View File

@ -6,7 +6,10 @@ package repository
import ( import (
"testing" "testing"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -32,8 +35,8 @@ func TestRepository_AddCollaborator(t *testing.T) {
func TestRepository_DeleteCollaboration(t *testing.T) { func TestRepository_DeleteCollaboration(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
assert.NoError(t, repo.LoadOwner(t.Context())) assert.NoError(t, repo.LoadOwner(t.Context()))
assert.NoError(t, DeleteCollaboration(t.Context(), repo, user)) assert.NoError(t, DeleteCollaboration(t.Context(), repo, user))
@ -44,3 +47,50 @@ func TestRepository_DeleteCollaboration(t *testing.T) {
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID}) unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
} }
func TestRepository_DeleteCollaborationRemovesSubscriptionsAndStopwatches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
assert.NoError(t, repo.LoadOwner(ctx))
assert.NoError(t, repo_model.WatchRepo(ctx, user, repo, true))
hasAccess, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo)
assert.NoError(t, err)
assert.True(t, hasAccess)
issueCount, err := db.GetEngine(ctx).Where("repo_id=?", repo.ID).Count(new(issues_model.Issue))
assert.NoError(t, err)
tempIssue := &issues_model.Issue{
RepoID: repo.ID,
Index: issueCount + 1,
PosterID: repo.OwnerID,
Title: "temp issue",
Content: "temp",
}
assert.NoError(t, db.Insert(ctx, tempIssue))
assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, tempIssue.ID, true))
ok, err := issues_model.CreateIssueStopwatch(ctx, user, tempIssue)
assert.NoError(t, err)
assert.True(t, ok)
assert.NoError(t, DeleteCollaboration(ctx, repo, user))
hasAccess, err = access_model.HasAnyUnitAccess(ctx, user.ID, repo)
assert.NoError(t, err)
assert.False(t, hasAccess)
watch, err := repo_model.GetWatch(ctx, user.ID, repo.ID)
assert.NoError(t, err)
assert.False(t, repo_model.IsWatchMode(watch.Mode))
_, exists, err := issues_model.GetIssueWatch(ctx, user.ID, tempIssue.ID)
assert.NoError(t, err)
assert.False(t, exists)
hasStopwatch, _, _, err := issues_model.HasUserStopwatch(ctx, user.ID)
assert.NoError(t, err)
assert.False(t, hasStopwatch)
}

View File

@ -194,6 +194,10 @@ func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err erro
return err return err
} }
if err = repo_model.ClearRepoWatches(ctx, repo.ID); err != nil {
return err
}
// Create/Remove git-daemon-export-ok for git-daemon... // Create/Remove git-daemon-export-ok for git-daemon...
if err := CheckDaemonExportOK(ctx, repo); err != nil { if err := CheckDaemonExportOK(ctx, repo); err != nil {
return err return err
@ -217,28 +221,25 @@ func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err erro
}) })
} }
// LinkedRepository returns the linked repo if any // GetAttachmentLinkedType returns the linked type of attachment if any
func LinkedRepository(ctx context.Context, a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { func GetAttachmentLinkedType(ctx context.Context, a *repo_model.Attachment) (unit.Type, error) {
if a.IssueID != 0 { if a.IssueID != 0 {
iss, err := issues_model.GetIssueByID(ctx, a.IssueID) iss, err := issues_model.GetIssueByID(ctx, a.IssueID)
if err != nil { if err != nil {
return nil, unit.TypeIssues, err return unit.TypeIssues, err
} }
repo, err := repo_model.GetRepositoryByID(ctx, iss.RepoID)
unitType := unit.TypeIssues unitType := unit.TypeIssues
if iss.IsPull { if iss.IsPull {
unitType = unit.TypePullRequests unitType = unit.TypePullRequests
} }
return repo, unitType, err return unitType, nil
} else if a.ReleaseID != 0 {
rel, err := repo_model.GetReleaseByID(ctx, a.ReleaseID)
if err != nil {
return nil, unit.TypeReleases, err
}
repo, err := repo_model.GetRepositoryByID(ctx, rel.RepoID)
return repo, unit.TypeReleases, err
} }
return nil, -1, nil
if a.ReleaseID != 0 {
_, err := repo_model.GetReleaseByID(ctx, a.ReleaseID)
return unit.TypeReleases, err
}
return unit.TypeInvalid, nil
} }
// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... // CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...

View File

@ -13,30 +13,27 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestLinkedRepository(t *testing.T) { func TestAttachLinkedType(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
testCases := []struct { testCases := []struct {
name string name string
attachID int64 attachID int64
expectedRepo *repo_model.Repository
expectedUnitType unit.Type expectedUnitType unit.Type
}{ }{
{"LinkedIssue", 1, &repo_model.Repository{ID: 1}, unit.TypeIssues}, {"LinkedIssue", 1, unit.TypeIssues},
{"LinkedComment", 3, &repo_model.Repository{ID: 1}, unit.TypePullRequests}, {"LinkedComment", 3, unit.TypePullRequests},
{"LinkedRelease", 9, &repo_model.Repository{ID: 1}, unit.TypeReleases}, {"LinkedRelease", 9, unit.TypeReleases},
{"Notlinked", 10, nil, -1}, {"Notlinked", 10, unit.TypeInvalid},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
attach, err := repo_model.GetAttachmentByID(t.Context(), tc.attachID) attach, err := repo_model.GetAttachmentByID(t.Context(), tc.attachID)
assert.NoError(t, err) assert.NoError(t, err)
repo, unitType, err := LinkedRepository(t.Context(), attach) unitType, err := GetAttachmentLinkedType(t.Context(), attach)
assert.NoError(t, err) assert.NoError(t, err)
if tc.expectedRepo != nil {
assert.Equal(t, tc.expectedRepo.ID, repo.ID)
}
assert.Equal(t, tc.expectedUnitType, unitType) assert.Equal(t, tc.expectedUnitType, unitType)
}) })
} }
@ -70,3 +67,24 @@ func TestRepository_HasWiki(t *testing.T) {
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.False(t, HasWiki(t.Context(), repo2)) assert.False(t, HasWiki(t.Context(), repo2))
} }
func TestMakeRepoPrivateClearsWatches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo.IsPrivate = false
watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
require.NoError(t, err)
require.NotEmpty(t, watchers)
assert.NoError(t, MakeRepoPrivate(t.Context(), repo))
watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
assert.NoError(t, err)
assert.Empty(t, watchers)
updatedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.True(t, updatedRepo.IsPrivate)
assert.Zero(t, updatedRepo.NumWatches)
}

View File

@ -239,6 +239,11 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
if err := deleteUser(ctx, u, purge); err != nil { if err := deleteUser(ctx, u, purge); err != nil {
return fmt.Errorf("DeleteUser: %w", err) return fmt.Errorf("DeleteUser: %w", err)
} }
// Finally delete any unlinked attachments, this will also delete the attached files
if err := deleteUserUnlinkedAttachments(ctx, u); err != nil {
return fmt.Errorf("deleteUserUnlinkedAttachments: %w", err)
}
return nil return nil
}); err != nil { }); err != nil {
return err return err
@ -269,6 +274,19 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
return nil return nil
} }
func deleteUserUnlinkedAttachments(ctx context.Context, u *user_model.User) error {
attachments, err := repo_model.GetUnlinkedAttachmentsByUserID(ctx, u.ID)
if err != nil {
return fmt.Errorf("GetUnlinkedAttachmentsByUserID: %w", err)
}
for _, attach := range attachments {
if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil {
return fmt.Errorf("DeleteAttachment ID[%d]: %w", attach.ID, err)
}
}
return nil
}
// DeleteInactiveUsers deletes all inactive users and their email addresses. // DeleteInactiveUsers deletes all inactive users and their email addresses.
func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
inactiveUsers, err := user_model.GetInactiveUsers(ctx, olderThan) inactiveUsers, err := user_model.GetInactiveUsers(ctx, olderThan)

View File

@ -63,6 +63,24 @@ func TestDeleteUser(t *testing.T) {
assert.Error(t, DeleteUser(t.Context(), org, false)) assert.Error(t, DeleteUser(t.Context(), org, false))
} }
func TestDeleteUserUnlinkedAttachments(t *testing.T) {
t.Run("DeleteExisting", func(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 8})
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 10})
assert.NoError(t, deleteUserUnlinkedAttachments(t.Context(), user))
unittest.AssertNotExistsBean(t, &repo_model.Attachment{ID: 10})
})
t.Run("NoUnlinkedAttachments", func(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
assert.NoError(t, deleteUserUnlinkedAttachments(t.Context(), user))
})
}
func TestPurgeUser(t *testing.T) { func TestPurgeUser(t *testing.T) {
test := func(userID int64) { test := func(userID int64) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())

View File

@ -1,5 +1,5 @@
// TODO: Move to .ts after https://github.com/stylelint/stylelint/issues/8893 is fixed
import {fileURLToPath} from 'node:url'; import {fileURLToPath} from 'node:url';
import type {Config} from 'stylelint';
const cssVarFiles = [ const cssVarFiles = [
fileURLToPath(new URL('web_src/css/base.css', import.meta.url)), fileURLToPath(new URL('web_src/css/base.css', import.meta.url)),
@ -146,4 +146,4 @@ export default {
'shorthand-property-no-redundant-values': true, 'shorthand-property-no-redundant-values': true,
'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}], 'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}],
}, },
} satisfies Config; };

View File

@ -82,43 +82,42 @@
{{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}}
{{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}} {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}}
<div class="diff-file-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}> <div class="diff-file-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<h4 class="diff-file-header sticky-2nd-row ui top attached header"> <div class="diff-file-header sticky-2nd-row ui top attached header">
<div class="diff-file-name tw-flex tw-flex-1 tw-items-center tw-gap-1 tw-flex-wrap"> <div class="diff-file-name tw-flex tw-flex-1 tw-items-center tw-gap-1 tw-flex-wrap">
<button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}"> <div class="flex-text-block">
{{if $file.ShouldBeHidden}} <button class="fold-file btn interact-bg tw-flex-shrink-0 tw-p-1{{if not $isExpandable}} tw-invisible{{end}}">
{{svg "octicon-chevron-right" 18}} {{if $file.ShouldBeHidden}}
{{else}} {{svg "octicon-chevron-right" 18}}
{{svg "octicon-chevron-down" 18}} {{else}}
{{end}} {{svg "octicon-chevron-down" 18}}
</button> {{end}}
<div class="tw-font-semibold tw-flex tw-items-center tw-font-mono"> </button>
{{if $file.IsBin}} {{$entryModeText := $file.TranslateDiffEntryMode ctx.Locale}}
<span class="tw-ml-0.5 tw-mr-2"> <a class="muted file-link tw-font-mono" title="{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">
{{ctx.Locale.Tr "repo.diff.bin"}} {{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}
</span> </a>
{{else}}
{{template "repo/diff/stats" dict "file" . "root" $}}
{{end}}
</div> </div>
<span class="file tw-flex tw-items-center tw-font-mono tw-flex-1"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}}{{end}}{{$file.Name}}</a> <button class="btn interact-fg tw-p-2 tw-shrink-0" data-clipboard-text="{{$file.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
<button class="btn interact-fg tw-p-2" data-clipboard-text="{{$file.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button> {{if $file.IsLFSFile}}
{{if .IsLFSFile}}<span class="ui label">LFS</span>{{end}} <span class="ui label">LFS</span>
{{if $file.IsGenerated}} {{end}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span> {{if $file.IsGenerated}}
{{end}} <span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span>
{{if $file.IsVendored}} {{end}}
<span class="ui label">{{ctx.Locale.Tr "repo.diff.vendored"}}</span> {{if $file.IsVendored}}
{{end}} <span class="ui label">{{ctx.Locale.Tr "repo.diff.vendored"}}</span>
{{if and $file.Mode $file.OldMode}} {{end}}
{{$old := ctx.Locale.Tr ($file.ModeTranslationKey $file.OldMode)}} {{if $entryModeText}}
{{$new := ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}} <span class="ui label">{{$entryModeText}}</span>
<span class="tw-mx-2 tw-font-mono tw-whitespace-nowrap">{{ctx.Locale.Tr "git.filemode.changed_filemode" $old $new}}</span> {{end}}
{{else if $file.Mode}}
<span class="tw-mx-2 tw-font-mono tw-whitespace-nowrap">{{ctx.Locale.Tr ($file.ModeTranslationKey $file.Mode)}}</span>
{{end}}
</span>
</div> </div>
<div class="diff-file-header-actions tw-flex tw-items-center tw-gap-1 tw-flex-wrap"> <div class="diff-file-header-actions flex-text-block tw-justify-end tw-flex-wrap">
{{if $file.IsBin}}
{{ctx.Locale.Tr "repo.diff.bin"}}
{{else}}
{{template "repo/diff/stats" dict "Addition" .Addition "Deletion" .Deletion}}
{{end}}
{{if $showFileViewToggle}} {{if $showFileViewToggle}}
<div class="ui compact icon buttons"> <div class="ui compact icon buttons">
<button class="ui tiny basic button file-view-toggle" data-toggle-selector="#diff-source-{{$file.NameHash}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_source"}}">{{svg "octicon-code"}}</button> <button class="ui tiny basic button file-view-toggle" data-toggle-selector="#diff-source-{{$file.NameHash}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_source"}}">{{svg "octicon-code"}}</button>
@ -157,7 +156,7 @@
</div> </div>
{{end}} {{end}}
</div> </div>
</h4> </div>
<div class="diff-file-body ui attached unstackable table segment" {{if and $file.IsViewed $.IsShowingAllCommits}}data-folded="true"{{end}}> <div class="diff-file-body ui attached unstackable table segment" {{if and $file.IsViewed $.IsShowingAllCommits}}data-folded="true"{{end}}>
<div id="diff-source-{{$file.NameHash}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} tw-hidden{{end}}"> <div id="diff-source-{{$file.NameHash}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} tw-hidden{{end}}">
{{if or $file.IsIncomplete $file.IsBin}} {{if or $file.IsIncomplete $file.IsBin}}

View File

@ -1,5 +1,17 @@
{{Eval .file.Addition "+" .file.Deletion}} {{/* Template Attributes:
<span class="diff-stats-bar tw-mx-2" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .file.Addition "+" .file.Deletion) .file.Addition .file.Deletion}}"> * Addition: Number of additions
{{/* if the denominator is zero, then the float result is "width: NaNpx", as before, it just works */}} * Deletion: Number of deletions
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .file.Addition "/" "(" .file.Addition "+" .file.Deletion "+" 0.0 ")"}}%"></div> * Classes: Additional classes for the root element
</span> */}}
{{if or .Addition .Deletion}}
<div class="flex-text-block tw-flex-shrink-0 tw-text-[13px] {{if .Classes}}{{.Classes}}{{end}}">
<span>
{{if .Addition}}<span class="tw-text-diff-added-fg">+{{.Addition}}</span>{{end}}
{{if .Deletion}}<span class="tw-text-diff-removed-fg">-{{.Deletion}}</span>{{end}}
</span>
<span class="diff-stats-bar" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.stats_desc_file" (Eval .Addition "+" .Deletion) .Addition .Deletion}}">
{{/* if the denominator is zero, then the float result is "width: NaNpx", as before, it just works */}}
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Addition "/" "(" .Addition "+" .Deletion "+" 0.0 ")"}}%"></div>
</span>
</div>
{{end}}

View File

@ -8,6 +8,8 @@
data-pull-link="{{.Issue.Link}}" data-pull-link="{{.Issue.Link}}"
{{end}} {{end}}
> >
{{$statusCheckData := .StatusCheckData}}
{{$requiredStatusCheckState := $statusCheckData.RequiredChecksState}}
<div class="timeline-avatar text {{if .Issue.PullRequest.HasMerged}}purple <div class="timeline-avatar text {{if .Issue.PullRequest.HasMerged}}purple
{{- else if .Issue.IsClosed}}grey {{- else if .Issue.IsClosed}}grey
{{- else if .IsPullWorkInProgress}}grey {{- else if .IsPullWorkInProgress}}grey
@ -18,8 +20,8 @@
{{- else if .IsBlockedByOfficialReviewRequests}}red {{- else if .IsBlockedByOfficialReviewRequests}}red
{{- else if .IsBlockedByOutdatedBranch}}red {{- else if .IsBlockedByOutdatedBranch}}red
{{- else if .IsBlockedByChangedProtectedFiles}}red {{- else if .IsBlockedByChangedProtectedFiles}}red
{{- else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red {{- else if and .EnableStatusCheck (or $requiredStatusCheckState.IsFailure $requiredStatusCheckState.IsError)}}red
{{- else if and .EnableStatusCheck (or (not $.LatestCommitStatus) .RequiredStatusCheckState.IsPending .RequiredStatusCheckState.IsWarning)}}yellow {{- else if and .EnableStatusCheck (or (not $.LatestCommitStatus) $requiredStatusCheckState.IsPending $requiredStatusCheckState.IsWarning)}}yellow
{{- else if and .AllowMerge .RequireSigned (not .WillSign)}}red {{- else if and .AllowMerge .RequireSigned (not .WillSign)}}red
{{- else if .Issue.PullRequest.IsChecking}}yellow {{- else if .Issue.PullRequest.IsChecking}}yellow
{{- else if .Issue.PullRequest.IsEmpty}}grey {{- else if .Issue.PullRequest.IsEmpty}}grey
@ -32,7 +34,7 @@
"CommitStatus" .LatestCommitStatus "CommitStatus" .LatestCommitStatus
"CommitStatuses" .LatestCommitStatuses "CommitStatuses" .LatestCommitStatuses
"ShowHideChecks" true "ShowHideChecks" true
"StatusCheckData" .StatusCheckData "StatusCheckData" $statusCheckData
)}} )}}
</div> </div>
{{end}} {{end}}
@ -145,12 +147,12 @@
<li>{{.}}</li> <li>{{.}}</li>
{{end}} {{end}}
</ul> </ul>
{{else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsError .RequiredStatusCheckState.IsFailure)}} {{else if and .EnableStatusCheck (or $requiredStatusCheckState.IsError $requiredStatusCheckState.IsFailure)}}
<div class="item"> <div class="item">
{{svg "octicon-x"}} {{svg "octicon-x"}}
{{ctx.Locale.Tr "repo.pulls.required_status_check_failed"}} {{ctx.Locale.Tr "repo.pulls.required_status_check_failed"}}
</div> </div>
{{else if and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess)}} {{else if and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess)}}
<div class="item"> <div class="item">
{{svg "octicon-x"}} {{svg "octicon-x"}}
{{ctx.Locale.Tr "repo.pulls.required_status_check_missing"}} {{ctx.Locale.Tr "repo.pulls.required_status_check_missing"}}
@ -166,7 +168,7 @@
</div> </div>
{{end}} {{end}}
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}} {{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess))}}
{{/* admin can merge without checks, writer can merge when checks succeed */}} {{/* admin can merge without checks, writer can merge when checks succeed */}}
{{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}} {{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
@ -351,7 +353,7 @@
<li>{{.}}</li> <li>{{.}}</li>
{{end}} {{end}}
</ul> </ul>
{{else if and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess)}} {{else if and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess)}}
<div class="item text red"> <div class="item text red">
{{svg "octicon-x"}} {{svg "octicon-x"}}
{{ctx.Locale.Tr "repo.pulls.required_status_check_failed"}} {{ctx.Locale.Tr "repo.pulls.required_status_check_failed"}}

View File

@ -8,19 +8,7 @@
{{if .CommitStatus}} {{if .CommitStatus}}
<div class="commit-status-panel"> <div class="commit-status-panel">
<div class="ui top attached header commit-status-header"> <div class="ui top attached header commit-status-header">
{{if or (eq .CommitStatus.State "pending") (.MissingRequiredChecks)}} {{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}}
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
{{else if eq .CommitStatus.State "success"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_success"}}
{{else if eq .CommitStatus.State "warning"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_warning"}}
{{else if eq .CommitStatus.State "failure"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_failure"}}
{{else if eq .CommitStatus.State "error"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_error"}}
{{else}}
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
{{end}}
{{if .ShowHideChecks}} {{if .ShowHideChecks}}
<div class="ui right"> <div class="ui right">

View File

@ -16,12 +16,7 @@
<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span> <span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
</a> </a>
{{if or .DiffShortStat.TotalAddition .DiffShortStat.TotalDeletion}} {{if or .DiffShortStat.TotalAddition .DiffShortStat.TotalDeletion}}
<span class="tw-ml-auto tw-pl-3 tw-whitespace-nowrap tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2"> {{template "repo/diff/stats" dict "Addition" .DiffShortStat.TotalAddition "Deletion" .DiffShortStat.TotalDeletion "Classes" "tw-ml-auto tw-pl-3 tw-font-semibold"}}
<span><span class="text green">{{if .DiffShortStat.TotalAddition}}+{{.DiffShortStat.TotalAddition}}{{end}}</span> <span class="text red">{{if .DiffShortStat.TotalDeletion}}-{{.DiffShortStat.TotalDeletion}}{{end}}</span></span>
<span class="diff-stats-bar">
<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .DiffShortStat.TotalAddition "/" "(" .DiffShortStat.TotalAddition "+" .DiffShortStat.TotalDeletion "+" 0.0 ")"}}%"></div>
</span>
</span>
{{end}} {{end}}
</div> </div>
<div class="ui tabs divider"></div> <div class="ui tabs divider"></div>

View File

@ -100,6 +100,11 @@
<a class="item" data-clipboard-text="{{.Repository.Link}}/src/commit/{{.CommitID}}/{{PathEscapeSegments .TreePath}}" data-clipboard-text-type="url"> <a class="item" data-clipboard-text="{{.Repository.Link}}/src/commit/{{.CommitID}}/{{PathEscapeSegments .TreePath}}" data-clipboard-text-type="url">
{{svg "octicon-link" 16}}{{ctx.Locale.Tr "repo.file_copy_permalink"}} {{svg "octicon-link" 16}}{{ctx.Locale.Tr "repo.file_copy_permalink"}}
</a> </a>
{{if and (not $.DisableDownloadSourceArchives) $.RefFullName}}
<div class="divider"></div>
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefFullName.ShortName}}.zip?path={{PathEscapeSegments .TreePath}}" rel="nofollow">{{svg "octicon-file-zip"}}{{ctx.Locale.Tr "repo.download_directory_as" "ZIP"}}</a>
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefFullName.ShortName}}.tar.gz?path={{PathEscapeSegments .TreePath}}" rel="nofollow">{{svg "octicon-file-zip"}}{{ctx.Locale.Tr "repo.download_directory_as" "TAR.GZ"}}</a>
{{end}}
{{if and (.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) (not .Repository.IsArchived) (not $isTreePathRoot)}} {{if and (.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) (not .Repository.IsArchived) (not $isTreePathRoot)}}
<div class="divider"></div> <div class="divider"></div>
<a class="item tw-text-danger" href="{{.RepoLink}}/_delete/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}"> <a class="item tw-text-danger" href="{{.RepoLink}}/_delete/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">

View File

@ -6256,6 +6256,16 @@
"name": "archive", "name": "archive",
"in": "path", "in": "path",
"required": true "required": true
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "subpath of the repository to download",
"name": "path",
"in": "query"
} }
], ],
"responses": { "responses": {

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
arch_module "code.gitea.io/gitea/modules/packages/arch" arch_module "code.gitea.io/gitea/modules/packages/arch"
"code.gitea.io/gitea/modules/test"
arch_service "code.gitea.io/gitea/services/packages/arch" arch_service "code.gitea.io/gitea/services/packages/arch"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -78,34 +79,6 @@ license = MIT`)
return buf.Bytes() return buf.Bytes()
} }
readIndexContent := func(r io.Reader) (map[string]string, error) {
gzr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
content := make(map[string]string)
tr := tar.NewReader(gzr)
for {
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
buf, err := io.ReadAll(tr)
if err != nil {
return nil, err
}
content[hd.Name] = string(buf)
}
return content, nil
}
compressions := []string{"gz", "xz", "zst"} compressions := []string{"gz", "xz", "zst"}
repositories := []string{"main", "testing", "with/slash", ""} repositories := []string{"main", "testing", "with/slash", ""}
@ -204,7 +177,7 @@ license = MIT`)
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
content, err := readIndexContent(resp.Body) content, err := test.ReadAllTarGzContent(resp.Body)
assert.NoError(t, err) assert.NoError(t, err)
desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)] desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
@ -256,7 +229,7 @@ license = MIT`)
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename))
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
content, err := readIndexContent(resp.Body) content, err := test.ReadAllTarGzContent(resp.Body)
assert.NoError(t, err) assert.NoError(t, err)
desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)] desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)]
@ -311,7 +284,7 @@ license = MIT`)
req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
content, err := readIndexContent(resp.Body) content, err := test.ReadAllTarGzContent(resp.Body)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, content, 2) assert.Len(t, content, 2)
@ -326,7 +299,7 @@ license = MIT`)
req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
resp = MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
content, err = readIndexContent(resp.Body) content, err = test.ReadAllTarGzContent(resp.Body)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, content, 2) assert.Len(t, content, 2)
_, has = content["gitea-test-1.0.0/desc"] _, has = content["gitea-test-1.0.0/desc"]

View File

@ -317,6 +317,7 @@ func TestAPILFSBatch(t *testing.T) {
ul := br.Objects[0].Actions["upload"] ul := br.Objects[0].Actions["upload"]
assert.NotNil(t, ul) assert.NotNil(t, ul)
assert.NotEmpty(t, ul.Href) assert.NotEmpty(t, ul.Href)
assert.Equal(t, "chunked", ul.Header["Transfer-Encoding"], "git-lfs client needs Transfer-Encoding to do chunked transfer")
assert.Contains(t, br.Objects[0].Actions, "verify") assert.Contains(t, br.Objects[0].Actions, "verify")
vl := br.Objects[0].Actions["verify"] vl := br.Objects[0].Actions["verify"]
assert.NotNil(t, vl) assert.NotNil(t, vl)

View File

@ -34,6 +34,14 @@ func testGeneratePngBytes() []byte {
} }
func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, filename string, content []byte, expectedStatus int) string { func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, filename string, content []byte, expectedStatus int) string {
return testCreateAttachment(t, session, repoURL, "issues", filename, content, expectedStatus)
}
func testCreateReleaseAttachment(t *testing.T, session *TestSession, repoURL, filename string, content []byte, expectedStatus int) string {
return testCreateAttachment(t, session, repoURL, "releases", filename, content, expectedStatus)
}
func testCreateAttachment(t *testing.T, session *TestSession, repoURL, issueOrRelease, filename string, content []byte, expectedStatus int) string {
body := &bytes.Buffer{} body := &bytes.Buffer{}
// Setup multi-part // Setup multi-part
@ -45,7 +53,7 @@ func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, file
err = writer.Close() err = writer.Close()
assert.NoError(t, err) assert.NoError(t, err)
req := NewRequestWithBody(t, "POST", repoURL+"/issues/attachments", body) req := NewRequestWithBody(t, "POST", repoURL+"/"+issueOrRelease+"/attachments", body)
req.Header.Add("Content-Type", writer.FormDataContentType()) req.Header.Add("Content-Type", writer.FormDataContentType())
resp := session.MakeRequest(t, req, expectedStatus) resp := session.MakeRequest(t, req, expectedStatus)
@ -57,12 +65,23 @@ func testCreateIssueAttachment(t *testing.T, session *TestSession, repoURL, file
return obj["uuid"] return obj["uuid"]
} }
func testDeleteIssueAttachment(t *testing.T, session *TestSession, repoURL, uuid string, expectedStatus int) {
req := NewRequestWithValues(t, "POST", repoURL+"/issues/attachments/remove", map[string]string{"file": uuid})
session.MakeRequest(t, req, expectedStatus)
}
func testDeleteReleaseAttachment(t *testing.T, session *TestSession, repoURL, uuid string, expectedStatus int) {
req := NewRequestWithValues(t, "POST", repoURL+"/releases/attachments/remove", map[string]string{"file": uuid})
session.MakeRequest(t, req, expectedStatus)
}
func TestAttachments(t *testing.T) { func TestAttachments(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
t.Run("CreateAnonymousAttachment", testCreateAnonymousAttachment) t.Run("CreateAnonymousAttachment", testCreateAnonymousAttachment)
t.Run("CreateUser2IssueAttachment", testCreateUser2IssueAttachment) t.Run("CreateUser2IssueAttachment", testCreateUser2IssueAttachment)
t.Run("UploadAttachmentDeleteTemp", testUploadAttachmentDeleteTemp) t.Run("UploadAttachmentDeleteTemp", testUploadAttachmentDeleteTemp)
t.Run("GetAttachment", testGetAttachment) t.Run("GetAttachment", testGetAttachment)
t.Run("DeleteAttachmentPermissions", testDeleteAttachmentPermissions)
} }
func testUploadAttachmentDeleteTemp(t *testing.T) { func testUploadAttachmentDeleteTemp(t *testing.T) {
@ -157,3 +176,28 @@ func testGetAttachment(t *testing.T) {
}) })
} }
} }
func testDeleteAttachmentPermissions(t *testing.T) {
const repoURL = "user2/repo1"
ownerSession := loginUser(t, "user2")
readonlySession := loginUser(t, "user5")
issueFromOwner := testCreateIssueAttachment(t, ownerSession, repoURL, "owner-issue.png", testGeneratePngBytes(), http.StatusOK)
testDeleteIssueAttachment(t, readonlySession, repoURL, issueFromOwner, http.StatusForbidden)
issueFromReader := testCreateIssueAttachment(t, readonlySession, repoURL, "reader-issue.png", testGeneratePngBytes(), http.StatusOK)
testDeleteIssueAttachment(t, ownerSession, repoURL, issueFromReader, http.StatusOK)
testCreateReleaseAttachment(t, readonlySession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusNotFound)
crossRepoUUID := testCreateIssueAttachment(t, ownerSession, repoURL, "cross-repo.png", testGeneratePngBytes(), http.StatusOK)
testDeleteIssueAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
testDeleteIssueAttachment(t, ownerSession, repoURL, crossRepoUUID, http.StatusOK)
releaseUUID := testCreateReleaseAttachment(t, ownerSession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusOK)
testDeleteReleaseAttachment(t, ownerSession, repoURL, releaseUUID, http.StatusOK)
// test deleting release attachment from another repo
testDeleteReleaseAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest)
}

View File

@ -701,6 +701,11 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
collaboratorCtx := NewAPITestContext(t, "user5", baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
readOnlyCtx := NewAPITestContext(t, "user4", baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
t.Run("AddAutoMergeCollaborator", doAPIAddCollaborator(*baseCtx, collaboratorCtx.Username, perm.AccessModeWrite))
t.Run("AddReadOnlyAutoMergeCollaborator", doAPIAddCollaborator(*baseCtx, readOnlyCtx.Username, perm.AccessModeRead))
// automerge will merge immediately if the PR is mergeable and there is no "status check" because no status check also means "all checks passed" // automerge will merge immediately if the PR is mergeable and there is no "status check" because no status check also means "all checks passed"
// so we must set up a status check to test the auto merge feature // so we must set up a status check to test the auto merge feature
@ -747,10 +752,32 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
ctx.ExpectedCode = http.StatusConflict ctx.ExpectedCode = http.StatusConflict
t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
// Read-only collaborator still cannot cancel
readOnlyCtx.ExpectedCode = http.StatusForbidden
t.Run("CancelAutoMergePRByReadOnlyCollaboratorForbidden", doAPICancelAutoMergePullRequest(readOnlyCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
readOnlyCtx.ExpectedCode = 0
// Collaborators with merge permissions can cancel a schedule made by someone else
collaboratorCtx.ExpectedCode = http.StatusNoContent
t.Run("CancelAutoMergePRByCollaborator", doAPICancelAutoMergePullRequest(collaboratorCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
collaboratorCtx.ExpectedCode = 0
// Re-add auto merge request so the repo owner can cancel it as well
ctx.ExpectedCode = http.StatusCreated
t.Run("AutoMergePRAfterCollaboratorCancel", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
// Cancel auto merge request // Cancel auto merge request
ctx.ExpectedCode = http.StatusNoContent ctx.ExpectedCode = http.StatusNoContent
t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
// Collaborator can schedule but admins should still be able to cancel their schedule
collaboratorCtx.ExpectedCode = http.StatusCreated
t.Run("AutoMergePRByCollaborator", doAPIAutoMergePullRequest(collaboratorCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
collaboratorCtx.ExpectedCode = 0
ctx.ExpectedCode = http.StatusNoContent
t.Run("CancelAutoMergePRByAdmin", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
// Add auto merge request // Add auto merge request
ctx.ExpectedCode = http.StatusCreated ctx.ExpectedCode = http.StatusCreated
t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestRepoDownloadArchive(t *testing.T) { func TestRepoDownloadArchive(t *testing.T) {
@ -23,11 +24,35 @@ func TestRepoDownloadArchive(t *testing.T) {
defer test.MockVariableValue(&web.GzipMinSize, 10)() defer test.MockVariableValue(&web.GzipMinSize, 10)()
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
req := NewRequest(t, "GET", "/user2/repo1/archive/master.zip") t.Run("NoDuplicateCompression", func(t *testing.T) {
req.Header.Set("Accept-Encoding", "gzip") req := NewRequest(t, "GET", "/user2/repo1/archive/master.zip")
resp := MakeRequest(t, req, http.StatusOK) req.Header.Set("Accept-Encoding", "gzip")
bs, err := io.ReadAll(resp.Body) resp := MakeRequest(t, req, http.StatusOK)
assert.NoError(t, err) bs, err := io.ReadAll(resp.Body)
assert.Empty(t, resp.Header().Get("Content-Encoding")) assert.NoError(t, err)
assert.Len(t, bs, 320) assert.Empty(t, resp.Header().Get("Content-Encoding"))
assert.Len(t, bs, 320)
})
t.Run("SubPath", func(t *testing.T) {
// When using "archiving and caching" approach, archiving with paths will always use streaming and never be cached
defer test.MockVariableValue(&setting.Repository.StreamArchives, false) // this can be removed if there is always streaming mode
req := NewRequest(t, "GET", "/user2/glob/archive/master.tar.gz?path=aaa.doc&path=x/y")
resp := MakeRequest(t, req, http.StatusOK)
content, err := test.ReadAllTarGzContent(resp.Body)
require.NoError(t, err)
assert.Empty(t, content["glob/a.txt"])
assert.NotEmpty(t, content["glob/aaa.doc"])
assert.Empty(t, content["glob/x/b.txt"])
assert.NotEmpty(t, content["glob/x/y/a.txt"])
req = NewRequest(t, "GET", "/user2/glob/archive/master.tar.gz")
resp = MakeRequest(t, req, http.StatusOK)
content, err = test.ReadAllTarGzContent(resp.Body)
require.NoError(t, err)
assert.NotEmpty(t, content["glob/a.txt"])
assert.NotEmpty(t, content["glob/aaa.doc"])
assert.NotEmpty(t, content["glob/x/b.txt"])
assert.NotEmpty(t, content["glob/x/y/a.txt"])
})
} }

View File

@ -44,6 +44,7 @@
"stripInternal": true, "stripInternal": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"types": [ "types": [
"webpack/module",
"vitest/globals", "vitest/globals",
"./web_src/js/globals.d.ts", "./web_src/js/globals.d.ts",
"./types.d.ts", "./types.d.ts",

5
types.d.ts vendored
View File

@ -17,8 +17,3 @@ declare module 'eslint-plugin-github' {
const plugin: Eslint.Plugin; const plugin: Eslint.Plugin;
export = plugin; export = plugin;
} }
declare module '@eslint-community/eslint-plugin-eslint-comments' {
import type {Eslint} from 'eslint';
const plugin: Eslint.Plugin;
export = plugin;
}

333
uv.lock generated
View File

@ -4,14 +4,14 @@ requires-python = ">=3.10"
[[package]] [[package]]
name = "click" name = "click"
version = "8.3.0" version = "8.3.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" }, { name = "colorama", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
] ]
[[package]] [[package]]
@ -100,7 +100,7 @@ dev = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "djlint", specifier = "==1.36.4" }, { name = "djlint", specifier = "==1.36.4" },
{ name = "yamllint", specifier = "==1.37.1" }, { name = "yamllint", specifier = "==1.38.0" },
] ]
[[package]] [[package]]
@ -118,20 +118,20 @@ wheels = [
[[package]] [[package]]
name = "json5" name = "json5"
version = "0.12.1" version = "0.13.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" } sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" }, { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" },
] ]
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.12.1" version = "1.0.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" },
] ]
[[package]] [[package]]
@ -200,109 +200,123 @@ wheels = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "2025.10.23" version = "2026.1.15"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/c8/1d2160d36b11fbe0a61acb7c3c81ab032d9ec8ad888ac9e0a61b85ab99dd/regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26", size = 401266, upload-time = "2025-10-21T15:58:20.23Z" } sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/11/849d5d23633a77047465eaae4cc0cbf24ded7aa496c02e8b9710e28b1687/regex-2025.10.23-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:17bbcde374bef1c5fad9b131f0e28a6a24856dd90368d8c0201e2b5a69533daa", size = 487957, upload-time = "2025-10-21T15:54:26.151Z" }, { url = "https://files.pythonhosted.org/packages/ea/d2/e6ee96b7dff201a83f650241c52db8e5bd080967cb93211f57aa448dc9d6/regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e", size = 488166, upload-time = "2026-01-14T23:13:46.408Z" },
{ url = "https://files.pythonhosted.org/packages/87/12/5985386e7e3200a0d6a6417026d2c758d783a932428a5efc0a42ca1ddf74/regex-2025.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4e10434279cc8567f99ca6e018e9025d14f2fded2a603380b6be2090f476426", size = 290419, upload-time = "2025-10-21T15:54:28.804Z" }, { url = "https://files.pythonhosted.org/packages/23/8a/819e9ce14c9f87af026d0690901b3931f3101160833e5d4c8061fa3a1b67/regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f", size = 290632, upload-time = "2026-01-14T23:13:48.688Z" },
{ url = "https://files.pythonhosted.org/packages/67/cf/a8615923f962f8fdc41a3a6093a48726955e8b1993f4614b26a41d249f9b/regex-2025.10.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c9bb421cbe7012c744a5a56cf4d6c80829c72edb1a2991677299c988d6339c8", size = 288285, upload-time = "2025-10-21T15:54:30.47Z" }, { url = "https://files.pythonhosted.org/packages/d5/c3/23dfe15af25d1d45b07dfd4caa6003ad710dcdcb4c4b279909bdfe7a2de8/regex-2026.1.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bda75ebcac38d884240914c6c43d8ab5fb82e74cde6da94b43b17c411aa4c2b", size = 288500, upload-time = "2026-01-14T23:13:50.503Z" },
{ url = "https://files.pythonhosted.org/packages/4e/3d/6a3a1e12c86354cd0b3cbf8c3dd6acbe853609ee3b39d47ecd3ce95caf84/regex-2025.10.23-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:275cd1c2ed8c4a78ebfa489618d7aee762e8b4732da73573c3e38236ec5f65de", size = 781458, upload-time = "2025-10-21T15:54:31.978Z" }, { url = "https://files.pythonhosted.org/packages/c6/31/1adc33e2f717df30d2f4d973f8776d2ba6ecf939301efab29fca57505c95/regex-2026.1.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dcc02368585334f5bc81fc73a2a6a0bbade60e7d83da21cead622faf408f32c", size = 781670, upload-time = "2026-01-14T23:13:52.453Z" },
{ url = "https://files.pythonhosted.org/packages/46/47/76a8da004489f2700361754859e373b87a53d043de8c47f4d1583fd39d78/regex-2025.10.23-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b426ae7952f3dc1e73a86056d520bd4e5f021397484a6835902fc5648bcacce", size = 850605, upload-time = "2025-10-21T15:54:33.753Z" }, { url = "https://files.pythonhosted.org/packages/23/ce/21a8a22d13bc4adcb927c27b840c948f15fc973e21ed2346c1bd0eae22dc/regex-2026.1.15-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:693b465171707bbe882a7a05de5e866f33c76aa449750bee94a8d90463533cc9", size = 850820, upload-time = "2026-01-14T23:13:54.894Z" },
{ url = "https://files.pythonhosted.org/packages/67/05/fa886461f97d45a6f4b209699cb994dc6d6212d6e219d29444dac5005775/regex-2025.10.23-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5cdaf5b6d37c7da1967dbe729d819461aab6a98a072feef65bbcff0a6e60649", size = 898563, upload-time = "2025-10-21T15:54:35.431Z" }, { url = "https://files.pythonhosted.org/packages/6c/4f/3eeacdf587a4705a44484cd0b30e9230a0e602811fb3e2cc32268c70d509/regex-2026.1.15-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0d190e6f013ea938623a58706d1469a62103fb2a241ce2873a9906e0386582c", size = 898777, upload-time = "2026-01-14T23:13:56.908Z" },
{ url = "https://files.pythonhosted.org/packages/2d/db/3ddd8d01455f23cabad7499f4199de0df92f5e96d39633203ff9d0b592dc/regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bfeff0b08f296ab28b4332a7e03ca31c437ee78b541ebc874bbf540e5932f8d", size = 791535, upload-time = "2025-10-21T15:54:37.269Z" }, { url = "https://files.pythonhosted.org/packages/79/a9/1898a077e2965c35fc22796488141a22676eed2d73701e37c73ad7c0b459/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ff818702440a5878a81886f127b80127f5d50563753a28211482867f8318106", size = 791750, upload-time = "2026-01-14T23:13:58.527Z" },
{ url = "https://files.pythonhosted.org/packages/7c/ae/0fa5cbf41ca92b6ec3370222fcb6c68b240d68ab10e803d086c03a19fd9e/regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f97236a67307b775f30a74ef722b64b38b7ab7ba3bb4a2508518a5de545459c", size = 782461, upload-time = "2025-10-21T15:54:39.187Z" }, { url = "https://files.pythonhosted.org/packages/4c/84/e31f9d149a178889b3817212827f5e0e8c827a049ff31b4b381e76b26e2d/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f052d1be37ef35a54e394de66136e30fa1191fab64f71fc06ac7bc98c9a84618", size = 782674, upload-time = "2026-01-14T23:13:59.874Z" },
{ url = "https://files.pythonhosted.org/packages/d4/23/70af22a016df11af4def27870eb175c2c7235b72d411ecf75a4b4a422cb6/regex-2025.10.23-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:be19e7de499940cd72475fb8e46ab2ecb1cf5906bebdd18a89f9329afb1df82f", size = 774583, upload-time = "2025-10-21T15:54:41.018Z" }, { url = "https://files.pythonhosted.org/packages/d2/ff/adf60063db24532add6a1676943754a5654dcac8237af024ede38244fd12/regex-2026.1.15-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6bfc31a37fd1592f0c4fc4bfc674b5c42e52efe45b4b7a6a14f334cca4bcebe4", size = 767906, upload-time = "2026-01-14T23:14:01.298Z" },
{ url = "https://files.pythonhosted.org/packages/7a/ee/a54a6851f6905f33d3c4ed64e8737b1d85ed01b5724712530ddc0f9abdb1/regex-2025.10.23-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:883df76ee42d9ecb82b37ff8d01caea5895b3f49630a64d21111078bbf8ef64c", size = 845649, upload-time = "2025-10-21T15:54:42.615Z" }, { url = "https://files.pythonhosted.org/packages/af/3e/e6a216cee1e2780fec11afe7fc47b6f3925d7264e8149c607ac389fd9b1a/regex-2026.1.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d6ce5ae80066b319ae3bc62fd55a557c9491baa5efd0d355f0de08c4ba54e79", size = 774798, upload-time = "2026-01-14T23:14:02.715Z" },
{ url = "https://files.pythonhosted.org/packages/80/7d/c3ec1cae14e01fab00e38c41ed35f47a853359e95e9c023e9a4381bb122c/regex-2025.10.23-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2e9117d1d35fc2addae6281019ecc70dc21c30014b0004f657558b91c6a8f1a7", size = 836037, upload-time = "2025-10-21T15:54:44.63Z" }, { url = "https://files.pythonhosted.org/packages/0f/98/23a4a8378a9208514ed3efc7e7850c27fa01e00ed8557c958df0335edc4a/regex-2026.1.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1704d204bd42b6bb80167df0e4554f35c255b579ba99616def38f69e14a5ccb9", size = 845861, upload-time = "2026-01-14T23:14:04.824Z" },
{ url = "https://files.pythonhosted.org/packages/15/ae/45771140dd43c4d67c87b54d3728078ed6a96599d9fc7ba6825086236782/regex-2025.10.23-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ff1307f531a5d8cf5c20ea517254551ff0a8dc722193aab66c656c5a900ea68", size = 779705, upload-time = "2025-10-21T15:54:46.08Z" }, { url = "https://files.pythonhosted.org/packages/f8/57/d7605a9d53bd07421a8785d349cd29677fe660e13674fa4c6cbd624ae354/regex-2026.1.15-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3174a5ed4171570dc8318afada56373aa9289eb6dc0d96cceb48e7358b0e220", size = 755648, upload-time = "2026-01-14T23:14:06.371Z" },
{ url = "https://files.pythonhosted.org/packages/b8/95/074e2581760eafce7c816a352b7d3a322536e5b68c346d1a8bacd895545c/regex-2025.10.23-cp310-cp310-win32.whl", hash = "sha256:7888475787cbfee4a7cd32998eeffe9a28129fa44ae0f691b96cb3939183ef41", size = 265663, upload-time = "2025-10-21T15:54:47.854Z" }, { url = "https://files.pythonhosted.org/packages/6f/76/6f2e24aa192da1e299cc1101674a60579d3912391867ce0b946ba83e2194/regex-2026.1.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87adf5bd6d72e3e17c9cb59ac4096b1faaf84b7eb3037a5ffa61c4b4370f0f13", size = 836250, upload-time = "2026-01-14T23:14:08.343Z" },
{ url = "https://files.pythonhosted.org/packages/f7/c7/a25f56a718847e34d3f1608c72eadeb67653bff1a0411da023dd8f4c647b/regex-2025.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ec41a905908496ce4906dab20fb103c814558db1d69afc12c2f384549c17936a", size = 277587, upload-time = "2025-10-21T15:54:49.571Z" }, { url = "https://files.pythonhosted.org/packages/11/3a/1f2a1d29453299a7858eab7759045fc3d9d1b429b088dec2dc85b6fa16a2/regex-2026.1.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e85dc94595f4d766bd7d872a9de5ede1ca8d3063f3bdf1e2c725f5eb411159e3", size = 779919, upload-time = "2026-01-14T23:14:09.954Z" },
{ url = "https://files.pythonhosted.org/packages/d3/e5/63eb17c6b5deaefd93c2bbb1feae7c0a8d2157da25883a6ca2569cf7a663/regex-2025.10.23-cp310-cp310-win_arm64.whl", hash = "sha256:b2b7f19a764d5e966d5a62bf2c28a8b4093cc864c6734510bdb4aeb840aec5e6", size = 269979, upload-time = "2025-10-21T15:54:51.375Z" }, { url = "https://files.pythonhosted.org/packages/c0/67/eab9bc955c9dcc58e9b222c801e39cff7ca0b04261792a2149166ce7e792/regex-2026.1.15-cp310-cp310-win32.whl", hash = "sha256:21ca32c28c30d5d65fc9886ff576fc9b59bbca08933e844fa2363e530f4c8218", size = 265888, upload-time = "2026-01-14T23:14:11.35Z" },
{ url = "https://files.pythonhosted.org/packages/82/e5/74b7cd5cd76b4171f9793042045bb1726f7856dd56e582fc3e058a7a8a5e/regex-2025.10.23-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c531155bf9179345e85032052a1e5fe1a696a6abf9cea54b97e8baefff970fd", size = 487960, upload-time = "2025-10-21T15:54:53.253Z" }, { url = "https://files.pythonhosted.org/packages/1d/62/31d16ae24e1f8803bddb0885508acecaec997fcdcde9c243787103119ae4/regex-2026.1.15-cp310-cp310-win_amd64.whl", hash = "sha256:3038a62fc7d6e5547b8915a3d927a0fbeef84cdbe0b1deb8c99bbd4a8961b52a", size = 277830, upload-time = "2026-01-14T23:14:12.908Z" },
{ url = "https://files.pythonhosted.org/packages/b9/08/854fa4b3b20471d1df1c71e831b6a1aa480281e37791e52a2df9641ec5c6/regex-2025.10.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:912e9df4e89d383681268d38ad8f5780d7cccd94ba0e9aa09ca7ab7ab4f8e7eb", size = 290425, upload-time = "2025-10-21T15:54:55.21Z" }, { url = "https://files.pythonhosted.org/packages/e5/36/5d9972bccd6417ecd5a8be319cebfd80b296875e7f116c37fb2a2deecebf/regex-2026.1.15-cp310-cp310-win_arm64.whl", hash = "sha256:505831646c945e3e63552cc1b1b9b514f0e93232972a2d5bedbcc32f15bc82e3", size = 270376, upload-time = "2026-01-14T23:14:14.782Z" },
{ url = "https://files.pythonhosted.org/packages/ab/d3/6272b1dd3ca1271661e168762b234ad3e00dbdf4ef0c7b9b72d2d159efa7/regex-2025.10.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f375c61bfc3138b13e762fe0ae76e3bdca92497816936534a0177201666f44f", size = 288278, upload-time = "2025-10-21T15:54:56.862Z" }, { url = "https://files.pythonhosted.org/packages/d0/c9/0c80c96eab96948363d270143138d671d5731c3a692b417629bf3492a9d6/regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a", size = 488168, upload-time = "2026-01-14T23:14:16.129Z" },
{ url = "https://files.pythonhosted.org/packages/14/8f/c7b365dd9d9bc0a36e018cb96f2ffb60d2ba8deb589a712b437f67de2920/regex-2025.10.23-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e248cc9446081119128ed002a3801f8031e0c219b5d3c64d3cc627da29ac0a33", size = 793289, upload-time = "2025-10-21T15:54:58.352Z" }, { url = "https://files.pythonhosted.org/packages/17/f0/271c92f5389a552494c429e5cc38d76d1322eb142fb5db3c8ccc47751468/regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f", size = 290636, upload-time = "2026-01-14T23:14:17.715Z" },
{ url = "https://files.pythonhosted.org/packages/d4/fb/b8fbe9aa16cf0c21f45ec5a6c74b4cecbf1a1c0deb7089d4a6f83a9c1caa/regex-2025.10.23-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b52bf9282fdf401e4f4e721f0f61fc4b159b1307244517789702407dd74e38ca", size = 860321, upload-time = "2025-10-21T15:54:59.813Z" }, { url = "https://files.pythonhosted.org/packages/a0/f9/5f1fd077d106ca5655a0f9ff8f25a1ab55b92128b5713a91ed7134ff688e/regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1", size = 288496, upload-time = "2026-01-14T23:14:19.326Z" },
{ url = "https://files.pythonhosted.org/packages/b0/81/bf41405c772324926a9bd8a640dedaa42da0e929241834dfce0733070437/regex-2025.10.23-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c084889ab2c59765a0d5ac602fd1c3c244f9b3fcc9a65fdc7ba6b74c5287490", size = 907011, upload-time = "2025-10-21T15:55:01.968Z" }, { url = "https://files.pythonhosted.org/packages/b5/e1/8f43b03a4968c748858ec77f746c286d81f896c2e437ccf050ebc5d3128c/regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b", size = 793503, upload-time = "2026-01-14T23:14:20.922Z" },
{ url = "https://files.pythonhosted.org/packages/a4/fb/5ad6a8b92d3f88f3797b51bb4ef47499acc2d0b53d2fbe4487a892f37a73/regex-2025.10.23-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80e8eb79009bdb0936658c44ca06e2fbbca67792013e3818eea3f5f228971c2", size = 800312, upload-time = "2025-10-21T15:55:04.15Z" }, { url = "https://files.pythonhosted.org/packages/8d/4e/a39a5e8edc5377a46a7c875c2f9a626ed3338cb3bb06931be461c3e1a34a/regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8", size = 860535, upload-time = "2026-01-14T23:14:22.405Z" },
{ url = "https://files.pythonhosted.org/packages/42/48/b4efba0168a2b57f944205d823f8e8a3a1ae6211a34508f014ec2c712f4f/regex-2025.10.23-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6f259118ba87b814a8ec475380aee5f5ae97a75852a3507cf31d055b01b5b40", size = 782839, upload-time = "2025-10-21T15:55:05.641Z" }, { url = "https://files.pythonhosted.org/packages/dc/1c/9dce667a32a9477f7a2869c1c767dc00727284a9fa3ff5c09a5c6c03575e/regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413", size = 907225, upload-time = "2026-01-14T23:14:23.897Z" },
{ url = "https://files.pythonhosted.org/packages/13/2a/c9efb4c6c535b0559c1fa8e431e0574d229707c9ca718600366fcfef6801/regex-2025.10.23-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b8c72a242683dcc72d37595c4f1278dfd7642b769e46700a8df11eab19dfd82", size = 854270, upload-time = "2025-10-21T15:55:07.27Z" }, { url = "https://files.pythonhosted.org/packages/a4/3c/87ca0a02736d16b6262921425e84b48984e77d8e4e572c9072ce96e66c30/regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026", size = 800526, upload-time = "2026-01-14T23:14:26.039Z" },
{ url = "https://files.pythonhosted.org/packages/34/2d/68eecc1bdaee020e8ba549502291c9450d90d8590d0552247c9b543ebf7b/regex-2025.10.23-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d7b7a0a3df9952f9965342159e0c1f05384c0f056a47ce8b61034f8cecbe83", size = 845771, upload-time = "2025-10-21T15:55:09.477Z" }, { url = "https://files.pythonhosted.org/packages/4b/ff/647d5715aeea7c87bdcbd2f578f47b415f55c24e361e639fe8c0cc88878f/regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785", size = 773446, upload-time = "2026-01-14T23:14:28.109Z" },
{ url = "https://files.pythonhosted.org/packages/a5/cd/a1ae499cf9b87afb47a67316bbf1037a7c681ffe447c510ed98c0aa2c01c/regex-2025.10.23-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:413bfea20a484c524858125e92b9ce6ffdd0a4b97d4ff96b5859aa119b0f1bdd", size = 788778, upload-time = "2025-10-21T15:55:11.396Z" }, { url = "https://files.pythonhosted.org/packages/af/89/bf22cac25cb4ba0fe6bff52ebedbb65b77a179052a9d6037136ae93f42f4/regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e", size = 783051, upload-time = "2026-01-14T23:14:29.929Z" },
{ url = "https://files.pythonhosted.org/packages/38/f9/70765e63f5ea7d43b2b6cd4ee9d3323f16267e530fb2a420d92d991cf0fc/regex-2025.10.23-cp311-cp311-win32.whl", hash = "sha256:f76deef1f1019a17dad98f408b8f7afc4bd007cbe835ae77b737e8c7f19ae575", size = 265666, upload-time = "2025-10-21T15:55:13.306Z" }, { url = "https://files.pythonhosted.org/packages/1e/f4/6ed03e71dca6348a5188363a34f5e26ffd5db1404780288ff0d79513bce4/regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763", size = 854485, upload-time = "2026-01-14T23:14:31.366Z" },
{ url = "https://files.pythonhosted.org/packages/9c/1a/18e9476ee1b63aaec3844d8e1cb21842dc19272c7e86d879bfc0dcc60db3/regex-2025.10.23-cp311-cp311-win_amd64.whl", hash = "sha256:59bba9f7125536f23fdab5deeea08da0c287a64c1d3acc1c7e99515809824de8", size = 277600, upload-time = "2025-10-21T15:55:15.087Z" }, { url = "https://files.pythonhosted.org/packages/d9/9a/8e8560bd78caded8eb137e3e47612430a05b9a772caf60876435192d670a/regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb", size = 762195, upload-time = "2026-01-14T23:14:32.802Z" },
{ url = "https://files.pythonhosted.org/packages/1d/1b/c019167b1f7a8ec77251457e3ff0339ed74ca8bce1ea13138dc98309c923/regex-2025.10.23-cp311-cp311-win_arm64.whl", hash = "sha256:b103a752b6f1632ca420225718d6ed83f6a6ced3016dd0a4ab9a6825312de566", size = 269974, upload-time = "2025-10-21T15:55:16.841Z" }, { url = "https://files.pythonhosted.org/packages/38/6b/61fc710f9aa8dfcd764fe27d37edfaa023b1a23305a0d84fccd5adb346ea/regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2", size = 845986, upload-time = "2026-01-14T23:14:34.898Z" },
{ url = "https://files.pythonhosted.org/packages/f6/57/eeb274d83ab189d02d778851b1ac478477522a92b52edfa6e2ae9ff84679/regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc", size = 489187, upload-time = "2025-10-21T15:55:18.322Z" }, { url = "https://files.pythonhosted.org/packages/fd/2e/fbee4cb93f9d686901a7ca8d94285b80405e8c34fe4107f63ffcbfb56379/regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1", size = 788992, upload-time = "2026-01-14T23:14:37.116Z" },
{ url = "https://files.pythonhosted.org/packages/55/5c/7dad43a9b6ea88bf77e0b8b7729a4c36978e1043165034212fd2702880c6/regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2", size = 291122, upload-time = "2025-10-21T15:55:20.2Z" }, { url = "https://files.pythonhosted.org/packages/ed/14/3076348f3f586de64b1ab75a3fbabdaab7684af7f308ad43be7ef1849e55/regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569", size = 265893, upload-time = "2026-01-14T23:14:38.426Z" },
{ url = "https://files.pythonhosted.org/packages/66/21/38b71e6f2818f0f4b281c8fba8d9d57cfca7b032a648fa59696e0a54376a/regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45", size = 288797, upload-time = "2025-10-21T15:55:21.932Z" }, { url = "https://files.pythonhosted.org/packages/0f/19/772cf8b5fc803f5c89ba85d8b1870a1ca580dc482aa030383a9289c82e44/regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7", size = 277840, upload-time = "2026-01-14T23:14:39.785Z" },
{ url = "https://files.pythonhosted.org/packages/be/95/888f069c89e7729732a6d7cca37f76b44bfb53a1e35dda8a2c7b65c1b992/regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e", size = 798442, upload-time = "2025-10-21T15:55:23.747Z" }, { url = "https://files.pythonhosted.org/packages/78/84/d05f61142709474da3c0853222d91086d3e1372bcdab516c6fd8d80f3297/regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec", size = 270374, upload-time = "2026-01-14T23:14:41.592Z" },
{ url = "https://files.pythonhosted.org/packages/76/70/4f903c608faf786627a8ee17c06e0067b5acade473678b69c8094b248705/regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43", size = 864039, upload-time = "2025-10-21T15:55:25.656Z" }, { url = "https://files.pythonhosted.org/packages/92/81/10d8cf43c807d0326efe874c1b79f22bfb0fb226027b0b19ebc26d301408/regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1", size = 489398, upload-time = "2026-01-14T23:14:43.741Z" },
{ url = "https://files.pythonhosted.org/packages/62/19/2df67b526bf25756c7f447dde554fc10a220fd839cc642f50857d01e4a7b/regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca", size = 912057, upload-time = "2025-10-21T15:55:27.309Z" }, { url = "https://files.pythonhosted.org/packages/90/b0/7c2a74e74ef2a7c32de724658a69a862880e3e4155cba992ba04d1c70400/regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681", size = 291339, upload-time = "2026-01-14T23:14:45.183Z" },
{ url = "https://files.pythonhosted.org/packages/99/14/9a39b7c9e007968411bc3c843cc14cf15437510c0a9991f080cab654fd16/regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc", size = 803374, upload-time = "2025-10-21T15:55:28.9Z" }, { url = "https://files.pythonhosted.org/packages/19/4d/16d0773d0c818417f4cc20aa0da90064b966d22cd62a8c46765b5bd2d643/regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f", size = 289003, upload-time = "2026-01-14T23:14:47.25Z" },
{ url = "https://files.pythonhosted.org/packages/d4/f7/3495151dd3ca79949599b6d069b72a61a2c5e24fc441dccc79dcaf708fe6/regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a", size = 787714, upload-time = "2025-10-21T15:55:30.628Z" }, { url = "https://files.pythonhosted.org/packages/c6/e4/1fc4599450c9f0863d9406e944592d968b8d6dfd0d552a7d569e43bceada/regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa", size = 798656, upload-time = "2026-01-14T23:14:48.77Z" },
{ url = "https://files.pythonhosted.org/packages/28/65/ee882455e051131869957ee8597faea45188c9a98c0dad724cfb302d4580/regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32", size = 858392, upload-time = "2025-10-21T15:55:32.322Z" }, { url = "https://files.pythonhosted.org/packages/b2/e6/59650d73a73fa8a60b3a590545bfcf1172b4384a7df2e7fe7b9aab4e2da9/regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804", size = 864252, upload-time = "2026-01-14T23:14:50.528Z" },
{ url = "https://files.pythonhosted.org/packages/53/25/9287fef5be97529ebd3ac79d256159cb709a07eb58d4be780d1ca3885da8/regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288", size = 850484, upload-time = "2025-10-21T15:55:34.037Z" }, { url = "https://files.pythonhosted.org/packages/6e/ab/1d0f4d50a1638849a97d731364c9a80fa304fec46325e48330c170ee8e80/regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c", size = 912268, upload-time = "2026-01-14T23:14:52.952Z" },
{ url = "https://files.pythonhosted.org/packages/f3/b4/b49b88b4fea2f14dc73e5b5842755e782fc2e52f74423d6f4adc130d5880/regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147", size = 789634, upload-time = "2025-10-21T15:55:35.958Z" }, { url = "https://files.pythonhosted.org/packages/dd/df/0d722c030c82faa1d331d1921ee268a4e8fb55ca8b9042c9341c352f17fa/regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5", size = 803589, upload-time = "2026-01-14T23:14:55.182Z" },
{ url = "https://files.pythonhosted.org/packages/b6/3c/2f8d199d0e84e78bcd6bdc2be9b62410624f6b796e2893d1837ae738b160/regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079", size = 266060, upload-time = "2025-10-21T15:55:37.902Z" }, { url = "https://files.pythonhosted.org/packages/66/23/33289beba7ccb8b805c6610a8913d0131f834928afc555b241caabd422a9/regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3", size = 775700, upload-time = "2026-01-14T23:14:56.707Z" },
{ url = "https://files.pythonhosted.org/packages/d7/67/c35e80969f6ded306ad70b0698863310bdf36aca57ad792f45ddc0e2271f/regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842", size = 276931, upload-time = "2025-10-21T15:55:39.502Z" }, { url = "https://files.pythonhosted.org/packages/e7/65/bf3a42fa6897a0d3afa81acb25c42f4b71c274f698ceabd75523259f6688/regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb", size = 787928, upload-time = "2026-01-14T23:14:58.312Z" },
{ url = "https://files.pythonhosted.org/packages/f5/a1/4ed147de7d2b60174f758412c87fa51ada15cd3296a0ff047f4280aaa7ca/regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335", size = 270103, upload-time = "2025-10-21T15:55:41.24Z" }, { url = "https://files.pythonhosted.org/packages/f4/f5/13bf65864fc314f68cdd6d8ca94adcab064d4d39dbd0b10fef29a9da48fc/regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410", size = 858607, upload-time = "2026-01-14T23:15:00.657Z" },
{ url = "https://files.pythonhosted.org/packages/28/c6/195a6217a43719d5a6a12cc192a22d12c40290cecfa577f00f4fb822f07d/regex-2025.10.23-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7690f95404a1293923a296981fd943cca12c31a41af9c21ba3edd06398fc193", size = 488956, upload-time = "2025-10-21T15:55:42.887Z" }, { url = "https://files.pythonhosted.org/packages/a3/31/040e589834d7a439ee43fb0e1e902bc81bd58a5ba81acffe586bb3321d35/regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4", size = 763729, upload-time = "2026-01-14T23:15:02.248Z" },
{ url = "https://files.pythonhosted.org/packages/4c/93/181070cd1aa2fa541ff2d3afcf763ceecd4937b34c615fa92765020a6c90/regex-2025.10.23-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a32d77aeaea58a13230100dd8797ac1a84c457f3af2fdf0d81ea689d5a9105b", size = 290997, upload-time = "2025-10-21T15:55:44.53Z" }, { url = "https://files.pythonhosted.org/packages/9b/84/6921e8129687a427edf25a34a5594b588b6d88f491320b9de5b6339a4fcb/regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d", size = 850697, upload-time = "2026-01-14T23:15:03.878Z" },
{ url = "https://files.pythonhosted.org/packages/b6/c5/9d37fbe3a40ed8dda78c23e1263002497540c0d1522ed75482ef6c2000f0/regex-2025.10.23-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b24b29402f264f70a3c81f45974323b41764ff7159655360543b7cabb73e7d2f", size = 288686, upload-time = "2025-10-21T15:55:46.186Z" }, { url = "https://files.pythonhosted.org/packages/8a/87/3d06143d4b128f4229158f2de5de6c8f2485170c7221e61bf381313314b2/regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22", size = 789849, upload-time = "2026-01-14T23:15:06.102Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e7/db610ff9f10c2921f9b6ac0c8d8be4681b28ddd40fc0549429366967e61f/regex-2025.10.23-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563824a08c7c03d96856d84b46fdb3bbb7cfbdf79da7ef68725cda2ce169c72a", size = 798466, upload-time = "2025-10-21T15:55:48.24Z" }, { url = "https://files.pythonhosted.org/packages/77/69/c50a63842b6bd48850ebc7ab22d46e7a2a32d824ad6c605b218441814639/regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913", size = 266279, upload-time = "2026-01-14T23:15:07.678Z" },
{ url = "https://files.pythonhosted.org/packages/90/10/aab883e1fa7fe2feb15ac663026e70ca0ae1411efa0c7a4a0342d9545015/regex-2025.10.23-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0ec8bdd88d2e2659c3518087ee34b37e20bd169419ffead4240a7004e8ed03b", size = 863996, upload-time = "2025-10-21T15:55:50.478Z" }, { url = "https://files.pythonhosted.org/packages/f2/36/39d0b29d087e2b11fd8191e15e81cce1b635fcc845297c67f11d0d19274d/regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a", size = 277166, upload-time = "2026-01-14T23:15:09.257Z" },
{ url = "https://files.pythonhosted.org/packages/a2/b0/8f686dd97a51f3b37d0238cd00a6d0f9ccabe701f05b56de1918571d0d61/regex-2025.10.23-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b577601bfe1d33913fcd9276d7607bbac827c4798d9e14d04bf37d417a6c41cb", size = 912145, upload-time = "2025-10-21T15:55:52.215Z" }, { url = "https://files.pythonhosted.org/packages/28/32/5b8e476a12262748851fa8ab1b0be540360692325975b094e594dfebbb52/regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056", size = 270415, upload-time = "2026-01-14T23:15:10.743Z" },
{ url = "https://files.pythonhosted.org/packages/a3/ca/639f8cd5b08797bca38fc5e7e07f76641a428cf8c7fca05894caf045aa32/regex-2025.10.23-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c9f2c68ac6cb3de94eea08a437a75eaa2bd33f9e97c84836ca0b610a5804368", size = 803370, upload-time = "2025-10-21T15:55:53.944Z" }, { url = "https://files.pythonhosted.org/packages/f8/2e/6870bb16e982669b674cce3ee9ff2d1d46ab80528ee6bcc20fb2292efb60/regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e", size = 489164, upload-time = "2026-01-14T23:15:13.962Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1e/a40725bb76959eddf8abc42a967bed6f4851b39f5ac4f20e9794d7832aa5/regex-2025.10.23-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89f8b9ea3830c79468e26b0e21c3585f69f105157c2154a36f6b7839f8afb351", size = 787767, upload-time = "2025-10-21T15:55:56.004Z" }, { url = "https://files.pythonhosted.org/packages/dc/67/9774542e203849b0286badf67199970a44ebdb0cc5fb739f06e47ada72f8/regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10", size = 291218, upload-time = "2026-01-14T23:15:15.647Z" },
{ url = "https://files.pythonhosted.org/packages/3d/d8/8ee9858062936b0f99656dce390aa667c6e7fb0c357b1b9bf76fb5e2e708/regex-2025.10.23-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:98fd84c4e4ea185b3bb5bf065261ab45867d8875032f358a435647285c722673", size = 858335, upload-time = "2025-10-21T15:55:58.185Z" }, { url = "https://files.pythonhosted.org/packages/b2/87/b0cda79f22b8dee05f774922a214da109f9a4c0eca5da2c9d72d77ea062c/regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc", size = 288895, upload-time = "2026-01-14T23:15:17.788Z" },
{ url = "https://files.pythonhosted.org/packages/d8/0a/ed5faaa63fa8e3064ab670e08061fbf09e3a10235b19630cf0cbb9e48c0a/regex-2025.10.23-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1e11d3e5887b8b096f96b4154dfb902f29c723a9556639586cd140e77e28b313", size = 850402, upload-time = "2025-10-21T15:56:00.023Z" }, { url = "https://files.pythonhosted.org/packages/3b/6a/0041f0a2170d32be01ab981d6346c83a8934277d82c780d60b127331f264/regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599", size = 798680, upload-time = "2026-01-14T23:15:19.342Z" },
{ url = "https://files.pythonhosted.org/packages/79/14/d05f617342f4b2b4a23561da500ca2beab062bfcc408d60680e77ecaf04d/regex-2025.10.23-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f13450328a6634348d47a88367e06b64c9d84980ef6a748f717b13f8ce64e87", size = 789739, upload-time = "2025-10-21T15:56:01.967Z" }, { url = "https://files.pythonhosted.org/packages/58/de/30e1cfcdbe3e891324aa7568b7c968771f82190df5524fabc1138cb2d45a/regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae", size = 864210, upload-time = "2026-01-14T23:15:22.005Z" },
{ url = "https://files.pythonhosted.org/packages/f9/7b/e8ce8eef42a15f2c3461f8b3e6e924bbc86e9605cb534a393aadc8d3aff8/regex-2025.10.23-cp313-cp313-win32.whl", hash = "sha256:37be9296598a30c6a20236248cb8b2c07ffd54d095b75d3a2a2ee5babdc51df1", size = 266054, upload-time = "2025-10-21T15:56:05.291Z" }, { url = "https://files.pythonhosted.org/packages/64/44/4db2f5c5ca0ccd40ff052ae7b1e9731352fcdad946c2b812285a7505ca75/regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5", size = 912358, upload-time = "2026-01-14T23:15:24.569Z" },
{ url = "https://files.pythonhosted.org/packages/71/2d/55184ed6be6473187868d2f2e6a0708195fc58270e62a22cbf26028f2570/regex-2025.10.23-cp313-cp313-win_amd64.whl", hash = "sha256:ea7a3c283ce0f06fe789365841e9174ba05f8db16e2fd6ae00a02df9572c04c0", size = 276917, upload-time = "2025-10-21T15:56:07.303Z" }, { url = "https://files.pythonhosted.org/packages/79/b6/e6a5665d43a7c42467138c8a2549be432bad22cbd206f5ec87162de74bd7/regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6", size = 803583, upload-time = "2026-01-14T23:15:26.526Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d4/927eced0e2bd45c45839e556f987f8c8f8683268dd3c00ad327deb3b0172/regex-2025.10.23-cp313-cp313-win_arm64.whl", hash = "sha256:d9a4953575f300a7bab71afa4cd4ac061c7697c89590a2902b536783eeb49a4f", size = 270105, upload-time = "2025-10-21T15:56:09.857Z" }, { url = "https://files.pythonhosted.org/packages/e7/53/7cd478222169d85d74d7437e74750005e993f52f335f7c04ff7adfda3310/regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788", size = 775782, upload-time = "2026-01-14T23:15:29.352Z" },
{ url = "https://files.pythonhosted.org/packages/3e/b3/95b310605285573341fc062d1d30b19a54f857530e86c805f942c4ff7941/regex-2025.10.23-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7d6606524fa77b3912c9ef52a42ef63c6cfbfc1077e9dc6296cd5da0da286044", size = 491850, upload-time = "2025-10-21T15:56:11.685Z" }, { url = "https://files.pythonhosted.org/packages/ca/b5/75f9a9ee4b03a7c009fe60500fe550b45df94f0955ca29af16333ef557c5/regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714", size = 787978, upload-time = "2026-01-14T23:15:31.295Z" },
{ url = "https://files.pythonhosted.org/packages/a4/8f/207c2cec01e34e56db1eff606eef46644a60cf1739ecd474627db90ad90b/regex-2025.10.23-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c037aadf4d64bdc38af7db3dbd34877a057ce6524eefcb2914d6d41c56f968cc", size = 292537, upload-time = "2025-10-21T15:56:13.963Z" }, { url = "https://files.pythonhosted.org/packages/72/b3/79821c826245bbe9ccbb54f6eadb7879c722fd3e0248c17bfc90bf54e123/regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d", size = 858550, upload-time = "2026-01-14T23:15:33.558Z" },
{ url = "https://files.pythonhosted.org/packages/98/3b/025240af4ada1dc0b5f10d73f3e5122d04ce7f8908ab8881e5d82b9d61b6/regex-2025.10.23-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99018c331fb2529084a0c9b4c713dfa49fafb47c7712422e49467c13a636c656", size = 290904, upload-time = "2025-10-21T15:56:16.016Z" }, { url = "https://files.pythonhosted.org/packages/4a/85/2ab5f77a1c465745bfbfcb3ad63178a58337ae8d5274315e2cc623a822fa/regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3", size = 763747, upload-time = "2026-01-14T23:15:35.206Z" },
{ url = "https://files.pythonhosted.org/packages/81/8e/104ac14e2d3450c43db18ec03e1b96b445a94ae510b60138f00ce2cb7ca1/regex-2025.10.23-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd8aba965604d70306eb90a35528f776e59112a7114a5162824d43b76fa27f58", size = 807311, upload-time = "2025-10-21T15:56:17.818Z" }, { url = "https://files.pythonhosted.org/packages/6d/84/c27df502d4bfe2873a3e3a7cf1bdb2b9cc10284d1a44797cf38bed790470/regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31", size = 850615, upload-time = "2026-01-14T23:15:37.523Z" },
{ url = "https://files.pythonhosted.org/packages/19/63/78aef90141b7ce0be8a18e1782f764f6997ad09de0e05251f0d2503a914a/regex-2025.10.23-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:238e67264b4013e74136c49f883734f68656adf8257bfa13b515626b31b20f8e", size = 873241, upload-time = "2025-10-21T15:56:19.941Z" }, { url = "https://files.pythonhosted.org/packages/7d/b7/658a9782fb253680aa8ecb5ccbb51f69e088ed48142c46d9f0c99b46c575/regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3", size = 789951, upload-time = "2026-01-14T23:15:39.582Z" },
{ url = "https://files.pythonhosted.org/packages/b3/a8/80eb1201bb49ae4dba68a1b284b4211ed9daa8e74dc600018a10a90399fb/regex-2025.10.23-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2eb48bd9848d66fd04826382f5e8491ae633de3233a3d64d58ceb4ecfa2113a", size = 914794, upload-time = "2025-10-21T15:56:22.488Z" }, { url = "https://files.pythonhosted.org/packages/fc/2a/5928af114441e059f15b2f63e188bd00c6529b3051c974ade7444b85fcda/regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f", size = 266275, upload-time = "2026-01-14T23:15:42.108Z" },
{ url = "https://files.pythonhosted.org/packages/f0/d5/1984b6ee93281f360a119a5ca1af6a8ca7d8417861671388bf750becc29b/regex-2025.10.23-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d36591ce06d047d0c0fe2fc5f14bfbd5b4525d08a7b6a279379085e13f0e3d0e", size = 812581, upload-time = "2025-10-21T15:56:24.319Z" }, { url = "https://files.pythonhosted.org/packages/4f/16/5bfbb89e435897bff28cf0352a992ca719d9e55ebf8b629203c96b6ce4f7/regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e", size = 277145, upload-time = "2026-01-14T23:15:44.244Z" },
{ url = "https://files.pythonhosted.org/packages/c4/39/11ebdc6d9927172a64ae237d16763145db6bd45ebb4055c17b88edab72a7/regex-2025.10.23-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5d4ece8628d6e364302006366cea3ee887db397faebacc5dacf8ef19e064cf8", size = 795346, upload-time = "2025-10-21T15:56:26.232Z" }, { url = "https://files.pythonhosted.org/packages/56/c1/a09ff7392ef4233296e821aec5f78c51be5e91ffde0d163059e50fd75835/regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337", size = 270411, upload-time = "2026-01-14T23:15:45.858Z" },
{ url = "https://files.pythonhosted.org/packages/3b/b4/89a591bcc08b5e436af43315284bd233ba77daf0cf20e098d7af12f006c1/regex-2025.10.23-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:39a7e8083959cb1c4ff74e483eecb5a65d3b3e1d821b256e54baf61782c906c6", size = 868214, upload-time = "2025-10-21T15:56:28.597Z" }, { url = "https://files.pythonhosted.org/packages/3c/38/0cfd5a78e5c6db00e6782fdae70458f89850ce95baa5e8694ab91d89744f/regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be", size = 492068, upload-time = "2026-01-14T23:15:47.616Z" },
{ url = "https://files.pythonhosted.org/packages/3d/ff/58ba98409c1dbc8316cdb20dafbc63ed267380a07780cafecaf5012dabc9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:842d449a8fefe546f311656cf8c0d6729b08c09a185f1cad94c756210286d6a8", size = 854540, upload-time = "2025-10-21T15:56:30.875Z" }, { url = "https://files.pythonhosted.org/packages/50/72/6c86acff16cb7c959c4355826bbf06aad670682d07c8f3998d9ef4fee7cd/regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8", size = 292756, upload-time = "2026-01-14T23:15:49.307Z" },
{ url = "https://files.pythonhosted.org/packages/9a/f2/4a9e9338d67626e2071b643f828a482712ad15889d7268e11e9a63d6f7e9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d614986dc68506be8f00474f4f6960e03e4ca9883f7df47744800e7d7c08a494", size = 799346, upload-time = "2025-10-21T15:56:32.725Z" }, { url = "https://files.pythonhosted.org/packages/4e/58/df7fb69eadfe76526ddfce28abdc0af09ffe65f20c2c90932e89d705153f/regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd", size = 291114, upload-time = "2026-01-14T23:15:51.484Z" },
{ url = "https://files.pythonhosted.org/packages/63/be/543d35c46bebf6f7bf2be538cca74d6585f25714700c36f37f01b92df551/regex-2025.10.23-cp313-cp313t-win32.whl", hash = "sha256:a5b7a26b51a9df473ec16a1934d117443a775ceb7b39b78670b2e21893c330c9", size = 268657, upload-time = "2025-10-21T15:56:34.577Z" }, { url = "https://files.pythonhosted.org/packages/ed/6c/a4011cd1cf96b90d2cdc7e156f91efbd26531e822a7fbb82a43c1016678e/regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a", size = 807524, upload-time = "2026-01-14T23:15:53.102Z" },
{ url = "https://files.pythonhosted.org/packages/14/9f/4dd6b7b612037158bb2c9bcaa710e6fb3c40ad54af441b9c53b3a137a9f1/regex-2025.10.23-cp313-cp313t-win_amd64.whl", hash = "sha256:ce81c5544a5453f61cb6f548ed358cfb111e3b23f3cd42d250a4077a6be2a7b6", size = 280075, upload-time = "2025-10-21T15:56:36.767Z" }, { url = "https://files.pythonhosted.org/packages/1d/25/a53ffb73183f69c3e9f4355c4922b76d2840aee160af6af5fac229b6201d/regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93", size = 873455, upload-time = "2026-01-14T23:15:54.956Z" },
{ url = "https://files.pythonhosted.org/packages/81/7a/5bd0672aa65d38c8da6747c17c8b441bdb53d816c569e3261013af8e83cf/regex-2025.10.23-cp313-cp313t-win_arm64.whl", hash = "sha256:e9bf7f6699f490e4e43c44757aa179dab24d1960999c84ab5c3d5377714ed473", size = 271219, upload-time = "2025-10-21T15:56:39.033Z" }, { url = "https://files.pythonhosted.org/packages/66/0b/8b47fc2e8f97d9b4a851736f3890a5f786443aa8901061c55f24c955f45b/regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af", size = 915007, upload-time = "2026-01-14T23:15:57.041Z" },
{ url = "https://files.pythonhosted.org/packages/73/f6/0caf29fec943f201fbc8822879c99d31e59c1d51a983d9843ee5cf398539/regex-2025.10.23-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5b5cb5b6344c4c4c24b2dc87b0bfee78202b07ef7633385df70da7fcf6f7cec6", size = 488960, upload-time = "2025-10-21T15:56:40.849Z" }, { url = "https://files.pythonhosted.org/packages/c2/fa/97de0d681e6d26fabe71968dbee06dd52819e9a22fdce5dac7256c31ed84/regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09", size = 812794, upload-time = "2026-01-14T23:15:58.916Z" },
{ url = "https://files.pythonhosted.org/packages/8e/7d/ebb7085b8fa31c24ce0355107cea2b92229d9050552a01c5d291c42aecea/regex-2025.10.23-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a6ce7973384c37bdf0f371a843f95a6e6f4e1489e10e0cf57330198df72959c5", size = 290932, upload-time = "2025-10-21T15:56:42.875Z" }, { url = "https://files.pythonhosted.org/packages/22/38/e752f94e860d429654aa2b1c51880bff8dfe8f084268258adf9151cf1f53/regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5", size = 781159, upload-time = "2026-01-14T23:16:00.817Z" },
{ url = "https://files.pythonhosted.org/packages/27/41/43906867287cbb5ca4cee671c3cc8081e15deef86a8189c3aad9ac9f6b4d/regex-2025.10.23-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2ee3663f2c334959016b56e3bd0dd187cbc73f948e3a3af14c3caaa0c3035d10", size = 288766, upload-time = "2025-10-21T15:56:44.894Z" }, { url = "https://files.pythonhosted.org/packages/e9/a7/d739ffaef33c378fc888302a018d7f81080393d96c476b058b8c64fd2b0d/regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794", size = 795558, upload-time = "2026-01-14T23:16:03.267Z" },
{ url = "https://files.pythonhosted.org/packages/ab/9e/ea66132776700fc77a39b1056e7a5f1308032fead94507e208dc6716b7cd/regex-2025.10.23-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2003cc82a579107e70d013482acce8ba773293f2db534fb532738395c557ff34", size = 798884, upload-time = "2025-10-21T15:56:47.178Z" }, { url = "https://files.pythonhosted.org/packages/3e/c4/542876f9a0ac576100fc73e9c75b779f5c31e3527576cfc9cb3009dcc58a/regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a", size = 868427, upload-time = "2026-01-14T23:16:05.646Z" },
{ url = "https://files.pythonhosted.org/packages/d5/99/aed1453687ab63819a443930770db972c5c8064421f0d9f5da9ad029f26b/regex-2025.10.23-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:182c452279365a93a9f45874f7f191ec1c51e1f1eb41bf2b16563f1a40c1da3a", size = 864768, upload-time = "2025-10-21T15:56:49.793Z" }, { url = "https://files.pythonhosted.org/packages/fc/0f/d5655bea5b22069e32ae85a947aa564912f23758e112cdb74212848a1a1b/regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80", size = 769939, upload-time = "2026-01-14T23:16:07.542Z" },
{ url = "https://files.pythonhosted.org/packages/99/5d/732fe747a1304805eb3853ce6337eea16b169f7105a0d0dd9c6a5ffa9948/regex-2025.10.23-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b1249e9ff581c5b658c8f0437f883b01f1edcf424a16388591e7c05e5e9e8b0c", size = 911394, upload-time = "2025-10-21T15:56:52.186Z" }, { url = "https://files.pythonhosted.org/packages/20/06/7e18a4fa9d326daeda46d471a44ef94201c46eaa26dbbb780b5d92cbfdda/regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2", size = 854753, upload-time = "2026-01-14T23:16:10.395Z" },
{ url = "https://files.pythonhosted.org/packages/5e/48/58a1f6623466522352a6efa153b9a3714fc559d9f930e9bc947b4a88a2c3/regex-2025.10.23-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b841698f93db3ccc36caa1900d2a3be281d9539b822dc012f08fc80b46a3224", size = 803145, upload-time = "2025-10-21T15:56:55.142Z" }, { url = "https://files.pythonhosted.org/packages/3b/67/dc8946ef3965e166f558ef3b47f492bc364e96a265eb4a2bb3ca765c8e46/regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60", size = 799559, upload-time = "2026-01-14T23:16:12.347Z" },
{ url = "https://files.pythonhosted.org/packages/ea/f6/7dea79be2681a5574ab3fc237aa53b2c1dfd6bd2b44d4640b6c76f33f4c1/regex-2025.10.23-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:956d89e0c92d471e8f7eee73f73fdff5ed345886378c45a43175a77538a1ffe4", size = 787831, upload-time = "2025-10-21T15:56:57.203Z" }, { url = "https://files.pythonhosted.org/packages/a5/61/1bba81ff6d50c86c65d9fd84ce9699dd106438ee4cdb105bf60374ee8412/regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952", size = 268879, upload-time = "2026-01-14T23:16:14.049Z" },
{ url = "https://files.pythonhosted.org/packages/3a/ad/07b76950fbbe65f88120ca2d8d845047c401450f607c99ed38862904671d/regex-2025.10.23-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5c259cb363299a0d90d63b5c0d7568ee98419861618a95ee9d91a41cb9954462", size = 859162, upload-time = "2025-10-21T15:56:59.195Z" }, { url = "https://files.pythonhosted.org/packages/e9/5e/cef7d4c5fb0ea3ac5c775fd37db5747f7378b29526cc83f572198924ff47/regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10", size = 280317, upload-time = "2026-01-14T23:16:15.718Z" },
{ url = "https://files.pythonhosted.org/packages/41/87/374f3b2021b22aa6a4fc0b750d63f9721e53d1631a238f7a1c343c1cd288/regex-2025.10.23-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:185d2b18c062820b3a40d8fefa223a83f10b20a674bf6e8c4a432e8dfd844627", size = 849899, upload-time = "2025-10-21T15:57:01.747Z" }, { url = "https://files.pythonhosted.org/packages/b4/52/4317f7a5988544e34ab57b4bde0f04944c4786128c933fb09825924d3e82/regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829", size = 271551, upload-time = "2026-01-14T23:16:17.533Z" },
{ url = "https://files.pythonhosted.org/packages/12/4a/7f7bb17c5a5a9747249807210e348450dab9212a46ae6d23ebce86ba6a2b/regex-2025.10.23-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:281d87fa790049c2b7c1b4253121edd80b392b19b5a3d28dc2a77579cb2a58ec", size = 789372, upload-time = "2025-10-21T15:57:04.018Z" }, { url = "https://files.pythonhosted.org/packages/52/0a/47fa888ec7cbbc7d62c5f2a6a888878e76169170ead271a35239edd8f0e8/regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac", size = 489170, upload-time = "2026-01-14T23:16:19.835Z" },
{ url = "https://files.pythonhosted.org/packages/c9/dd/9c7728ff544fea09bbc8635e4c9e7c423b11c24f1a7a14e6ac4831466709/regex-2025.10.23-cp314-cp314-win32.whl", hash = "sha256:63b81eef3656072e4ca87c58084c7a9c2b81d41a300b157be635a8a675aacfb8", size = 271451, upload-time = "2025-10-21T15:57:06.266Z" }, { url = "https://files.pythonhosted.org/packages/ac/c4/d000e9b7296c15737c9301708e9e7fbdea009f8e93541b6b43bdb8219646/regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6", size = 291146, upload-time = "2026-01-14T23:16:21.541Z" },
{ url = "https://files.pythonhosted.org/packages/48/f8/ef7837ff858eb74079c4804c10b0403c0b740762e6eedba41062225f7117/regex-2025.10.23-cp314-cp314-win_amd64.whl", hash = "sha256:0967c5b86f274800a34a4ed862dfab56928144d03cb18821c5153f8777947796", size = 280173, upload-time = "2025-10-21T15:57:08.206Z" }, { url = "https://files.pythonhosted.org/packages/f9/b6/921cc61982e538682bdf3bdf5b2c6ab6b34368da1f8e98a6c1ddc503c9cf/regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2", size = 288986, upload-time = "2026-01-14T23:16:23.381Z" },
{ url = "https://files.pythonhosted.org/packages/8e/d0/d576e1dbd9885bfcd83d0e90762beea48d9373a6f7ed39170f44ed22e336/regex-2025.10.23-cp314-cp314-win_arm64.whl", hash = "sha256:c70dfe58b0a00b36aa04cdb0f798bf3e0adc31747641f69e191109fd8572c9a9", size = 273206, upload-time = "2025-10-21T15:57:10.367Z" }, { url = "https://files.pythonhosted.org/packages/ca/33/eb7383dde0bbc93f4fb9d03453aab97e18ad4024ac7e26cef8d1f0a2cff0/regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846", size = 799098, upload-time = "2026-01-14T23:16:25.088Z" },
{ url = "https://files.pythonhosted.org/packages/a6/d0/2025268315e8b2b7b660039824cb7765a41623e97d4cd421510925400487/regex-2025.10.23-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1f5799ea1787aa6de6c150377d11afad39a38afd033f0c5247aecb997978c422", size = 491854, upload-time = "2025-10-21T15:57:12.526Z" }, { url = "https://files.pythonhosted.org/packages/27/56/b664dccae898fc8d8b4c23accd853f723bde0f026c747b6f6262b688029c/regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b", size = 864980, upload-time = "2026-01-14T23:16:27.297Z" },
{ url = "https://files.pythonhosted.org/packages/44/35/5681c2fec5e8b33454390af209c4353dfc44606bf06d714b0b8bd0454ffe/regex-2025.10.23-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a9639ab7540cfea45ef57d16dcbea2e22de351998d614c3ad2f9778fa3bdd788", size = 292542, upload-time = "2025-10-21T15:57:15.158Z" }, { url = "https://files.pythonhosted.org/packages/16/40/0999e064a170eddd237bae9ccfcd8f28b3aa98a38bf727a086425542a4fc/regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e", size = 911607, upload-time = "2026-01-14T23:16:29.235Z" },
{ url = "https://files.pythonhosted.org/packages/5d/17/184eed05543b724132e4a18149e900f5189001fcfe2d64edaae4fbaf36b4/regex-2025.10.23-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:08f52122c352eb44c3421dab78b9b73a8a77a282cc8314ae576fcaa92b780d10", size = 290903, upload-time = "2025-10-21T15:57:17.108Z" }, { url = "https://files.pythonhosted.org/packages/07/78/c77f644b68ab054e5a674fb4da40ff7bffb2c88df58afa82dbf86573092d/regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde", size = 803358, upload-time = "2026-01-14T23:16:31.369Z" },
{ url = "https://files.pythonhosted.org/packages/25/d0/5e3347aa0db0de382dddfa133a7b0ae72f24b4344f3989398980b44a3924/regex-2025.10.23-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebf1baebef1c4088ad5a5623decec6b52950f0e4d7a0ae4d48f0a99f8c9cb7d7", size = 807546, upload-time = "2025-10-21T15:57:19.179Z" }, { url = "https://files.pythonhosted.org/packages/27/31/d4292ea8566eaa551fafc07797961c5963cf5235c797cc2ae19b85dfd04d/regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5", size = 775833, upload-time = "2026-01-14T23:16:33.141Z" },
{ url = "https://files.pythonhosted.org/packages/d2/bb/40c589bbdce1be0c55e9f8159789d58d47a22014f2f820cf2b517a5cd193/regex-2025.10.23-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:16b0f1c2e2d566c562d5c384c2b492646be0a19798532fdc1fdedacc66e3223f", size = 873322, upload-time = "2025-10-21T15:57:21.36Z" }, { url = "https://files.pythonhosted.org/packages/ce/b2/cff3bf2fea4133aa6fb0d1e370b37544d18c8350a2fa118c7e11d1db0e14/regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34", size = 788045, upload-time = "2026-01-14T23:16:35.005Z" },
{ url = "https://files.pythonhosted.org/packages/fe/56/a7e40c01575ac93360e606278d359f91829781a9f7fb6e5aa435039edbda/regex-2025.10.23-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7ada5d9dceafaab92646aa00c10a9efd9b09942dd9b0d7c5a4b73db92cc7e61", size = 914855, upload-time = "2025-10-21T15:57:24.044Z" }, { url = "https://files.pythonhosted.org/packages/8d/99/2cb9b69045372ec877b6f5124bda4eb4253bc58b8fe5848c973f752bc52c/regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75", size = 859374, upload-time = "2026-01-14T23:16:36.919Z" },
{ url = "https://files.pythonhosted.org/packages/5c/4b/d55587b192763db3163c3f508b3b67b31bb6f5e7a0e08b83013d0a59500a/regex-2025.10.23-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a36b4005770044bf08edecc798f0e41a75795b9e7c9c12fe29da8d792ef870c", size = 812724, upload-time = "2025-10-21T15:57:26.123Z" }, { url = "https://files.pythonhosted.org/packages/09/16/710b0a5abe8e077b1729a562d2f297224ad079f3a66dce46844c193416c8/regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e", size = 763940, upload-time = "2026-01-14T23:16:38.685Z" },
{ url = "https://files.pythonhosted.org/packages/33/20/18bac334955fbe99d17229f4f8e98d05e4a501ac03a442be8facbb37c304/regex-2025.10.23-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:af7b2661dcc032da1fae82069b5ebf2ac1dfcd5359ef8b35e1367bfc92181432", size = 795439, upload-time = "2025-10-21T15:57:28.497Z" }, { url = "https://files.pythonhosted.org/packages/dd/d1/7585c8e744e40eb3d32f119191969b91de04c073fca98ec14299041f6e7e/regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160", size = 850112, upload-time = "2026-01-14T23:16:40.646Z" },
{ url = "https://files.pythonhosted.org/packages/67/46/c57266be9df8549c7d85deb4cb82280cb0019e46fff677534c5fa1badfa4/regex-2025.10.23-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb976810ac1416a67562c2e5ba0accf6f928932320fef302e08100ed681b38e", size = 868336, upload-time = "2025-10-21T15:57:30.867Z" }, { url = "https://files.pythonhosted.org/packages/af/d6/43e1dd85df86c49a347aa57c1f69d12c652c7b60e37ec162e3096194a278/regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1", size = 789586, upload-time = "2026-01-14T23:16:42.799Z" },
{ url = "https://files.pythonhosted.org/packages/b8/f3/bd5879e41ef8187fec5e678e94b526a93f99e7bbe0437b0f2b47f9101694/regex-2025.10.23-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1a56a54be3897d62f54290190fbcd754bff6932934529fbf5b29933da28fcd43", size = 854567, upload-time = "2025-10-21T15:57:33.062Z" }, { url = "https://files.pythonhosted.org/packages/93/38/77142422f631e013f316aaae83234c629555729a9fbc952b8a63ac91462a/regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1", size = 271691, upload-time = "2026-01-14T23:16:44.671Z" },
{ url = "https://files.pythonhosted.org/packages/e6/57/2b6bbdbd2f24dfed5b028033aa17ad8f7d86bb28f1a892cac8b3bc89d059/regex-2025.10.23-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8f3e6d202fb52c2153f532043bbcf618fd177df47b0b306741eb9b60ba96edc3", size = 799565, upload-time = "2025-10-21T15:57:35.153Z" }, { url = "https://files.pythonhosted.org/packages/4a/a9/ab16b4649524ca9e05213c1cdbb7faa85cc2aa90a0230d2f796cbaf22736/regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903", size = 280422, upload-time = "2026-01-14T23:16:46.607Z" },
{ url = "https://files.pythonhosted.org/packages/c7/ba/a6168f542ba73b151ed81237adf6b869c7b2f7f8d51618111296674e20ee/regex-2025.10.23-cp314-cp314t-win32.whl", hash = "sha256:1fa1186966b2621b1769fd467c7b22e317e6ba2d2cdcecc42ea3089ef04a8521", size = 274428, upload-time = "2025-10-21T15:57:37.996Z" }, { url = "https://files.pythonhosted.org/packages/be/2a/20fd057bf3521cb4791f69f869635f73e0aaf2b9ad2d260f728144f9047c/regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705", size = 273467, upload-time = "2026-01-14T23:16:48.967Z" },
{ url = "https://files.pythonhosted.org/packages/ef/a0/c84475e14a2829e9b0864ebf77c3f7da909df9d8acfe2bb540ff0072047c/regex-2025.10.23-cp314-cp314t-win_amd64.whl", hash = "sha256:08a15d40ce28362eac3e78e83d75475147869c1ff86bc93285f43b4f4431a741", size = 284140, upload-time = "2025-10-21T15:57:40.027Z" }, { url = "https://files.pythonhosted.org/packages/ad/77/0b1e81857060b92b9cad239104c46507dd481b3ff1fa79f8e7f865aae38a/regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8", size = 492073, upload-time = "2026-01-14T23:16:51.154Z" },
{ url = "https://files.pythonhosted.org/packages/51/33/6a08ade0eee5b8ba79386869fa6f77afeb835b60510f3525db987e2fffc4/regex-2025.10.23-cp314-cp314t-win_arm64.whl", hash = "sha256:a93e97338e1c8ea2649e130dcfbe8cd69bba5e1e163834752ab64dcb4de6d5ed", size = 274497, upload-time = "2025-10-21T15:57:42.389Z" }, { url = "https://files.pythonhosted.org/packages/70/f3/f8302b0c208b22c1e4f423147e1913fd475ddd6230565b299925353de644/regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf", size = 292757, upload-time = "2026-01-14T23:16:53.08Z" },
{ url = "https://files.pythonhosted.org/packages/bf/f0/ef55de2460f3b4a6da9d9e7daacd0cb79d4ef75c64a2af316e68447f0df0/regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d", size = 291122, upload-time = "2026-01-14T23:16:55.383Z" },
{ url = "https://files.pythonhosted.org/packages/cf/55/bb8ccbacabbc3a11d863ee62a9f18b160a83084ea95cdfc5d207bfc3dd75/regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84", size = 807761, upload-time = "2026-01-14T23:16:57.251Z" },
{ url = "https://files.pythonhosted.org/packages/8f/84/f75d937f17f81e55679a0509e86176e29caa7298c38bd1db7ce9c0bf6075/regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df", size = 873538, upload-time = "2026-01-14T23:16:59.349Z" },
{ url = "https://files.pythonhosted.org/packages/b8/d9/0da86327df70349aa8d86390da91171bd3ca4f0e7c1d1d453a9c10344da3/regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434", size = 915066, upload-time = "2026-01-14T23:17:01.607Z" },
{ url = "https://files.pythonhosted.org/packages/2a/5e/f660fb23fc77baa2a61aa1f1fe3a4eea2bbb8a286ddec148030672e18834/regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a", size = 812938, upload-time = "2026-01-14T23:17:04.366Z" },
{ url = "https://files.pythonhosted.org/packages/69/33/a47a29bfecebbbfd1e5cd3f26b28020a97e4820f1c5148e66e3b7d4b4992/regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10", size = 781314, upload-time = "2026-01-14T23:17:06.378Z" },
{ url = "https://files.pythonhosted.org/packages/65/ec/7ec2bbfd4c3f4e494a24dec4c6943a668e2030426b1b8b949a6462d2c17b/regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac", size = 795652, upload-time = "2026-01-14T23:17:08.521Z" },
{ url = "https://files.pythonhosted.org/packages/46/79/a5d8651ae131fe27d7c521ad300aa7f1c7be1dbeee4d446498af5411b8a9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea", size = 868550, upload-time = "2026-01-14T23:17:10.573Z" },
{ url = "https://files.pythonhosted.org/packages/06/b7/25635d2809664b79f183070786a5552dd4e627e5aedb0065f4e3cf8ee37d/regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e", size = 769981, upload-time = "2026-01-14T23:17:12.871Z" },
{ url = "https://files.pythonhosted.org/packages/16/8b/fc3fcbb2393dcfa4a6c5ffad92dc498e842df4581ea9d14309fcd3c55fb9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521", size = 854780, upload-time = "2026-01-14T23:17:14.837Z" },
{ url = "https://files.pythonhosted.org/packages/d0/38/dde117c76c624713c8a2842530be9c93ca8b606c0f6102d86e8cd1ce8bea/regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db", size = 799778, upload-time = "2026-01-14T23:17:17.369Z" },
{ url = "https://files.pythonhosted.org/packages/e3/0d/3a6cfa9ae99606afb612d8fb7a66b245a9d5ff0f29bb347c8a30b6ad561b/regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e", size = 274667, upload-time = "2026-01-14T23:17:19.301Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b2/297293bb0742fd06b8d8e2572db41a855cdf1cae0bf009b1cb74fe07e196/regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf", size = 284386, upload-time = "2026-01-14T23:17:21.231Z" },
{ url = "https://files.pythonhosted.org/packages/95/e4/a3b9480c78cf8ee86626cb06f8d931d74d775897d44201ccb813097ae697/regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70", size = 274837, upload-time = "2026-01-14T23:17:23.146Z" },
] ]
[[package]] [[package]]
@ -316,51 +330,56 @@ wheels = [
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.3.0" version = "2.4.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
{ url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
{ url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
{ url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
{ url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
{ url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
{ url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
{ url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
{ url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
{ url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
{ url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
{ url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
{ url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
{ url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
{ url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
{ url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
{ url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
{ url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
{ url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
{ url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
{ url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
{ url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
{ url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
{ url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
{ url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
{ url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
{ url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
{ url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
{ url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
{ url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
] ]
[[package]] [[package]]
@ -386,13 +405,13 @@ wheels = [
[[package]] [[package]]
name = "yamllint" name = "yamllint"
version = "1.37.1" version = "1.38.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "pathspec" }, { name = "pathspec" },
{ name = "pyyaml" }, { name = "pyyaml" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/46/f2/cd8b7584a48ee83f0bc94f8a32fea38734cefcdc6f7324c4d3bfc699457b/yamllint-1.37.1.tar.gz", hash = "sha256:81f7c0c5559becc8049470d86046b36e96113637bcbe4753ecef06977c00245d", size = 141613, upload-time = "2025-05-04T08:25:54.355Z" } sdist = { url = "https://files.pythonhosted.org/packages/28/a0/8fc2d68e132cf918f18273fdc8a1b8432b60d75ac12fdae4b0ef5c9d2e8d/yamllint-1.38.0.tar.gz", hash = "sha256:09e5f29531daab93366bb061e76019d5e91691ef0a40328f04c927387d1d364d", size = 142446, upload-time = "2026-01-13T07:47:53.276Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/dd/b9/be7a4cfdf47e03785f657f94daea8123e838d817be76c684298305bd789f/yamllint-1.37.1-py3-none-any.whl", hash = "sha256:364f0d79e81409f591e323725e6a9f4504c8699ddf2d7263d8d2b539cd66a583", size = 68813, upload-time = "2025-05-04T08:25:52.552Z" }, { url = "https://files.pythonhosted.org/packages/05/92/aed08e68de6e6a3d7c2328ce7388072cd6affc26e2917197430b646aed02/yamllint-1.38.0-py3-none-any.whl", hash = "sha256:fc394a5b3be980a4062607b8fdddc0843f4fa394152b6da21722f5d59013c220", size = 68940, upload-time = "2026-01-13T07:47:51.343Z" },
] ]

View File

@ -1293,8 +1293,7 @@ td .commit-summary {
filter: drop-shadow(-4px 0 0 var(--color-primary-alpha-30)) !important; filter: drop-shadow(-4px 0 0 var(--color-primary-alpha-30)) !important;
} }
.code-comment:target, .code-comment:target {
.diff-file-box:target {
border-color: var(--color-primary) !important; border-color: var(--color-primary) !important;
border-radius: var(--border-radius) !important; border-radius: var(--border-radius) !important;
box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important; box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important;
@ -1681,18 +1680,27 @@ tbody.commit-list {
margin-top: 1em; margin-top: 1em;
} }
.diff-file-box:target {
border-color: var(--color-primary) !important;
border-radius: var(--border-radius) !important;
box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important;
}
.diff-file-header { .diff-file-header {
padding: 5px 8px !important; padding: 5px 8px !important;
box-shadow: 0 -1px 0 1px var(--color-body); /* prevent borders being visible behind top corners when sticky and scrolled */
font-weight: var(--font-weight-normal);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em;
/* prevent borders from being visible behind top corners when sticky and scrolled,
this "shadow" is used to use body's color to cover the scrolled-up left and right borders at corners */
box-shadow: 0 -1px 0 1px var(--color-body);
} }
.diff-file-header .file { .diff-file-box:target .diff-file-header {
min-width: 0; box-shadow: unset; /* when targeted, still use the parent's box-shadow, remove the patched above */
} }
.diff-file-header .file-link { .diff-file-header .file-link {
@ -1715,6 +1723,7 @@ tbody.commit-list {
.diff-file-header { .diff-file-header {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
gap: 0;
} }
} }
@ -1743,13 +1752,13 @@ tbody.commit-list {
.diff-stats-bar { .diff-stats-bar {
display: inline-block; display: inline-block;
background-color: var(--color-red); background-color: var(--color-diff-removed-fg); /* the background is used as "text foreground color" */
height: 12px; height: 12px;
width: 44px; width: 44px;
} }
.diff-stats-bar .diff-stats-add-bar { .diff-stats-bar .diff-stats-add-bar {
background-color: var(--color-green); background-color: var(--color-diff-added-fg);
height: 100%; height: 100%;
} }

View File

@ -7,12 +7,13 @@ gitea-theme-meta-info {
} }
/* red/green colorblind-friendly colors */ /* red/green colorblind-friendly colors */
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
:root { :root {
--color-diff-added-linenum-bg: #1979fd46; --color-diff-added-fg: #58a6ff;
--color-diff-added-row-bg: #1979fd20; --color-diff-added-linenum-bg: #243d5d;
--color-diff-added-word-bg: #1979fd66; --color-diff-added-row-bg: #132339;
--color-diff-removed-linenum-bg: #c8622146; --color-diff-added-word-bg: #214d87;
--color-diff-removed-row-bg: #c8622120; --color-diff-removed-fg: #f0883e;
--color-diff-removed-word-bg: #c8622166; --color-diff-removed-linenum-bg: #5b361c;
--color-diff-removed-row-bg: #3c2419;
--color-diff-removed-word-bg: #824e1f;
} }

View File

@ -1,4 +1,4 @@
@import "./theme-gitea-dark-protanopia-deuteranopia.css"; @import "./theme-gitea-dark.css";
gitea-theme-meta-info { gitea-theme-meta-info {
--theme-display-name: "Dark"; --theme-display-name: "Dark";
@ -7,9 +7,9 @@ gitea-theme-meta-info {
} }
/* blue/yellow colorblind-friendly colors */ /* blue/yellow colorblind-friendly colors */
/* from GitHub: blue yellow blindness is based on red green blindness, and --diffBlob-deletion-* restored to the normal theme color */
:root { :root {
--color-diff-removed-linenum-bg: #482121; --color-diff-added-fg: #58a6ff;
--color-diff-removed-row-bg: #301e1e; --color-diff-added-linenum-bg: #243d5d;
--color-diff-removed-word-bg: #6f3333; --color-diff-added-row-bg: #132339;
--color-diff-added-word-bg: #214d87;
} }

View File

@ -148,12 +148,14 @@ gitea-theme-meta-info {
--color-grey-light: #818f9e; --color-grey-light: #818f9e;
--color-gold: #b1983b; --color-gold: #b1983b;
--color-white: #ffffff; --color-white: #ffffff;
--color-diff-added-fg: #87ab63;
--color-diff-added-linenum-bg: #274227; --color-diff-added-linenum-bg: #274227;
--color-diff-added-row-bg: #203224; --color-diff-added-row-bg: #203224;
--color-diff-added-row-border: #314a37; --color-diff-added-row-border: #314a37;
--color-diff-added-word-bg: #3c653c; --color-diff-added-word-bg: #3c653c;
--color-diff-moved-row-bg: #818044; --color-diff-moved-row-bg: #818044;
--color-diff-moved-row-border: #bcca6f; --color-diff-moved-row-border: #bcca6f;
--color-diff-removed-fg: #cc4848;
--color-diff-removed-linenum-bg: #482121; --color-diff-removed-linenum-bg: #482121;
--color-diff-removed-row-bg: #301e1e; --color-diff-removed-row-bg: #301e1e;
--color-diff-removed-row-border: #634343; --color-diff-removed-row-border: #634343;

View File

@ -7,12 +7,13 @@ gitea-theme-meta-info {
} }
/* red/green colorblind-friendly colors */ /* red/green colorblind-friendly colors */
/* from GitHub: --diffBlob-addition-*, --diffBlob-deletion-*, etc */
:root { :root {
--color-diff-added-linenum-bg: #54aeff4d; --color-diff-added-fg: #2185d0;
--color-diff-added-row-bg: #ddf4ff80; --color-diff-added-linenum-bg: #b6e3ff;
--color-diff-added-word-bg: #54aeff66; --color-diff-added-row-bg: #ddf4ff;
--color-diff-removed-linenum-bg: #ffb77c4d; --color-diff-added-word-bg: #b6e3ff;
--color-diff-removed-row-bg: #fff1e580; --color-diff-removed-fg: #fc6500;
--color-diff-removed-word-bg: #ffb77c80; --color-diff-removed-linenum-bg: #ffd8b5;
--color-diff-removed-row-bg: #fff1e5;
--color-diff-removed-word-bg: #ffd8b5;
} }

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