diff options
57 files changed, 326 insertions, 205 deletions
@@ -36,7 +36,8 @@ XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1 -GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1 +GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.19.0 +GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.19.0 DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -230,7 +231,7 @@ clean: ## delete backend and integration files tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ .PHONY: fmt -fmt: ## format the Go code +fmt: ## format the Go and template code @GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' $(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl')) @# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only @@ -249,6 +250,19 @@ fmt-check: fmt exit 1; \ fi +.PHONY: fix +fix: ## apply automated fixes to Go code + $(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./... + +.PHONY: fix-check +fix-check: fix + @diff=$$(git diff --color=always $(GO_SOURCES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fix' and commit the result:"; \ + printf "%s" "$${diff}"; \ + exit 1; \ + fi + .PHONY: $(TAGS_EVIDENCE) $(TAGS_EVIDENCE): @mkdir -p $(MAKE_EVIDENCE_DIR) @@ -288,7 +302,7 @@ checks: checks-frontend checks-backend ## run various consistency checks checks-frontend: lockfile-check svg-check ## check frontend files .PHONY: checks-backend -checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files +checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files .PHONY: lint lint: lint-frontend lint-backend lint-spell ## lint everything @@ -809,6 +823,7 @@ deps-tools: ## install tool dependencies $(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE) & \ $(GO) install $(GOPLS_PACKAGE) & \ + $(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \ wait node_modules: package-lock.json diff --git a/cmd/admin_auth_smtp.go b/cmd/admin_auth_smtp.go index e9daf71809..93e0587fc3 100644 --- a/cmd/admin_auth_smtp.go +++ b/cmd/admin_auth_smtp.go @@ -39,12 +39,10 @@ func smtpCLIFlags() []cli.Flag { &cli.BoolFlag{ Name: "force-smtps", Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.", - Value: true, }, &cli.BoolFlag{ Name: "skip-verify", Usage: "Skip TLS verify.", - Value: true, }, &cli.StringFlag{ Name: "helo-hostname", @@ -54,7 +52,6 @@ func smtpCLIFlags() []cli.Flag { &cli.BoolFlag{ Name: "disable-helo", Usage: "Disable SMTP helo.", - Value: true, }, &cli.StringFlag{ Name: "allowed-domains", @@ -64,7 +61,6 @@ func smtpCLIFlags() []cli.Flag { &cli.BoolFlag{ Name: "skip-local-2fa", Usage: "Skip 2FA to log on.", - Value: true, }, &cli.BoolFlag{ Name: "active", diff --git a/cmd/admin_auth_smtp_test.go b/cmd/admin_auth_smtp_test.go index 9778ff87d2..e54e01830c 100644 --- a/cmd/admin_auth_smtp_test.go +++ b/cmd/admin_auth_smtp_test.go @@ -60,10 +60,8 @@ func TestAddSMTP(t *testing.T) { Auth: "PLAIN", Host: "localhost", Port: 25, - // ForceSMTPS: true, - // SkipVerify: true, }, - TwoFactorPolicy: "skip", + TwoFactorPolicy: "", }, }, { @@ -73,12 +71,12 @@ func TestAddSMTP(t *testing.T) { "--host", "localhost", "--port", "25", "--auth-type", "LOGIN", - "--force-smtps=false", - "--skip-verify=false", + "--force-smtps", + "--skip-verify", "--helo-hostname", "example.com", - "--disable-helo=false", + "--disable-helo=true", "--allowed-domains", "example.com,example.org", - "--skip-local-2fa=false", + "--skip-local-2fa", "--active=false", }, source: &auth_model.Source{ @@ -89,13 +87,13 @@ func TestAddSMTP(t *testing.T) { Auth: "LOGIN", Host: "localhost", Port: 25, - ForceSMTPS: false, - SkipVerify: false, + ForceSMTPS: true, + SkipVerify: true, HeloHostname: "example.com", - DisableHelo: false, + DisableHelo: true, AllowedDomains: "example.com,example.org", }, - TwoFactorPolicy: "", + TwoFactorPolicy: "skip", }, }, } @@ -157,13 +155,10 @@ func TestUpdateSMTP(t *testing.T) { Name: "old name", IsActive: true, Cfg: &smtp.Source{ - Auth: "PLAIN", - Host: "old host", - Port: 26, - ForceSMTPS: true, - SkipVerify: true, + Auth: "PLAIN", + Host: "old host", + Port: 26, }, - TwoFactorPolicy: "", }, args: []string{ "--id", "1", @@ -177,13 +172,10 @@ func TestUpdateSMTP(t *testing.T) { Name: "test", IsActive: true, Cfg: &smtp.Source{ - Auth: "PLAIN", - Host: "localhost", - Port: 25, - ForceSMTPS: true, - SkipVerify: true, + Auth: "PLAIN", + Host: "localhost", + Port: 25, }, - TwoFactorPolicy: "skip", }, }, { @@ -197,10 +189,7 @@ func TestUpdateSMTP(t *testing.T) { Auth: "PLAIN", Host: "old host", Port: 26, - ForceSMTPS: true, - SkipVerify: true, HeloHostname: "old.example.com", - DisableHelo: false, AllowedDomains: "old.example.com", }, TwoFactorPolicy: "", @@ -211,12 +200,12 @@ func TestUpdateSMTP(t *testing.T) { "--host", "localhost", "--port", "25", "--auth-type", "LOGIN", - "--force-smtps=false", - "--skip-verify=false", + "--force-smtps", + "--skip-verify", "--helo-hostname", "example.com", - "--disable-helo=true", + "--disable-helo", "--allowed-domains", "example.com,example.org", - "--skip-local-2fa=true", + "--skip-local-2fa", "--active=false", }, authSource: &auth_model.Source{ @@ -228,8 +217,8 @@ func TestUpdateSMTP(t *testing.T) { Auth: "LOGIN", Host: "localhost", Port: 25, - ForceSMTPS: false, - SkipVerify: false, + ForceSMTPS: true, + SkipVerify: true, HeloHostname: "example.com", DisableHelo: true, AllowedDomains: "example.com,example.org", @@ -252,11 +241,8 @@ func TestUpdateSMTP(t *testing.T) { Name: "test", IsActive: true, Cfg: &smtp.Source{ - Auth: "PLAIN", - SkipVerify: true, - ForceSMTPS: true, + Auth: "PLAIN", }, - TwoFactorPolicy: "skip", }, nil }, diff --git a/cmd/cmd.go b/cmd/cmd.go index 7a4d5d0d89..5b96bcbf9a 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -132,3 +132,13 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cl return ctx, nil } } + +func isValidDefaultSubCommand(cmd *cli.Command) (string, bool) { + // Dirty patch for urfave/cli's strange design. + // "./gitea bad-cmd" should not start the web server. + rootArgs := cmd.Root().Args().Slice() + if len(rootArgs) != 0 && rootArgs[0] != cmd.Name { + return rootArgs[0], false + } + return "", true +} diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 0000000000..a36d05c76e --- /dev/null +++ b/cmd/cmd_test.go @@ -0,0 +1,38 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v3" +) + +func TestDefaultCommand(t *testing.T) { + test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) { + called := false + cmd := &cli.Command{ + DefaultCommand: "test", + Commands: []*cli.Command{ + { + Name: "test", + Action: func(ctx context.Context, command *cli.Command) error { + retName, retValid := isValidDefaultSubCommand(command) + assert.Equal(t, expectedRetName, retName) + assert.Equal(t, expectedRetValid, retValid) + called = true + return nil + }, + }, + }, + } + assert.NoError(t, cmd.Run(t.Context(), args)) + assert.True(t, called) + } + test(t, []string{"./gitea"}, "", true) + test(t, []string{"./gitea", "test"}, "", true) + test(t, []string{"./gitea", "other"}, "other", false) +} diff --git a/cmd/main.go b/cmd/main.go index 128b8776b4..3b8a8a9311 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -152,6 +152,8 @@ func NewMainApp(appVer AppVersion) *cli.Command { CmdDocs, } + // TODO: we should eventually drop the default command, + // but not sure whether it would break Windows users who used to double-click the EXE to run. app.DefaultCommand = CmdWeb.Name app.Flags = append(app.Flags, cli.VersionFlag) diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index c83073e9c6..ac29e7d3e5 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -119,7 +119,6 @@ var ( Name: "rotate", Aliases: []string{"r"}, Usage: "Rotate logs", - Value: true, }, &cli.Int64Flag{ Name: "max-size", @@ -130,7 +129,6 @@ var ( Name: "daily", Aliases: []string{"d"}, Usage: "Rotate logs daily", - Value: true, }, &cli.IntFlag{ Name: "max-days", @@ -141,7 +139,6 @@ var ( Name: "compress", Aliases: []string{"z"}, Usage: "Compress rotated logs", - Value: true, }, &cli.IntFlag{ Name: "compression-level", diff --git a/cmd/web.go b/cmd/web.go index 39e336fe54..61ee3cbc20 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -251,6 +251,10 @@ func runWeb(_ context.Context, cmd *cli.Command) error { } }() + if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid { + return fmt.Errorf("unknown command: %s", subCmdName) + } + managerCtx, cancel := context.WithCancel(context.Background()) graceful.InitManager(managerCtx) defer cancel() diff --git a/models/migrations/v1_22/v294_test.go b/models/migrations/v1_22/v294_test.go index a1d702cb77..c3de332650 100644 --- a/models/migrations/v1_22/v294_test.go +++ b/models/migrations/v1_22/v294_test.go @@ -4,7 +4,6 @@ package v1_22 //nolint import ( - "slices" "testing" "code.gitea.io/gitea/models/migrations/base" @@ -44,7 +43,7 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) { for _, index := range tables[0].Indexes { if index.Type == schemas.UniqueType { found = true - slices.Equal(index.Cols, []string{"project_id", "issue_id"}) + assert.ElementsMatch(t, index.Cols, []string{"project_id", "issue_id"}) break } } diff --git a/models/packages/package_property.go b/models/packages/package_property.go index 10670951ad..7ddbfd97e9 100644 --- a/models/packages/package_property.go +++ b/models/packages/package_property.go @@ -92,8 +92,8 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error { return err } -// DeletePropertyByName deletes properties by name -func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error { +// DeletePropertiesByName deletes properties by name +func DeletePropertiesByName(ctx context.Context, refType PropertyType, refID int64, name string) error { _, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{}) return err } diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 5cdd45b046..e7677edc59 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -565,18 +565,12 @@ func matchPullRequestReviewEvent(prPayload *api.PullRequestPayload, evt *jobpars actions = append(actions, "submitted", "edited") } - matched := false for _, val := range vals { if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) { - matched = true - } - if matched { + matchTimes++ break } } - if matched { - matchTimes++ - } default: log.Warn("pull request review event unsupported condition %q", cond) } @@ -611,18 +605,12 @@ func matchPullRequestReviewCommentEvent(prPayload *api.PullRequestPayload, evt * actions = append(actions, "created", "edited") } - matched := false for _, val := range vals { if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) { - matched = true - } - if matched { + matchTimes++ break } } - if matched { - matchTimes++ - } default: log.Warn("pull request review comment event unsupported condition %q", cond) } diff --git a/modules/packages/content_store.go b/modules/packages/content_store.go index 37612556d7..dadb7eaefc 100644 --- a/modules/packages/content_store.go +++ b/modules/packages/content_store.go @@ -28,8 +28,7 @@ func NewContentStore() *ContentStore { return contentStore } -// Get gets a package blob -func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) { +func (s *ContentStore) OpenBlob(key BlobHash256Key) (storage.Object, error) { return s.store.Open(KeyToRelativePath(key)) } diff --git a/modules/public/public.go b/modules/public/public.go index 9bc04f3f7e..a7eace1538 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -110,5 +110,4 @@ func servePublicAsset(w http.ResponseWriter, req *http.Request, fi os.FileInfo, } } http.ServeContent(w, req, fi.Name(), modtime, content) - return } diff --git a/modules/setting/markup.go b/modules/setting/markup.go index afaaaa2831..057b0650c3 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -149,7 +149,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) { func newMarkupSanitizer(name string, sec ConfigSection) { rule, ok := createMarkupSanitizerRule(name, sec) if ok { - if after, ok0 := strings.CutPrefix(name, "sanitizer."); ok0 { + if after, found := strings.CutPrefix(name, "sanitizer."); found { names := strings.SplitN(after, ".", 2) name = names[0] } diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index a740ac1ff5..fab4a06ecb 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1334,6 +1334,7 @@ editor.new_branch_name_desc=Назва нової гілки… editor.cancel=Відмінити editor.filename_cannot_be_empty=Назва файлу не може бути порожньою. editor.filename_is_invalid=Назва файлу недійсна: "%s". +editor.commit_email=Електронна пошта коміту editor.invalid_commit_email=Адреса електронної пошти для коміту недійсна. editor.file_is_a_symlink=`"%s" - це символічне посилання. Символічні посилання не можна редагувати у веб-редакторі` editor.filename_is_a_directory=Назва файлу '%s' вже використовується як назва каталогу у цьому сховищі. @@ -1369,6 +1370,7 @@ commits.signed_by_untrusted_user=Підписаний недовіреним к commits.signed_by_untrusted_user_unmatched=Підписано недовіреним користувачем, який не відповідає комітеру commits.gpg_key_id=Ідентифікатор GPG ключа commits.ssh_key_fingerprint=Відбиток ключа SSH +commits.view_path=Переглянути в історії commits.view_file_diff=Переглянути зміни до цього файлу в цьому коміті commit.revert=Повернути до попереднього стану @@ -1497,6 +1499,7 @@ issues.filter_project=Проєкт issues.filter_project_all=Всі проєкти issues.filter_project_none=Проєкт відсутній issues.filter_assignee=Виконавець +issues.filter_assignee_any_assignee=Призначено будь-кому issues.filter_poster=Автор issues.filter_user_placeholder=Пошук користувачів issues.filter_user_no_select=Усі користувачі @@ -1527,6 +1530,8 @@ issues.action_milestone=Етап issues.action_milestone_no_select=Етап відсутній issues.action_assignee=Виконавець issues.action_assignee_no_select=Немає виконавця +issues.action_check=Встановити/зняти позначку +issues.action_check_all=Встановити/зняти позначку з усіх елементів issues.opened_by=%[1]s відкрито <a href="%[2]s">%[3]s</a> issues.opened_by_fake=%[1]s відкрито користувачем %[2]s issues.previous=Попередній @@ -1534,6 +1539,7 @@ issues.next=Далі issues.open_title=Відкрито issues.closed_title=Закрито issues.draft_title=Чернетка +issues.num_comments_1=%d коментар issues.num_comments=%d коментарів issues.commented_at=`прокоментував(ла) <a href="#%s">%s</a>` issues.delete_comment_confirm=Ви впевнені, що хочете видалити цей коментар? @@ -1542,6 +1548,7 @@ issues.context.quote_reply=Цитувати відповідь issues.context.reference_issue=Посилання в новій задачі issues.context.edit=Редагувати issues.context.delete=Видалити +issues.close=Закрити задачу issues.comment_manually_pull_merged_at=вручну об'єднав(-ла) коміти %[1]s в %[2]s %[3]s issues.close_comment_issue=Закрити з коментарем issues.reopen_issue=Відкрити знову @@ -1568,6 +1575,7 @@ issues.role.collaborator=Співавтор issues.role.collaborator_helper=Цей користувач був запрошений до співпраці у сховищі. issues.role.first_time_contributor=Учасник, який вперше долучився issues.role.first_time_contributor_helper=Це перший внесок цього користувача в сховищі. +issues.role.contributor=Співавтор issues.role.contributor_helper=Цей користувач раніше вже вносив зміни до сховища. issues.re_request_review=Повторно попросити рецензію issues.is_stale=З часу останньої перевірки в цей PR було внесено деякі зміни @@ -2190,6 +2198,7 @@ settings.webhook.response=Відповідь settings.webhook.headers=Заголовки settings.webhook.payload=Зміст settings.webhook.body=Тіло +settings.webhook.replay.description=Повторити цей веб-хук. settings.githook_edit_desc=Якщо хук неактивний, буде показано зразок вмісту. Якщо залишити вміст порожнім, хук буде вимкнено. settings.githook_name=Назва хуку settings.githook_content=Зміст хука @@ -2246,6 +2255,7 @@ settings.event_pull_request_sync=Запит на злиття синхроніз settings.event_pull_request_sync_desc=Запит до злиття синхронізовано. settings.event_package=Пакет settings.branch_filter=Фільтр гілок +settings.authorization_header=Заголовок авторизації settings.active=Активний settings.active_helper=Інформацію про викликані події буде надіслано за цією веб-хук URL-адресою. settings.add_hook_success=Веб-хук було додано. @@ -2294,6 +2304,7 @@ settings.protect_disable_push=Заборонити Push settings.protect_disable_push_desc=Для цієї гілки буде заборонено виконання push. settings.protect_enable_push=Дозволити Push settings.protect_enable_push_desc=Будь-хто із правом запису зможе виконувати push для цієї гілки (за виключенням force push). +settings.protect_enable_merge=Увімкнути об’єднання settings.protect_check_status_contexts=Увімкнути перевірку стану settings.protect_status_check_patterns=Шаблони перевірки стану: settings.protect_status_check_patterns_desc=Введіть шаблони, щоб вказати, які перевірки стану повинні пройти гілки, перш ніж їх буде об'єднано у гілку, що відповідає цьому правилу. Кожен рядок визначає шаблон. Шаблони не можуть бути порожніми. @@ -2327,6 +2338,8 @@ settings.block_outdated_branch_desc=Об'єднання буде неможли settings.block_admin_merge_override=Адміністратори повинні дотримуватися правил захисту гілки settings.block_admin_merge_override_desc=Адміністратори повинні дотримуватися правил захисту гілки і не можуть їх обійти. settings.default_branch_desc=Обрати типову гілку сховища для запитів на злиття і комітів: +settings.merge_style_desc=Стилі об'єднання +settings.default_merge_style_desc=Типовий стиль об'єднання settings.choose_branch=Оберіть гілку… settings.no_protected_branch=Немає захищених гілок. settings.edit_protected_branch=Редагувати @@ -2348,12 +2361,14 @@ settings.chat_id=ID чату settings.matrix.homeserver_url=URL домашньої сторінки settings.matrix.room_id=ID кімнати settings.matrix.message_type=Тип повідомлення +settings.visibility.public.button=Зробити публічним settings.archive.header=Відправити репозиторій в архів settings.archive.success=Сховище успішно заархівовано. settings.archive.error=Сталася помилка при спробі архівувати репозиторій. Докладнішу інформацію дивіться у журналі. settings.archive.error_ismirror=Неможливо архівувати дзеркальне сховище. settings.archive.branchsettings_unavailable=Параметри гілки не доступні, якщо репозиторій архівний. settings.archive.tagsettings_unavailable=Параметри міток недоступні, якщо репозиторій архівний. +settings.unarchive.header=Розархівувати це сховище settings.unarchive.success=Сховище успішно розархівовано. settings.update_avatar_success=Аватар репозиторію оновлений. settings.lfs=LFS @@ -2437,6 +2452,7 @@ diff.protected=Захищений diff.image.side_by_side=Поруч diff.image.swipe=Провести пальцем diff.image.overlay=Накласти +diff.has_escaped=Цей рядок містить приховані символи Юнікоду diff.show_file_tree=Показати дерево файлів diff.hide_file_tree=Сховати дерево файлів diff.submodule_added=Підмодуль %[1]s додано в %[2]s @@ -2749,6 +2765,7 @@ dashboard.resync_all_hooks=Заново синхронізувати хуки п dashboard.reinit_missing_repos=Заново ініціалізувати всі відсутні сховища Git'а, для яких існують записи dashboard.sync_external_users=Синхронізувати дані зовнішніх користувачів dashboard.cleanup_hook_task_table=Очистити таблицю hook_task +dashboard.cleanup_actions=Очищення ресурсів прострочених дій dashboard.server_uptime=Час роботи сервера dashboard.current_goroutine=Поточна кількість Goroutines dashboard.current_memory_usage=Поточне використання пам'яті @@ -2778,7 +2795,11 @@ dashboard.total_gc_time=Загальна пауза збирача сміття dashboard.total_gc_pause=Загальна пауза збирача сміття (GC) dashboard.last_gc_pause=Остання пауза збирача сміття (GC) dashboard.gc_times=Кількість запусків збирача сміття (GC) +dashboard.delete_old_actions=Видалити всі старі дії з бази даних dashboard.update_checker=Перевірка оновлень +dashboard.gc_lfs=Збір сміття мета-об'єктів LFS +dashboard.cancel_abandoned_jobs=Скасувати покинуті завдання +dashboard.start_schedule_tasks=Запуск запланованих завдань dashboard.sync_branch.started=Розпочато синхронізацію гілок dashboard.rebuild_issue_indexer=Перебудувати індексатор задач dashboard.sync_repo_licenses=Синхронізувати ліцензії сховища @@ -2851,6 +2872,7 @@ emails.not_updated=Не вдалось оновити адресу електр emails.duplicate_active=Ця адреса електронної пошти вже активна для іншого користувача. emails.change_email_header=Редагувати властивості електронної пошти emails.change_email_text=Ви впевнені, що хочете оновити адресу електронної пошти? +emails.delete=Видалити адресу електронної пошти emails.delete_desc=Ви впевнені, що хочете видалити адресу електронної пошти? emails.deletion_success=Адресу електронної пошти видалено. emails.delete_primary_email_error=Ви не можете видалити основну адресу електронної пошти. @@ -2871,6 +2893,7 @@ repos.issues=Задачі repos.size=Розмір repos.lfs_size=Розмір LFS +packages.package_manage_panel=Керування пакетами packages.total_size=Загальний розмір: %s packages.unreferenced_size=Розмір без посилань: %s packages.cleanup=Очистити прострочені дані @@ -3393,6 +3416,7 @@ settings.link=Прив'язати пакет до сховища settings.link.description=Якщо ви зв'яжете пакет зі сховищем, його буде вказано у списку пакетів сховища. settings.link.select=Обрати сховище settings.link.button=Оновити посилання на сховище +settings.link.error=Не вдалося оновити посилання на сховище. settings.delete=Видалити пакет settings.delete.description=Видалення пакета є остаточним і не може бути скасоване. settings.delete.notice=Ви збираєтесь видалити %s (%s). Цю операцію неможливо скасувати, ви впевнені? @@ -3516,6 +3540,7 @@ logs.always_expand_running=Завжди розгортати поточні жу [projects] deleted.display_name=Видалений проєкт type-1.display_name=Індивідуальний проєкт +type-2.display_name=Проєкт сховища type-3.display_name=Проєкт організації enter_fullscreen=Повноекранний режим exit_fullscreen=Вийти з повноекранного режиму diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go index f35cff3df2..ba4a4f23ce 100644 --- a/routers/api/packages/alpine/alpine.go +++ b/routers/api/packages/alpine/alpine.go @@ -68,7 +68,7 @@ func GetRepositoryFile(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion( ctx, pv, &packages_service.PackageFileInfo{ @@ -216,7 +216,7 @@ func DownloadPackageFile(ctx *context.Context) { } } - s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0]) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) diff --git a/routers/api/packages/arch/arch.go b/routers/api/packages/arch/arch.go index f5dc6c1d01..bf9cc3f1b8 100644 --- a/routers/api/packages/arch/arch.go +++ b/routers/api/packages/arch/arch.go @@ -239,7 +239,7 @@ func GetPackageOrRepositoryFile(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0]) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go index 57cb83404f..cfcf79244f 100644 --- a/routers/api/packages/cargo/cargo.go +++ b/routers/api/packages/cargo/cargo.go @@ -165,7 +165,7 @@ func ListOwners(ctx *context.Context) { // DownloadPackageFile serves the content of a package func DownloadPackageFile(ctx *context.Context) { - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go index a0c8c5696c..1f11afe548 100644 --- a/routers/api/packages/chef/chef.go +++ b/routers/api/packages/chef/chef.go @@ -343,7 +343,7 @@ func DownloadPackage(ctx *context.Context) { pf := pd.Files[0].File - s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.OpenFileForDownload(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index 3713805579..9daf0ffeff 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -160,7 +160,7 @@ func PackageMetadata(ctx *context.Context) { // DownloadPackageFile serves the content of a package func DownloadPackageFile(ctx *context.Context) { - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go index 8019eee9f7..fe70e02cd6 100644 --- a/routers/api/packages/conan/conan.go +++ b/routers/api/packages/conan/conan.go @@ -480,7 +480,7 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe return } - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go index 7a46681235..fe7542dd18 100644 --- a/routers/api/packages/conda/conda.go +++ b/routers/api/packages/conda/conda.go @@ -293,7 +293,7 @@ func DownloadPackageFile(ctx *context.Context) { pf := pfs[0] - s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.OpenFileForDownload(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index 477c3bc71a..972506103a 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -705,7 +705,7 @@ func DeleteManifest(ctx *context.Context) { func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) { serveDirectReqParams := make(url.Values) serveDirectReqParams.Set("response-content-type", pfd.Properties.GetByName(container_module.PropertyMediaType)) - s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob, serveDirectReqParams) + s, u, _, err := packages_service.OpenBlobForDownload(ctx, pfd.File, pfd.Blob, serveDirectReqParams) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index 0cbd46e943..a640bcda25 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" packages_service "code.gitea.io/gitea/services/packages" + container_service "code.gitea.io/gitea/services/packages/container" "github.com/opencontainers/go-digest" oci "github.com/opencontainers/image-spec/specs-go/v1" @@ -84,12 +85,11 @@ func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf manifestDigest := "" err := func() error { - var manifest oci.Manifest - if err := json.NewDecoder(buf).Decode(&manifest); err != nil { + manifest, configDescriptor, metadata, err := container_service.ParseManifestMetadata(ctx, buf, mci.Owner.ID, mci.Image) + if err != nil { return err } - - if _, err := buf.Seek(0, io.SeekStart); err != nil { + if _, err = buf.Seek(0, io.SeekStart); err != nil { return err } @@ -99,28 +99,7 @@ func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf } defer committer.Close() - configDescriptor, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{ - OwnerID: mci.Owner.ID, - Image: mci.Image, - Digest: string(manifest.Config.Digest), - }) - if err != nil { - return err - } - - configReader, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(configDescriptor.Blob.HashSHA256)) - if err != nil { - return err - } - defer configReader.Close() - - metadata, err := container_module.ParseImageConfig(manifest.Config.MediaType, configReader) - if err != nil { - return err - } - blobReferences := make([]*blobReference, 0, 1+len(manifest.Layers)) - blobReferences = append(blobReferences, &blobReference{ Digest: manifest.Config.Digest, MediaType: manifest.Config.MediaType, @@ -388,19 +367,16 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met return nil, err } } else { - props, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged) - if err != nil { + if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged); err != nil { return nil, err } - for _, prop := range props { - if err = packages_model.DeletePropertyByID(ctx, prop.ID); err != nil { - return nil, err - } - } } + if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference); err != nil { + return nil, err + } for _, manifest := range metadata.Manifests { - if err = packages_model.InsertOrUpdateProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil { + if _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil { return nil, err } } diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go index 8a20072cb6..732acd215f 100644 --- a/routers/api/packages/cran/cran.go +++ b/routers/api/packages/cran/cran.go @@ -250,7 +250,7 @@ func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) { return } - s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.OpenFileForDownload(ctx, pf) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index fec34c91a6..346f71fa5d 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -59,7 +59,7 @@ func GetRepositoryFile(ctx *context.Context) { key += "|" + component + "|" + architecture } - s, u, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion( ctx, pv, &packages_service.PackageFileInfo{ @@ -106,7 +106,7 @@ func GetRepositoryFileByHash(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0]) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) @@ -210,7 +210,7 @@ func DownloadPackageFile(ctx *context.Context) { name := ctx.PathParam("name") version := ctx.PathParam("version") - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index 0b5daa7334..db7aeace50 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -31,7 +31,7 @@ func apiError(ctx *context.Context, status int, obj any) { // DownloadPackageFile serves the specific generic package. func DownloadPackageFile(ctx *context.Context) { - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go index bde29df739..89ec86bce9 100644 --- a/routers/api/packages/goproxy/goproxy.go +++ b/routers/api/packages/goproxy/goproxy.go @@ -106,7 +106,7 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, u, _, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, _, err := packages_service.OpenFileForDownload(ctx, pfs[0]) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go index fb12daaa46..39c34f4da4 100644 --- a/routers/api/packages/helm/helm.go +++ b/routers/api/packages/helm/helm.go @@ -122,7 +122,7 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion( ctx, pvs[0], &packages_service.PackageFileInfo{ diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index 9089c2eccf..40a8ff8242 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -223,7 +223,7 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool return } - s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb, nil) + s, u, _, err := packages_service.OpenBlobForDownload(ctx, pf, pb, nil) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 6ec46bcb36..1f09816d32 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -85,7 +85,7 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.PathParam("version") filename := ctx.PathParam("filename") - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -132,7 +132,7 @@ func DownloadPackageFileByName(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion( ctx, pvs[0], &packages_service.PackageFileInfo{ diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index fa5067a278..0a3254979d 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -405,7 +405,7 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.PathParam("version") filename := ctx.PathParam("filename") - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -669,7 +669,7 @@ func DownloadSymbolFile(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0]) if err != nil { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) { apiError(ctx, http.StatusNotFound, err) diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go index e7b07aefd0..4bd36e94b6 100644 --- a/routers/api/packages/pub/pub.go +++ b/routers/api/packages/pub/pub.go @@ -274,7 +274,7 @@ func DownloadPackageFile(ctx *context.Context) { pf := pd.Files[0].File - s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.OpenFileForDownload(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go index 199f4e7478..9b5ae6c89d 100644 --- a/routers/api/packages/pypi/pypi.go +++ b/routers/api/packages/pypi/pypi.go @@ -82,7 +82,7 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.PathParam("version") filename := ctx.PathParam("filename") - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index a00a61c079..938c35341d 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -96,7 +96,7 @@ func GetRepositoryFile(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion( ctx, pv, &packages_service.PackageFileInfo{ @@ -220,7 +220,7 @@ func DownloadPackageFile(ctx *context.Context) { name := ctx.PathParam("name") version := ctx.PathParam("version") - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index cb880c8bdb..774d5520fd 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -178,7 +178,7 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, u, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion( ctx, pvs[0], &packages_service.PackageFileInfo{ diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go index 47439c4c3b..bf542f33a7 100644 --- a/routers/api/packages/swift/swift.go +++ b/routers/api/packages/swift/swift.go @@ -429,7 +429,7 @@ func DownloadPackageFile(ctx *context.Context) { pf := pd.Files[0].File - s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.OpenFileForDownload(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go index 3afaa5de1f..9eb67e5397 100644 --- a/routers/api/packages/vagrant/vagrant.go +++ b/routers/api/packages/vagrant/vagrant.go @@ -218,7 +218,7 @@ func UploadPackageFile(ctx *context.Context) { } func DownloadPackageFile(ctx *context.Context) { - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, diff --git a/routers/install/install.go b/routers/install/install.go index 4b3eba78b3..b9bc41dfcf 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -99,11 +99,7 @@ func Install(ctx *context.Context) { form.SSLMode = setting.Database.SSLMode curDBType := setting.Database.Type.String() - var isCurDBTypeSupported bool - if slices.Contains(setting.SupportedDatabaseTypes, curDBType) { - isCurDBTypeSupported = true - } - if !isCurDBTypeSupported { + if !slices.Contains(setting.SupportedDatabaseTypes, curDBType) { curDBType = "mysql" } ctx.Data["CurDbType"] = curDBType diff --git a/routers/private/hook_verification_test.go b/routers/private/hook_verification_test.go index f6c2e1087f..8653e34daa 100644 --- a/routers/private/hook_verification_test.go +++ b/routers/private/hook_verification_test.go @@ -18,7 +18,9 @@ func TestVerifyCommits(t *testing.T) { unittest.PrepareTestEnv(t) gitRepo, err := git.OpenRepository(t.Context(), testReposDir+"repo1_hook_verification") - defer gitRepo.Close() + if err != nil { + defer gitRepo.Close() + } assert.NoError(t, err) objectFormat, err := gitRepo.GetObjectFormat() diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 8bde983e30..3bb50ef397 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -94,11 +94,7 @@ func Code(ctx *context.Context) { loadRepoIDs := make([]int64, 0, len(searchResults)) for _, result := range searchResults { - var find bool - if slices.Contains(loadRepoIDs, result.RepoID) { - find = true - } - if !find { + if !slices.Contains(loadRepoIDs, result.RepoID) { loadRepoIDs = append(loadRepoIDs, result.RepoID) } } diff --git a/routers/web/user/code.go b/routers/web/user/code.go index 20ab1405dd..11579c40a6 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -87,11 +87,7 @@ func CodeSearch(ctx *context.Context) { loadRepoIDs := make([]int64, 0, len(searchResults)) for _, result := range searchResults { - var find bool - if slices.Contains(loadRepoIDs, result.RepoID) { - find = true - } - if !find { + if !slices.Contains(loadRepoIDs, result.RepoID) { loadRepoIDs = append(loadRepoIDs, result.RepoID) } } diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 8c85fc22c7..fd33a81901 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -4,6 +4,8 @@ package user import ( + gocontext "context" + "errors" "net/http" "net/url" @@ -20,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/optional" alpine_module "code.gitea.io/gitea/modules/packages/alpine" arch_module "code.gitea.io/gitea/modules/packages/arch" + container_module "code.gitea.io/gitea/modules/packages/container" debian_module "code.gitea.io/gitea/modules/packages/debian" rpm_module "code.gitea.io/gitea/modules/packages/rpm" "code.gitea.io/gitea/modules/setting" @@ -31,6 +34,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" packages_service "code.gitea.io/gitea/services/packages" + container_service "code.gitea.io/gitea/services/packages/container" ) const ( @@ -162,6 +166,24 @@ func RedirectToLastVersion(ctx *context.Context) { ctx.Redirect(pd.VersionWebLink()) } +func viewPackageContainerImage(ctx gocontext.Context, pd *packages_model.PackageDescriptor, digest string) (*container_module.Metadata, error) { + manifestBlob, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{ + OwnerID: pd.Owner.ID, + Image: pd.Package.LowerName, + Digest: digest, + }) + if err != nil { + return nil, err + } + manifestReader, err := packages_service.OpenBlobStream(manifestBlob.Blob) + if err != nil { + return nil, err + } + defer manifestReader.Close() + _, _, metadata, err := container_service.ParseManifestMetadata(ctx, manifestReader, pd.Owner.ID, pd.Package.LowerName) + return metadata, err +} + // ViewPackageVersion displays a single package version func ViewPackageVersion(ctx *context.Context) { if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { @@ -169,6 +191,7 @@ func ViewPackageVersion(ctx *context.Context) { return } + versionSub := ctx.PathParam("version_sub") pd := ctx.Package.Descriptor ctx.Data["Title"] = pd.Package.Name ctx.Data["IsPackagesPage"] = true @@ -180,6 +203,9 @@ func ViewPackageVersion(ctx *context.Context) { } ctx.Data["PackageRegistryHost"] = registryHostURL.Host + var pvs []*packages_model.PackageVersion + pvsTotal := int64(0) + switch pd.Package.Type { case packages_model.TypeAlpine: branches := make(container.Set[string]) @@ -257,21 +283,26 @@ func ViewPackageVersion(ctx *context.Context) { ctx.Data["Groups"] = util.Sorted(groups.Values()) ctx.Data["Architectures"] = util.Sorted(architectures.Values()) - } - - var ( - total int64 - pvs []*packages_model.PackageVersion - ) - switch pd.Package.Type { case packages_model.TypeContainer: - pvs, total, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{ + imageMetadata := pd.Metadata + if versionSub != "" { + imageMetadata, err = viewPackageContainerImage(ctx, pd, versionSub) + if errors.Is(err, util.ErrNotExist) { + ctx.NotFound(nil) + return + } else if err != nil { + ctx.ServerError("viewPackageContainerImage", err) + return + } + } + ctx.Data["ContainerImageMetadata"] = imageMetadata + pvs, pvsTotal, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{ Paginator: db.NewAbsoluteListOptions(0, 5), PackageID: pd.Package.ID, IsTagged: true, }) default: - pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + pvs, pvsTotal, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ Paginator: db.NewAbsoluteListOptions(0, 5), PackageID: pd.Package.ID, IsInternal: optional.Some(false), @@ -283,7 +314,7 @@ func ViewPackageVersion(ctx *context.Context) { } ctx.Data["LatestVersions"] = pvs - ctx.Data["TotalVersionCount"] = total + ctx.Data["TotalVersionCount"] = pvsTotal ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin() @@ -482,9 +513,9 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.OpenFileForDownload(ctx, pf) if err != nil { - ctx.ServerError("GetPackageFileStream", err) + ctx.ServerError("OpenFileForDownload", err) return } diff --git a/routers/web/web.go b/routers/web/web.go index 5eba29c601..a54f96ec68 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1012,6 +1012,7 @@ func registerWebRoutes(m *web.Router) { m.Get("/versions", user.ListPackageVersions) m.Group("/{version}", func() { m.Get("", user.ViewPackageVersion) + m.Get("/{version_sub}", user.ViewPackageVersion) m.Get("/files/{fileid}", user.DownloadPackageFile) m.Group("/settings", func() { m.Get("", user.PackageSettings) diff --git a/services/packages/arch/vercmp.go b/services/packages/arch/vercmp.go index 6dcd0df419..d44aa530f0 100644 --- a/services/packages/arch/vercmp.go +++ b/services/packages/arch/vercmp.go @@ -35,7 +35,7 @@ func parseEVR(evr string) (epoch, version, release string) { func compareSegments(a, b []string) int { lenA, lenB := len(a), len(b) l := min(lenA, lenB) - for i := 0; i < l; i++ { + for i := range l { if r := compare(a[i], b[i]); r != 0 { return r } diff --git a/services/packages/container/common.go b/services/packages/container/common.go index 5a14ed5b7a..71e8b86fcd 100644 --- a/services/packages/container/common.go +++ b/services/packages/container/common.go @@ -5,11 +5,17 @@ package container import ( "context" + "io" "strings" packages_model "code.gitea.io/gitea/models/packages" + container_service "code.gitea.io/gitea/models/packages/container" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/packages" container_module "code.gitea.io/gitea/modules/packages/container" + + "github.com/opencontainers/image-spec/specs-go/v1" ) // UpdateRepositoryNames updates the repository name property for all packages of the specific owner @@ -22,7 +28,7 @@ func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwner newOwnerName = strings.ToLower(newOwnerName) for _, p := range ps { - if err := packages_model.DeletePropertyByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil { + if err := packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil { return err } @@ -33,3 +39,26 @@ func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwner return nil } + +func ParseManifestMetadata(ctx context.Context, rd io.Reader, ownerID int64, imageName string) (*v1.Manifest, *packages_model.PackageFileDescriptor, *container_module.Metadata, error) { + var manifest v1.Manifest + if err := json.NewDecoder(rd).Decode(&manifest); err != nil { + return nil, nil, nil, err + } + configDescriptor, err := container_service.GetContainerBlob(ctx, &container_service.BlobSearchOptions{ + OwnerID: ownerID, + Image: imageName, + Digest: string(manifest.Config.Digest), + }) + if err != nil { + return nil, nil, nil, err + } + + configReader, err := packages.NewContentStore().OpenBlob(packages.BlobHash256Key(configDescriptor.Blob.HashSHA256)) + if err != nil { + return nil, nil, nil, err + } + defer configReader.Close() + metadata, err := container_module.ParseImageConfig(manifest.Config.MediaType, configReader) + return &manifest, configDescriptor, metadata, err +} diff --git a/services/packages/packages.go b/services/packages/packages.go index bd1d460fd3..517334cbc7 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -563,8 +563,8 @@ func DeletePackageFile(ctx context.Context, pf *packages_model.PackageFile) erro return packages_model.DeleteFileByID(ctx, pf.ID) } -// GetFileStreamByPackageNameAndVersion returns the content of the specific package file -func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { +// OpenFileForDownloadByPackageNameAndVersion returns the content of the specific package file and increases the download counter. +func OpenFileForDownloadByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { log.Trace("Getting package file stream: %v, %v, %s, %s, %s, %s", pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version, pfi.Filename, pfi.CompositeKey) pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version) @@ -576,32 +576,38 @@ func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, return nil, nil, nil, err } - return GetFileStreamByPackageVersion(ctx, pv, pfi) + return OpenFileForDownloadByPackageVersion(ctx, pv, pfi) } -// GetFileStreamByPackageVersion returns the content of the specific package file -func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { +// OpenFileForDownloadByPackageVersion returns the content of the specific package file and increases the download counter. +func OpenFileForDownloadByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, pfi.Filename, pfi.CompositeKey) if err != nil { return nil, nil, nil, err } - return GetPackageFileStream(ctx, pf) + return OpenFileForDownload(ctx, pf) } -// GetPackageFileStream returns the content of the specific package file -func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { +// OpenFileForDownload returns the content of the specific package file and increases the download counter. +func OpenFileForDownload(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { pb, err := packages_model.GetBlobByID(ctx, pf.BlobID) if err != nil { return nil, nil, nil, err } - return GetPackageBlobStream(ctx, pf, pb, nil) + return OpenBlobForDownload(ctx, pf, pb, nil) } -// GetPackageBlobStream returns the content of the specific package blob +func OpenBlobStream(pb *packages_model.PackageBlob) (io.ReadSeekCloser, error) { + cs := packages_module.NewContentStore() + key := packages_module.BlobHash256Key(pb.HashSHA256) + return cs.OpenBlob(key) +} + +// OpenBlobForDownload returns the content of the specific package blob and increases the download counter. // If the storage supports direct serving and it's enabled, only the direct serving url is returned. -func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { +func OpenBlobForDownload(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { key := packages_module.BlobHash256Key(pb.HashSHA256) cs := packages_module.NewContentStore() @@ -617,7 +623,7 @@ func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, p } } if u == nil { - s, err = cs.Get(key) + s, err = cs.OpenBlob(key) } if err == nil { diff --git a/templates/package/content/container.tmpl b/templates/package/content/container.tmpl index b4e12cf26b..1a1335aaa6 100644 --- a/templates/package/content/container.tmpl +++ b/templates/package/content/container.tmpl @@ -49,7 +49,11 @@ {{/* "unknown/unknown" is attestation-manifest, so we should skip it */}} {{if ne .Platform "unknown/unknown"}} <tr> - <td><a class="tw-font-mono" href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{StringUtils.TrimPrefix .Digest "sha256:" | ShortSha}}</a></td> + <td> + <a class="tw-font-mono" href="{{$.PackageDescriptor.PackageWebLink}}/{{$.PackageDescriptor.Version.LowerVersion}}/{{PathEscape .Digest}}"> + {{StringUtils.TrimPrefix .Digest "sha256:" | ShortSha}} + </a> + </td> <td>{{.Platform}}</td> <td>{{FileSize .Size}}</td> </tr> @@ -65,12 +69,24 @@ {{.PackageDescriptor.Metadata.Description}} </div> {{end}} - {{if .PackageDescriptor.Metadata.ImageLayers}} - <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.container.layers"}}</h4> + + {{/* a container manifest may contain sub manifests, so here we try to display some information of the sub manifest, + not perfect, just better than before */}} + {{$imageMetadata := .ContainerImageMetadata}} + {{if $imageMetadata.ImageLayers}} + <h4 class="ui top attached header flex-text-block"> + {{ctx.Locale.Tr "packages.container.layers"}} + {{/* only show the platform if the image metadata is not the package's, which means that it is a sub manifest */}} + {{if ne .ContainerImageMetadata .PackageDescriptor.Metadata}} + <span class="tw-text-sm flex-text-inline" title="{{ctx.Locale.Tr "packages.container.details.platform"}}"> + ({{svg "octicon-cpu" 12}} {{.ContainerImageMetadata.Platform}}) + </span> + {{end}} + </h4> <div class="ui attached segment tw-break-anywhere"> - <table class="ui very basic compact table"> + <table class="ui very basic compact table tw-font-mono"> <tbody> - {{range .PackageDescriptor.Metadata.ImageLayers}} + {{range $imageMetadata.ImageLayers}} <tr> <td>{{.}}</td> </tr> @@ -79,10 +95,10 @@ </table> </div> {{end}} - {{if .PackageDescriptor.Metadata.Labels}} + {{if $imageMetadata.Labels}} <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.container.labels"}}</h4> <div class="ui attached segment"> - <table class="ui very basic compact table"> + <table class="ui very basic compact table tw-font-mono"> <thead> <tr> <th>{{ctx.Locale.Tr "packages.container.labels.key"}}</th> @@ -90,7 +106,7 @@ </tr> </thead> <tbody> - {{range $key, $value := .PackageDescriptor.Metadata.Labels}} + {{range $key, $value := $imageMetadata.Labels}} <tr> <td class="tw-align-top">{{$key}}</td> <td class="tw-break-anywhere">{{$value}}</td> diff --git a/templates/package/content/pypi.tmpl b/templates/package/content/pypi.tmpl index 2a22a6ed71..2625c160fe 100644 --- a/templates/package/content/pypi.tmpl +++ b/templates/package/content/pypi.tmpl @@ -4,7 +4,7 @@ <div class="ui form"> <div class="field"> <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.pypi.install"}}</label> - <div class="markup"><pre class="code-block"><code>pip install --index-url <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></origin-url> {{.PackageDescriptor.Package.Name}}</code></pre></div> + <div class="markup"><pre class="code-block"><code>pip install --index-url <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></origin-url> --extra-index-url https://pypi.org/ {{.PackageDescriptor.Package.Name}}</code></pre></div> </div> <div class="field"> <label>{{ctx.Locale.Tr "packages.registry.documentation" "PyPI" "https://docs.gitea.com/usage/packages/pypi/"}}</label> diff --git a/templates/package/shared/view.tmpl b/templates/package/shared/view.tmpl index 713e1bbfc5..52673accf9 100644 --- a/templates/package/shared/view.tmpl +++ b/templates/package/shared/view.tmpl @@ -1,4 +1,5 @@ <div class="issue-title-header"> + {{$packageVersionLink := print $.PackageDescriptor.PackageWebLink "/" (PathEscape .PackageDescriptor.Version.LowerVersion)}} <h1>{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</h1> <div> {{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}} @@ -9,8 +10,8 @@ {{end}} </div> </div> -<div class="issue-content"> - <div class="issue-content-left"> +<div class="packages-content"> + <div class="packages-content-left"> {{template "package/content/alpine" .}} {{template "package/content/arch" .}} {{template "package/content/cargo" .}} @@ -34,7 +35,7 @@ {{template "package/content/swift" .}} {{template "package/content/vagrant" .}} </div> - <div class="issue-content-right ui segment"> + <div class="ui segment packages-content-right"> <strong>{{ctx.Locale.Tr "packages.details"}}</strong> <div class="ui relaxed list flex-items-block"> <div class="item">{{svg .PackageDescriptor.Package.Type.SVGName}} {{.PackageDescriptor.Package.Type.Name}}</div> @@ -74,8 +75,8 @@ <div class="ui relaxed list"> {{range .PackageDescriptor.Files}} <div class="item"> - <a href="{{$.Link}}/files/{{.File.ID}}">{{.File.Name}}</a> - <span class="text small file-size">{{FileSize .Blob.Size}}</span> + <a href="{{$packageVersionLink}}/files/{{.File.ID}}">{{.File.Name}}</a> + <span class="text small tw-whitespace-nowrap">{{FileSize .Blob.Size}}</span> </div> {{end}} </div> @@ -98,7 +99,7 @@ <div class="item">{{svg "octicon-issue-opened"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div> {{end}} {{if .CanWritePackages}} - <div class="item">{{svg "octicon-tools"}} <a href="{{.Link}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div> + <div class="item">{{svg "octicon-tools"}} <a href="{{$packageVersionLink}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div> {{end}} </div> {{end}} diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index ce9c33c049..370c90a100 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -267,9 +267,7 @@ func TestAPISearchIssues(t *testing.T) { defer tests.PrepareTestEnv(t)() // as this API was used in the frontend, it uses UI page size - expectedIssueCount := min( - // from the fixtures - 20, setting.UI.IssuePagingNum) + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures link, _ := url.Parse("/api/v1/repos/issues/search") token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue) @@ -370,9 +368,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) { defer tests.PrepareTestEnv(t)() // as this API was used in the frontend, it uses UI page size - expectedIssueCount := min( - // from the fixtures - 20, setting.UI.IssuePagingNum) + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures link, _ := url.Parse("/api/v1/repos/issues/search") token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue) diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index 90bf1c3d21..8ae33dc35c 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -562,8 +562,7 @@ func TestPackageContainer(t *testing.T) { assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository)) assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged)) - // only the last manifest digest is associated with the version (OCI builders will push the index manifest digest as the final step) - assert.ElementsMatch(t, []string{untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference)) + assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference)) assert.IsType(t, &container_module.Metadata{}, pd.Metadata) metadata := pd.Metadata.(*container_module.Metadata) diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 7b803cd54d..aff8aeda83 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -488,9 +488,7 @@ func TestSearchIssues(t *testing.T) { session := loginUser(t, "user2") - expectedIssueCount := min( - // from the fixtures - 20, setting.UI.IssuePagingNum) + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures link, _ := url.Parse("/issues/search") req := NewRequest(t, "GET", link.String()) @@ -581,9 +579,7 @@ func TestSearchIssues(t *testing.T) { func TestSearchIssuesWithLabels(t *testing.T) { defer tests.PrepareTestEnv(t)() - expectedIssueCount := min( - // from the fixtures - 20, setting.UI.IssuePagingNum) + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures session := loginUser(t, "user1") link, _ := url.Parse("/issues/search") diff --git a/tools/lint-go-gopls.sh b/tools/lint-go-gopls.sh index a222ea14d7..2cd26ca6fe 100755 --- a/tools/lint-go-gopls.sh +++ b/tools/lint-go-gopls.sh @@ -11,7 +11,7 @@ IGNORE_PATTERNS=( # current absolute path, indicating a error was found. This is necessary # because the tool does not set non-zero exit code when errors are found. # ref: https://github.com/golang/go/issues/67078 -ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}")); +ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check -severity=warning "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}")); NUM_ERRORS=$(echo -n "$ERROR_LINES" | wc -l) if [ "$NUM_ERRORS" -eq "0" ]; then diff --git a/web_src/css/index.css b/web_src/css/index.css index de15c5c69c..291cd04b2b 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -70,6 +70,7 @@ @import "./repo/reactions.css"; @import "./repo/clone.css"; @import "./repo/commit-sign.css"; +@import "./repo/packages.css"; @import "./editor/fileeditor.css"; @import "./editor/combomarkdowneditor.css"; diff --git a/web_src/css/repo.css b/web_src/css/repo.css index f7ede5bf92..cbc890e356 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1227,10 +1227,6 @@ td .commit-summary { font-weight: var(--font-weight-normal); } -.repository.packages .file-size { - white-space: nowrap; -} - .repository .activity-header { display: flex; justify-content: space-between; diff --git a/web_src/css/repo/packages.css b/web_src/css/repo/packages.css new file mode 100644 index 0000000000..75675f5243 --- /dev/null +++ b/web_src/css/repo/packages.css @@ -0,0 +1,25 @@ +.packages-content { + display: flex; + align-items: flex-start; + gap: 16px; +} + +.packages-content-left { + margin: 0 !important; + width: calc(100% - 250px - 16px); +} + +.packages-content-right { + margin: 0 !important; + width: 250px; +} + +@media (max-width: 767.98px) { + .packages-content { + flex-direction: column; + } + .packages-content-left, + .packages-content-right { + width: 100%; + } +} |