From 07ec8288bff6295c7db5558846153a61625af80d Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 13 Jul 2022 17:39:19 +0000 Subject: [PATCH 1/9] Add missing return for when topic isn't found (#20351) Add missing return to DeleteTopic API when the topic is not found. --- routers/api/v1/repo/topic.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go index 1cc2c50dc20d..64dc763dc381 100644 --- a/routers/api/v1/repo/topic.go +++ b/routers/api/v1/repo/topic.go @@ -240,6 +240,7 @@ func DeleteTopic(ctx *context.APIContext) { if topic == nil { ctx.NotFound() + return } ctx.Status(http.StatusNoContent) From fe09ee564dc90b261beb7b34801b3176c89483a5 Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 13 Jul 2022 23:24:29 +0100 Subject: [PATCH 2/9] Prevent context deadline error propagation in GetCommitsInfo (#20346) * Prevent context deadline error propagation in GetCommitsInfo Although `WalkGitLog` tries to test for `context.DeadlineExceededErr` there is a small chance that the error will propagate to the reader before it is recognised. This will cause the error to propagate up to `renderDirectoryFiles` and cause a http status 500. Here we check that the error passed is a `DeadlineExceededErr` via error.Is Fix #20329 Signed-off-by: Andrew Thornton --- modules/git/log_name_status.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index ffd0a0991bf4..e1e117ff4b84 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "context" + "errors" "io" "path" "sort" @@ -62,9 +63,10 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p }) if err != nil { _ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) - } else { - _ = stdoutWriter.Close() + return } + + _ = stdoutWriter.Close() }() // For simplicities sake we'll us a buffered reader to read from the cat-file --batch @@ -354,7 +356,7 @@ heaploop: } current, err := g.Next(treepath, path2idx, changed, maxpathlen) if err != nil { - if err == context.DeadlineExceeded { + if errors.Is(err, context.DeadlineExceeded) { break heaploop } g.Close() From ed094dbab913d07bed5e55c6c224e33302da1895 Mon Sep 17 00:00:00 2001 From: zeripath Date: Thu, 14 Jul 2022 00:21:35 +0000 Subject: [PATCH 3/9] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 1 - options/locale/locale_de-DE.ini | 1 - options/locale/locale_el-GR.ini | 1 - options/locale/locale_es-ES.ini | 1 - options/locale/locale_fa-IR.ini | 1 - options/locale/locale_fr-FR.ini | 1 - options/locale/locale_it-IT.ini | 1 - options/locale/locale_ja-JP.ini | 5 ++++- options/locale/locale_lv-LV.ini | 1 - options/locale/locale_nl-NL.ini | 1 - options/locale/locale_pl-PL.ini | 1 - options/locale/locale_pt-BR.ini | 1 - options/locale/locale_pt-PT.ini | 2 +- options/locale/locale_ru-RU.ini | 1 - options/locale/locale_sv-SE.ini | 1 - options/locale/locale_tr-TR.ini | 1 - options/locale/locale_uk-UA.ini | 1 - options/locale/locale_zh-CN.ini | 1 - options/locale/locale_zh-TW.ini | 1 - 19 files changed, 5 insertions(+), 19 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 8f2288569d15..4cdc4d987449 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -897,7 +897,6 @@ form.name_pattern_not_allowed=Vzor „%s“ není povolený v názvu repozitář need_auth=Ověření migrate_options=Možnosti migrace migrate_service=Migrační služba -migrate_options_mirror_helper=Tento repozitář bude zrcadlem migrate_options_lfs=Migrovat LFS soubory migrate_options_lfs_endpoint.label=Koncový bod LFS migrate_options_lfs_endpoint.description=Migrace se pokusí použít váš vzdálený Git pro určení LFS serveru. Můžete také zadat vlastní koncový bod, pokud jsou data LFS repozitáře uložena někde jinde. diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index b33ad8871f79..496c4e6d9899 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -930,7 +930,6 @@ form.name_pattern_not_allowed='%s' ist nicht erlaubt für Repository-Namen. need_auth=Authentifizierung migrate_options=Migrationsoptionen migrate_service=Migrationsdienst -migrate_options_mirror_helper=Dieses Repository wird ein Mirror sein migrate_options_lfs=LFS-Dateien migrieren migrate_options_lfs_endpoint.label=LFS-Endpunkt migrate_options_lfs_endpoint.description=Migration wird versuchen, über den entfernten Git-Server den LFS-Server zu bestimmen. Du kannst auch einen eigenen Endpunkt angeben, wenn die LFS-Dateien woanders gespeichert werden. diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index dd4dea176092..21b2d4a5b3c6 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -930,7 +930,6 @@ form.name_pattern_not_allowed=Το μοτίβο '%s' δεν επιτρέπετα need_auth=Εξουσιοδότηση migrate_options=Επιλογές Μεταφοράς migrate_service=Υπηρεσία Μεταφοράς -migrate_options_mirror_helper=Αυτό το αποθετήριο θα είναι ένα είδωλο migrate_options_lfs=Μεταφορά αρχείων LFS migrate_options_lfs_endpoint.label=LFS Endpoint migrate_options_lfs_endpoint.description=Η μεταφορά θα προσπαθήσει να χρησιμοποιήσει το Git remote για να καθορίσει τον διακομιστή LFS. Μπορείτε επίσης να καθορίσετε ένα δικό σας endpoint αν τα δεδομένα LFS του αποθετηρίου αποθηκεύονται κάπου αλλού. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 2a1d2d3f7eb0..724d35467044 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -932,7 +932,6 @@ form.name_pattern_not_allowed=El patrón '%s' no está permitido en un nombre de need_auth=Autorización migrate_options=Opciones de migración migrate_service=Servicio de Migración -migrate_options_mirror_helper=Este repositorio será uno replicado migrate_options_lfs=Migrar archivos LFS migrate_options_lfs_endpoint.label=Punto final de LFS migrate_options_lfs_endpoint.description=Migración intentará usar su mando Git para determinar el servidor LFS. También puede especificar un punto final personalizado si los datos LFS del repositorio se almacenan en otro lugar. diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index dded0bbb45b2..c7488cf0e91a 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -863,7 +863,6 @@ form.name_pattern_not_allowed=الگوی %s در نام مخزن مجاز نیس need_auth=دسترسی migrate_options=تنظیمات مهاجرت migrate_service=سرویس مهاجرت -migrate_options_mirror_helper=این مخزن یک آینه خواهد بود migrate_options_lfs=مهاجرت فایلهای LFS migrate_options_lfs_endpoint.label=نشانهای پایانی LFS migrate_options_lfs_endpoint.description=Migration سعی خواهد کرد از کنترل از راه دور Git شما برای تعیین سرور LFS استفاده کند. همچنین اگر داده های LFS مخزن در جای دیگری ذخیره شده باشد، می توانید یک نقطه پایانی سفارشی را مشخص کنید. diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index ff1af0433069..396ad2900b93 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -867,7 +867,6 @@ form.name_pattern_not_allowed="%s" n'est pas autorisé dans un nom de dépôt. need_auth=Autorisation migrate_options=Options de migration migrate_service=Service de migration -migrate_options_mirror_helper=Ce dépôt sera un miroir migrate_options_lfs=Migrer les fichiers LFS migrate_options_lfs_endpoint.label=Point d'accès LFS migrate_options_lfs_endpoint.description=La migration va tenter d'utiliser votre dépôt Git distant pour déterminer le serveur LFS. Vous pouvez également spécifier un point d'accès personnalisé si les données LFS du dépôt sont stockées ailleurs. diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 99f9be14e2fc..7087416f91b9 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -736,7 +736,6 @@ form.name_pattern_not_allowed=Il modello '%s' non è consentito come nome di un migrate_options=Opzioni di migrazione migrate_service=Servizio migrazione -migrate_options_mirror_helper=Questo repository sarà un mirror migrate_items=Elementi di migrazione migrate_items_wiki=Wiki migrate_items_milestones=Milestone diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 746df9233d1c..748715f4364c 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -861,7 +861,9 @@ default_branch=デフォルトブランチ default_branch_helper=デフォルトブランチはプルリクエストとコードコミットのベースブランチとなります。 mirror_prune=Prune mirror_prune_desc=不要になった古いリモートトラッキング参照を削除 +mirror_interval=ミラー間隔 (有効な時間の単位は'h'、'm'、's')。 定期的な同期を無効にする場合は0。(最小間隔: %s) mirror_interval_invalid=ミラー間隔が不正です。 +mirror_sync_on_commit=コミットがプッシュされたときに同期 mirror_address=クローンするURL mirror_address_desc=必要な資格情報は「認証」セクションに設定してください。 mirror_address_url_invalid=入力したURLは無効です。 URLの構成要素はすべて正しくエスケープする必要があります。 @@ -930,7 +932,7 @@ form.name_pattern_not_allowed='%s' の形式はリポジトリ名に使用でき need_auth=認証 migrate_options=移行オプション migrate_service=移行するサービス -migrate_options_mirror_helper=このリポジトリをミラーにする +migrate_options_mirror_helper=このリポジトリをミラーにする migrate_options_lfs=LFS ファイルのマイグレート migrate_options_lfs_endpoint.label=LFS エンドポイント migrate_options_lfs_endpoint.description=マイグレーションでは、リモート側のGitをもとにLFSサーバーを決定しようとします。 リポジトリのLFSデータがほかの場所に保存されている場合は、独自のエンドポイントを指定することができます。 @@ -1301,6 +1303,7 @@ issues.previous=前ページ issues.next=次ページ issues.open_title=オープン issues.closed_title=クローズ +issues.draft_title=ドラフト issues.num_comments=%d件のコメント issues.commented_at=`が %s にコメント` issues.delete_comment_confirm=このコメントを削除してよろしいですか? diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 281db8998026..b87202e2b670 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -930,7 +930,6 @@ form.name_pattern_not_allowed=Repozitorija nosaukums '%s' nav atļauts. need_auth=Autorizācija migrate_options=Migrācijas opcijas migrate_service=Migrācijas serviss -migrate_options_mirror_helper=Šis repozitorijs būs spogulis migrate_options_lfs=Migrēt LFS failus migrate_options_lfs_endpoint.label=LFS galapunkts migrate_options_lfs_endpoint.description=Migrācija mēģinās izmantot attālināto URL, lai noteiktu LFS serveri. Var norādīt arī citu galapunktu, ja repozitorija LFS dati ir izvietoti citā vietā. diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index f3fc3ae33562..ccdf97880089 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -754,7 +754,6 @@ form.name_pattern_not_allowed=Het patroon '%s' is niet toegestaan in de naam van migrate_options=Migratie opties migrate_service=Migratie Service -migrate_options_mirror_helper=Deze repository zal een kopie zijn migrate_items=Migratie Items migrate_items_wiki=Wiki migrate_items_milestones=Mijlpalen diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 0d9a596e3928..3b65957bf5a9 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -870,7 +870,6 @@ form.name_pattern_not_allowed=Wzór "%s" nie jest dozwolony w nazwie repozytoriu need_auth=Autoryzacja migrate_options=Opcje migracji migrate_service=Usługa migracji -migrate_options_mirror_helper=To repozytorium będzie kopią lustrzaną migrate_options_lfs=Migruj pliki LFS migrate_options_lfs_endpoint.label=Punkt końcowy LFS migrate_options_lfs_endpoint.description=Migracja spróbuje użyć Git remote, aby określić serwer LFS. Możesz również określić niestandardowy punkt końcowy, jeśli dane repozytorium LFS są przechowywane gdzieś indziej. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index a91847b68a24..3bf98901acf4 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -930,7 +930,6 @@ form.name_pattern_not_allowed=O padrão de '%s' não é permitido em um nome de need_auth=Autorização migrate_options=Opções de Migração migrate_service=Serviço de Migração -migrate_options_mirror_helper=Este repositório será um espelho migrate_options_lfs=Migrar arquivos LFS migrate_options_lfs_endpoint.label=Destino LFS migrate_options_lfs_endpoint.description=A migração tentará usar seu controle remoto Git para determinar o servidor LFS. Você também pode especificar um destino personalizado se os dados do repositório LFS forem armazenados em outro lugar. diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index b44b383548e2..176537129ccc 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -932,7 +932,7 @@ form.name_pattern_not_allowed=O padrão '%s' não é permitido no nome de um rep need_auth=Autorização migrate_options=Opções de migração migrate_service=Serviço de migração -migrate_options_mirror_helper=Este repositório irá ser uma réplica +migrate_options_mirror_helper=Este repositório irá ser uma réplica migrate_options_lfs=Migrar ficheiros LFS migrate_options_lfs_endpoint.label=Destino LFS migrate_options_lfs_endpoint.description=A migração irá tentar usar o seu controlo remoto do Git para determinar o servidor LFS. Também pode especificar um destino personalizado se os dados do repositório LFS forem armazenados noutro lugar. diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 28c8a01fa241..555959b5124c 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -918,7 +918,6 @@ form.name_pattern_not_allowed=Шаблон имени репозитория '%s need_auth=Авторизация migrate_options=Параметры миграции migrate_service=Сервис миграции -migrate_options_mirror_helper=Этот репозиторий будет зеркалом migrate_options_lfs=Перенос LFS файлов migrate_options_lfs_endpoint.label=LFS Endpoint migrate_options_lfs_endpoint.description=Миграция попытается использовать ваш Git удаленно, чтобы определить сервер LFS. Вы также можете указать пользовательскую конечную точку, если данные хранятся в другом месте. diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index b82b0d9f65b9..e8458136b50d 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -700,7 +700,6 @@ form.name_pattern_not_allowed=Mönstret '%s' är otillåtet i ett utvecklingskat migrate_options=Migrationsalternativ migrate_service=Migreringstjänst -migrate_options_mirror_helper=Denna utvecklingskatalog kommer att vara en spegel migrate_items=Migrationsobjekt migrate_items_wiki=Wiki migrate_items_milestones=Milstenar diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 9b8a19c802a7..6d3e03c2461a 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -846,7 +846,6 @@ form.name_pattern_not_allowed='%s' deseni, depo adı için geçerli değildir. need_auth=Yetkilendirme migrate_options=Göç Seçenekleri migrate_service=Göç Hizmeti -migrate_options_mirror_helper=Bu depo bir yansı olacaktır migrate_options_lfs=LFS dosyalarını taşı migrate_options_lfs_endpoint.label=LFS Uç Noktası migrate_options_lfs_endpoint.description=Taşıma, LFS sunucusunu belirlemek için Git uzak sunucusunu kullanmaya çalışacak. Eğer LFS veri deposu başka yerdeyse özel bir uç nokta da belirtebilirsiniz. diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index f5cf9e93d09f..460ab2192cc2 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -870,7 +870,6 @@ form.name_pattern_not_allowed=Шаблон '%s' не дозволено в на need_auth=Авторизація migrate_options=Параметри міграції migrate_service=Сервіс міграції -migrate_options_mirror_helper=Цей репозиторій буде дзеркалом migrate_options_lfs=Перенесення LFS файлів migrate_options_lfs_endpoint.label=Кінцева точка LFS migrate_options_lfs_endpoint.description=Міграція буде намагатися використовувати ваш Git віддалено, щоб визначати LFS сервер. Ви також можете вказати свою кінцеву точку, якщо дані репозиторію LFS зберігаються в іншому місці. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index afb408f41cd7..eec7c0ba596e 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -932,7 +932,6 @@ form.name_pattern_not_allowed=仓库名称中不允许使用模式 "%s"。 need_auth=授权 migrate_options=迁移选项 migrate_service=迁移服务 -migrate_options_mirror_helper=该仓库将是一个 镜像 migrate_options_lfs=迁移 LFS 文件 migrate_options_lfs_endpoint.label=LFS 网址 migrate_options_lfs_endpoint.description=迁移将尝试使用你的 Git remote 来 确定 LFS 服务器。如果仓库 LFS 数据存储在其他位置,你还可以指定自定义网址。 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index d84267fe66e7..3dcd5ecd0401 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -930,7 +930,6 @@ form.name_pattern_not_allowed=儲存庫名稱不可包含字元「%s」。 need_auth=授權 migrate_options=遷移選項 migrate_service=遷移服務 -migrate_options_mirror_helper=將此儲存庫設定為鏡像儲存庫 migrate_options_lfs=遷移 LFS 檔案 migrate_options_lfs_endpoint.label=LFS 端點 migrate_options_lfs_endpoint.description=遷移將會嘗試使用您的 Git Remote 來確認 LFS 伺服器。如果存儲庫的 LFS 資料放在其他地方,您也可以指定自訂的端點。 From 715042c5bb7485e8463a37ed87bec81ae10ad9aa Mon Sep 17 00:00:00 2001 From: Tyrone Yeh Date: Thu, 14 Jul 2022 10:09:03 +0800 Subject: [PATCH 4/9] Fix org label open count, including close count issue (#20353) Fixed using organization tags to see open issues in the tag list including closed issues count --- models/issues/label.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models/issues/label.go b/models/issues/label.go index 9ad488252ab7..667a60868743 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) @@ -107,6 +108,7 @@ func (label *Label) CalOpenOrgIssues(repoID, labelID int64) { counts, _ := CountIssuesByRepo(&IssuesOptions{ RepoID: repoID, LabelIDs: []int64{labelID}, + IsClosed: util.OptionalBoolFalse, }) for _, count := range counts { From 175705356cac06c22d13d86b31605a6ad6dd9642 Mon Sep 17 00:00:00 2001 From: Baoshuo Ren Date: Thu, 14 Jul 2022 11:03:31 +0800 Subject: [PATCH 5/9] Fix icon margin in user/settings/repos (#20281) --- templates/user/settings/repos.tmpl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl index eba0d22d5eea..aedff91de901 100644 --- a/templates/user/settings/repos.tmpl +++ b/templates/user/settings/repos.tmpl @@ -109,15 +109,15 @@
{{if .IsPrivate}} - {{svg "octicon-lock"}} + {{svg "octicon-lock" 16 "mr-2 iconFloat text gold"}} {{else if .IsFork}} - {{svg "octicon-repo-forked"}} + {{svg "octicon-repo-forked" 16 "mr-2 iconFloat"}} {{else if .IsMirror}} - {{svg "octicon-mirror"}} + {{svg "octicon-mirror" 16 "mr-2 iconFloat"}} {{else if .IsTemplate}} - {{svg "octicon-repo-template"}} + {{svg "octicon-repo-template" 16 "mr-2 iconFloat"}} {{else}} - {{svg "octicon-repo"}} + {{svg "octicon-repo" 16 "mr-2 iconFloat"}} {{end}} {{.OwnerName}}/{{.Name}} {{FileSize .Size}} From bffa30302070b594a1c40cdc56264b9731036fb3 Mon Sep 17 00:00:00 2001 From: zeripath Date: Thu, 14 Jul 2022 08:22:09 +0100 Subject: [PATCH 6/9] Add option to purge users (#18064) Add the ability to purge users when deleting them. Close #15588 Signed-off-by: Andrew Thornton --- cmd/admin.go | 6 +- integrations/admin_user_test.go | 2 +- integrations/integration_test.go | 9 ++- models/packages/package_version.go | 9 ++- models/project/project.go | 37 +++++++++ models/repo.go | 12 +-- models/user.go | 6 +- options/locale/locale_en-US.ini | 2 + routers/api/v1/admin/user.go | 2 +- routers/web/admin/users.go | 22 ++--- routers/web/user/setting/account.go | 2 +- services/packages/container/cleanup.go | 2 +- services/packages/packages.go | 28 +++++++ services/user/user.go | 106 ++++++++++++++++++++++++- services/user/user_test.go | 10 +-- templates/admin/user/edit.tmpl | 17 +++- 16 files changed, 221 insertions(+), 51 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index 32f9a95a6658..524cc3056388 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -157,6 +157,10 @@ var ( Name: "email,e", Usage: "Email of the user to delete", }, + cli.BoolFlag{ + Name: "purge", + Usage: "Purge user, all their repositories, organizations and comments", + }, }, Action: runDeleteUser, } @@ -675,7 +679,7 @@ func runDeleteUser(c *cli.Context) error { return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id")) } - return user_service.DeleteUser(user) + return user_service.DeleteUser(ctx, user, c.Bool("purge")) } func runGenerateAccessToken(c *cli.Context) error { diff --git a/integrations/admin_user_test.go b/integrations/admin_user_test.go index 59adac7ecc65..a2020652b747 100644 --- a/integrations/admin_user_test.go +++ b/integrations/admin_user_test.go @@ -76,7 +76,7 @@ func TestAdminDeleteUser(t *testing.T) { req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{ "_csrf": csrf, }) - session.MakeRequest(t, req, http.StatusOK) + session.MakeRequest(t, req, http.StatusSeeOther) assertUserDeleted(t, 8) unittest.CheckConsistencyFor(t, &user_model.User{}) diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 8a43de7c45fa..230f780175c9 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -188,8 +188,13 @@ func initIntegrationTest() { switch { case setting.Database.UseMySQL: - db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/", - setting.Database.User, setting.Database.Passwd, setting.Database.Host)) + connType := "tcp" + if len(setting.Database.Host) > 0 && setting.Database.Host[0] == '/' { // looks like a unix socket + connType = "unix" + } + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@%s(%s)/", + setting.Database.User, setting.Database.Passwd, connType, setting.Database.Host)) defer db.Close() if err != nil { log.Fatal("sql.Open: %v", err) diff --git a/models/packages/package_version.go b/models/packages/package_version.go index 583f832e5eec..83c2fdb67489 100644 --- a/models/packages/package_version.go +++ b/models/packages/package_version.go @@ -107,7 +107,7 @@ func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType ExactMatch: true, Value: version, }, - IsInternal: isInternal, + IsInternal: util.OptionalBoolOf(isInternal), Paginator: db.NewAbsoluteListOptions(0, 1), }) if err != nil { @@ -171,7 +171,7 @@ type PackageSearchOptions struct { Name SearchValue // only results with the specific name are found Version SearchValue // only results with the specific version are found Properties map[string]string // only results are found which contain all listed version properties with the specific value - IsInternal bool + IsInternal util.OptionalBool HasFileWithName string // only results are found which are associated with a file with the specific name HasFiles util.OptionalBool // only results are found which have associated files Sort string @@ -179,7 +179,10 @@ type PackageSearchOptions struct { } func (opts *PackageSearchOptions) toConds() builder.Cond { - var cond builder.Cond = builder.Eq{"package_version.is_internal": opts.IsInternal} + cond := builder.NewCond() + if !opts.IsInternal.IsNone() { + cond = builder.Eq{"package_version.is_internal": opts.IsInternal.IsTrue()} + } if opts.OwnerID != 0 { cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID}) diff --git a/models/project/project.go b/models/project/project.go index 0aa37cc5c907..86a77947d883 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -330,3 +330,40 @@ func DeleteProjectByIDCtx(ctx context.Context, id int64) error { return updateRepositoryProjectCount(ctx, p.RepoID) } + +func DeleteProjectByRepoIDCtx(ctx context.Context, repoID int64) error { + switch { + case setting.Database.UseSQLite3: + if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_issue WHERE project_issue.id IN (SELECT project_issue.id FROM project_issue INNER JOIN project WHERE project.id = project_issue.project_id AND project.repo_id = ?)", repoID); err != nil { + return err + } + if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_board WHERE project_board.id IN (SELECT project_board.id FROM project_board INNER JOIN project WHERE project.id = project_board.project_id AND project.repo_id = ?)", repoID); err != nil { + return err + } + if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil { + return err + } + case setting.Database.UsePostgreSQL: + if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_issue USING project WHERE project.id = project_issue.project_id AND project.repo_id = ? ", repoID); err != nil { + return err + } + if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_board USING project WHERE project.id = project_board.project_id AND project.repo_id = ? ", repoID); err != nil { + return err + } + if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil { + return err + } + default: + if _, err := db.GetEngine(ctx).Exec("DELETE project_issue FROM project_issue INNER JOIN project ON project.id = project_issue.project_id WHERE project.repo_id = ? ", repoID); err != nil { + return err + } + if _, err := db.GetEngine(ctx).Exec("DELETE project_board FROM project_board INNER JOIN project ON project.id = project_board.project_id WHERE project.repo_id = ? ", repoID); err != nil { + return err + } + if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil { + return err + } + } + + return updateRepositoryProjectCount(ctx, repoID) +} diff --git a/models/repo.go b/models/repo.go index ca83b03e42ca..66ef51473950 100644 --- a/models/repo.go +++ b/models/repo.go @@ -342,16 +342,8 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { } } - projects, _, err := project_model.GetProjects(ctx, project_model.SearchOptions{ - RepoID: repoID, - }) - if err != nil { - return fmt.Errorf("get projects: %v", err) - } - for i := range projects { - if err := project_model.DeleteProjectByIDCtx(ctx, projects[i].ID); err != nil { - return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err) - } + if err := project_model.DeleteProjectByRepoIDCtx(ctx, repoID); err != nil { + return fmt.Errorf("unable to delete projects for repo[%d]: %v", repoID, err) } // Remove LFS objects diff --git a/models/user.go b/models/user.go index 49374014aa7d..86a714e746bb 100644 --- a/models/user.go +++ b/models/user.go @@ -27,7 +27,7 @@ import ( ) // DeleteUser deletes models associated to an user. -func DeleteUser(ctx context.Context, u *user_model.User) (err error) { +func DeleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) { e := db.GetEngine(ctx) // ***** START: Watch ***** @@ -95,8 +95,8 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) { return err } - if setting.Service.UserDeleteWithCommentsMaxTime != 0 && - u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now()) { + if purge || (setting.Service.UserDeleteWithCommentsMaxTime != 0 && + u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now())) { // Delete Comments const batchSize = 50 diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9e8a0303393b..167d2cc1f20c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2540,6 +2540,8 @@ users.delete_account = Delete User Account users.cannot_delete_self = "You cannot delete yourself" users.still_own_repo = This user still owns one or more repositories. Delete or transfer these repositories first. users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. +users.purge = Purge User +users.purge_help = Forcibly delete user and any repositories, organizations, and packages owned by the user. All comments will be deleted too. users.still_own_packages = This user still owns one or more packages. Delete these packages first. users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 71932136b1f9..1a4b02001133 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -316,7 +316,7 @@ func DeleteUser(ctx *context.APIContext) { return } - if err := user_service.DeleteUser(ctx.ContextUser); err != nil { + if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil { if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index c37ecfd71ea9..aab633ec84b2 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -419,29 +419,21 @@ func DeleteUser(ctx *context.Context) { // admin should not delete themself if u.ID == ctx.Doer.ID { ctx.Flash.Error(ctx.Tr("admin.users.cannot_delete_self")) - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")), - }) + ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid"))) return } - if err = user_service.DeleteUser(u); err != nil { + if err = user_service.DeleteUser(ctx, u, ctx.FormBool("purge")); err != nil { switch { case models.IsErrUserOwnRepos(err): ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo")) - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")), - }) + ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid"))) case models.IsErrUserHasOrgs(err): ctx.Flash.Error(ctx.Tr("admin.users.still_has_org")) - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")), - }) + ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid"))) case models.IsErrUserOwnPackages(err): ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages")) - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + ctx.Params(":userid"), - }) + ctx.Redirect(setting.AppSubURL + "/admin/users/" + ctx.Params(":userid")) default: ctx.ServerError("DeleteUser", err) } @@ -450,9 +442,7 @@ func DeleteUser(ctx *context.Context) { log.Trace("Account deleted by admin (%s): %s", ctx.Doer.Name, u.Name) ctx.Flash.Success(ctx.Tr("admin.users.deletion_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users", - }) + ctx.Redirect(setting.AppSubURL + "/admin/users") } // AvatarPost response for change user's avatar request diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index dfade13a1ce4..cdb24c606674 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -248,7 +248,7 @@ func DeleteAccount(ctx *context.Context) { return } - if err := user.DeleteUser(ctx.Doer); err != nil { + if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil { switch { case models.IsErrUserOwnRepos(err): ctx.Flash.Error(ctx.Tr("form.still_own_repo")) diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go index 390a0b7b052d..3e44f9aa1a0f 100644 --- a/services/packages/container/cleanup.go +++ b/services/packages/container/cleanup.go @@ -59,7 +59,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e ExactMatch: true, Value: container_model.UploadVersion, }, - IsInternal: true, + IsInternal: util.OptionalBoolTrue, HasFiles: util.OptionalBoolFalse, }) if err != nil { diff --git a/services/packages/packages.go b/services/packages/packages.go index 7f25fce5b85c..0ebf6e7df0cd 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" @@ -451,3 +452,30 @@ func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) ( } return s, pf, err } + +// RemoveAllPackages for User +func RemoveAllPackages(ctx context.Context, userID int64) (int, error) { + count := 0 + for { + pkgVersions, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + Paginator: &db.ListOptions{ + PageSize: repo_model.RepositoryListDefaultPageSize, + Page: 1, + }, + OwnerID: userID, + }) + if err != nil { + return count, fmt.Errorf("GetOwnedPackages[%d]: %w", userID, err) + } + if len(pkgVersions) == 0 { + break + } + for _, pv := range pkgVersions { + if err := DeletePackageVersionAndReferences(ctx, pv); err != nil { + return count, fmt.Errorf("unable to delete package %d:%s[%d]. Error: %w", pv.PackageID, pv.Version, pv.ID, err) + } + count++ + } + } + return count, nil +} diff --git a/services/user/user.go b/services/user/user.go index 4db4d7ca17f1..448b7c2daf8d 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -21,19 +21,116 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/avatar" + "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/packages" ) // DeleteUser completely and permanently deletes everything of a user, // but issues/comments/pulls will be kept and shown as someone has been deleted, // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. -func DeleteUser(u *user_model.User) error { +func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { if u.IsOrganization() { return fmt.Errorf("%s is an organization not a user", u.Name) } + if purge { + // Disable the user first + // NOTE: This is deliberately not within a transaction as it must disable the user immediately to prevent any further action by the user to be purged. + if err := user_model.UpdateUserCols(ctx, &user_model.User{ + ID: u.ID, + IsActive: false, + IsRestricted: true, + IsAdmin: false, + ProhibitLogin: true, + Passwd: "", + Salt: "", + PasswdHashAlgo: "", + MaxRepoCreation: 0, + }, "is_active", "is_restricted", "is_admin", "prohibit_login", "max_repo_creation", "passwd", "salt", "passwd_hash_algo"); err != nil { + return fmt.Errorf("unable to disable user: %s[%d] prior to purge. UpdateUserCols: %w", u.Name, u.ID, err) + } + + // Force any logged in sessions to log out + // FIXME: We also need to tell the session manager to log them out too. + eventsource.GetManager().SendMessage(u.ID, &eventsource.Event{ + Name: "logout", + }) + + // Delete all repos belonging to this user + // Now this is not within a transaction because there are internal transactions within the DeleteRepository + // BUT: the db will still be consistent even if a number of repos have already been deleted. + // And in fact we want to capture any repositories that are being created in other transactions in the meantime + // + // An alternative option here would be write a DeleteAllRepositoriesForUserID function which would delete all of the repos + // but such a function would likely get out of date + for { + repos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ + ListOptions: db.ListOptions{ + PageSize: repo_model.RepositoryListDefaultPageSize, + Page: 1, + }, + Private: true, + OwnerID: u.ID, + }) + if err != nil { + return fmt.Errorf("SearchRepositoryByName: %v", err) + } + if len(repos) == 0 { + break + } + for _, repo := range repos { + if err := models.DeleteRepository(u, u.ID, repo.ID); err != nil { + return fmt.Errorf("unable to delete repository %s for %s[%d]. Error: %v", repo.Name, u.Name, u.ID, err) + } + } + } + + // Remove from Organizations and delete last owner organizations + // Now this is not within a transaction because there are internal transactions within the DeleteOrganization + // BUT: the db will still be consistent even if a number of organizations memberships and organizations have already been deleted + // And in fact we want to capture any organization additions that are being created in other transactions in the meantime + // + // An alternative option here would be write a function which would delete all organizations but it seems + // but such a function would likely get out of date + for { + orgs, err := organization.FindOrgs(organization.FindOrgOptions{ + ListOptions: db.ListOptions{ + PageSize: repo_model.RepositoryListDefaultPageSize, + Page: 1, + }, + UserID: u.ID, + IncludePrivate: true, + }) + if err != nil { + return fmt.Errorf("unable to find org list for %s[%d]. Error: %v", u.Name, u.ID, err) + } + if len(orgs) == 0 { + break + } + for _, org := range orgs { + if err := models.RemoveOrgUser(org.ID, u.ID); err != nil { + if organization.IsErrLastOrgOwner(err) { + err = organization.DeleteOrganization(ctx, org) + } + if err != nil { + return fmt.Errorf("unable to remove user %s[%d] from org %s[%d]. Error: %v", u.Name, u.ID, org.Name, org.ID, err) + } + } + } + } + + // Delete Packages + if setting.Packages.Enabled { + if _, err := packages.RemoveAllPackages(ctx, u.ID); err != nil { + return err + } + } + } + ctx, committer, err := db.TxContext() if err != nil { return err @@ -41,7 +138,8 @@ func DeleteUser(u *user_model.User) error { defer committer.Close() // Note: A user owns any repository or belongs to any organization - // cannot perform delete operation. + // cannot perform delete operation. This causes a race with the purge above + // however consistency requires that we ensure that this is the case // Check ownership of repository. count, err := repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{OwnerID: u.ID}) @@ -66,7 +164,7 @@ func DeleteUser(u *user_model.User) error { return models.ErrUserOwnPackages{UID: u.ID} } - if err := models.DeleteUser(ctx, u); err != nil { + if err := models.DeleteUser(ctx, u, purge); err != nil { return fmt.Errorf("DeleteUser: %v", err) } @@ -117,7 +215,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { return db.ErrCancelledf("Before delete inactive user %s", u.Name) default: } - if err := DeleteUser(u); err != nil { + if err := DeleteUser(ctx, u, false); err != nil { // Ignore users that were set inactive by admin. if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { continue diff --git a/services/user/user_test.go b/services/user/user_test.go index cfa02b003311..aefbcd9ecb49 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -33,7 +33,7 @@ func TestDeleteUser(t *testing.T) { ownedRepos := make([]*repo_model.Repository, 0, 10) assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&ownedRepos, &repo_model.Repository{OwnerID: userID})) if len(ownedRepos) > 0 { - err := DeleteUser(user) + err := DeleteUser(db.DefaultContext, user, false) assert.Error(t, err) assert.True(t, models.IsErrUserOwnRepos(err)) return @@ -47,7 +47,7 @@ func TestDeleteUser(t *testing.T) { return } } - assert.NoError(t, DeleteUser(user)) + assert.NoError(t, DeleteUser(db.DefaultContext, user, false)) unittest.AssertNotExistsBean(t, &user_model.User{ID: userID}) unittest.CheckConsistencyFor(t, &user_model.User{}, &repo_model.Repository{}) } @@ -57,7 +57,7 @@ func TestDeleteUser(t *testing.T) { test(11) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) - assert.Error(t, DeleteUser(org)) + assert.Error(t, DeleteUser(db.DefaultContext, org, false)) } func TestCreateUser(t *testing.T) { @@ -72,7 +72,7 @@ func TestCreateUser(t *testing.T) { assert.NoError(t, user_model.CreateUser(user)) - assert.NoError(t, DeleteUser(user)) + assert.NoError(t, DeleteUser(db.DefaultContext, user, false)) } func TestCreateUser_Issue5882(t *testing.T) { @@ -101,6 +101,6 @@ func TestCreateUser_Issue5882(t *testing.T) { assert.Equal(t, !u.AllowCreateOrganization, v.disableOrgCreation) - assert.NoError(t, DeleteUser(v.user)) + assert.NoError(t, DeleteUser(db.DefaultContext, v.user, false)) } } diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 29dcaa127af2..17cbef9f108a 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -151,7 +151,7 @@
-
{{.locale.Tr "admin.users.delete_account"}}
+
{{.locale.Tr "admin.users.delete_account"}}
@@ -196,7 +196,7 @@
-