From 498352c210fc3adf0ab4243e2c56138657fce73b Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 8 Aug 2022 05:07:42 +0000 Subject: [PATCH 01/11] Fix SecToTime edge-cases (#20610) --- modules/util/sec_to_time.go | 20 ++++++++++++++++---- modules/util/sec_to_time_test.go | 23 +++++++++++++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/modules/util/sec_to_time.go b/modules/util/sec_to_time.go index 9ce6fe1a13eb5..13461915e6f35 100644 --- a/modules/util/sec_to_time.go +++ b/modules/util/sec_to_time.go @@ -18,10 +18,22 @@ import ( // 45677465s -> 1 year 6 months func SecToTime(duration int64) string { formattedTime := "" - years := duration / (3600 * 24 * 7 * 4 * 12) - months := (duration / (3600 * 24 * 30)) % 12 - weeks := (duration / (3600 * 24 * 7)) % 4 - days := (duration / (3600 * 24)) % 7 + + // The following four variables are calculated by taking + // into account the previously calculated variables, this avoids + // pitfalls when using remainders. As that could lead to incorrect + // results when the calculated number equals the quotient number. + remainingDays := duration / (60 * 60 * 24) + years := remainingDays / 365 + remainingDays -= years * 365 + months := remainingDays * 12 / 365 + remainingDays -= months * 365 / 12 + weeks := remainingDays / 7 + remainingDays -= weeks * 7 + days := remainingDays + + // The following three variables are calculated without depending + // on the previous calculated variables. hours := (duration / 3600) % 24 minutes := (duration / 60) % 60 seconds := duration % 60 diff --git a/modules/util/sec_to_time_test.go b/modules/util/sec_to_time_test.go index 854190462bc37..1e256aa8650cf 100644 --- a/modules/util/sec_to_time_test.go +++ b/modules/util/sec_to_time_test.go @@ -11,10 +11,21 @@ import ( ) func TestSecToTime(t *testing.T) { - assert.Equal(t, SecToTime(66), "1 minute 6 seconds") - assert.Equal(t, SecToTime(52410), "14 hours 33 minutes") - assert.Equal(t, SecToTime(563418), "6 days 12 hours") - assert.Equal(t, SecToTime(1563418), "2 weeks 4 days") - assert.Equal(t, SecToTime(3937125), "1 month 2 weeks") - assert.Equal(t, SecToTime(45677465), "1 year 5 months") + second := int64(1) + minute := 60 * second + hour := 60 * minute + day := 24 * hour + year := 365 * day + + assert.Equal(t, "1 minute 6 seconds", SecToTime(minute+6*second)) + assert.Equal(t, "1 hour", SecToTime(hour)) + assert.Equal(t, "1 hour", SecToTime(hour+second)) + assert.Equal(t, "14 hours 33 minutes", SecToTime(14*hour+33*minute+30*second)) + assert.Equal(t, "6 days 12 hours", SecToTime(6*day+12*hour+30*minute+18*second)) + assert.Equal(t, "2 weeks 4 days", SecToTime((2*7+4)*day+2*hour+16*minute+58*second)) + assert.Equal(t, "4 weeks", SecToTime(4*7*day)) + assert.Equal(t, "4 weeks 1 day", SecToTime((4*7+1)*day)) + assert.Equal(t, "1 month 2 weeks", SecToTime((6*7+3)*day+13*hour+38*minute+45*second)) + assert.Equal(t, "11 months", SecToTime(year-25*day)) + assert.Equal(t, "1 year 5 months", SecToTime(year+163*day+10*hour+11*minute+5*second)) } From 68059ab69752ad0944c0d4d2d7904ea21e24f45c Mon Sep 17 00:00:00 2001 From: Norwin Date: Mon, 8 Aug 2022 09:27:04 +0200 Subject: [PATCH 02/11] docs: move search input to navbar (#20551) * remove search item from sidebar * fix search for some docs languages Co-authored-by: wxiaoguang --- docs/config.yaml | 1 + docs/content/doc/help/search.de-de.md | 18 ++++++++++++++++++ docs/content/doc/help/search.en-us.md | 6 ------ docs/content/doc/help/search.fr-fr.md | 6 ------ docs/content/doc/help/search.nl-nl.md | 18 ++++++++++++++++++ docs/content/doc/help/search.pt-br.md | 18 ++++++++++++++++++ docs/content/doc/help/search.zh-cn.md | 6 ------ docs/content/doc/help/search.zh-tw.md | 6 ------ docs/layouts/doc/search.html | 6 ------ 9 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 docs/content/doc/help/search.de-de.md create mode 100644 docs/content/doc/help/search.nl-nl.md create mode 100644 docs/content/doc/help/search.pt-br.md diff --git a/docs/config.yaml b/docs/config.yaml index 88406da5df9da..d9544bd301775 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -22,6 +22,7 @@ params: minGoVersion: 1.18 goVersion: 1.19 minNodeVersion: 14 + search: nav outputs: home: diff --git a/docs/content/doc/help/search.de-de.md b/docs/content/doc/help/search.de-de.md new file mode 100644 index 0000000000000..cfacfe737e0f5 --- /dev/null +++ b/docs/content/doc/help/search.de-de.md @@ -0,0 +1,18 @@ +--- +date: "2019-11-12T16:00:00+02:00" +title: "Search" +slug: "search" +weight: 4 +toc: false +draft: false +sitemap: + priority : 0.1 +layout: "search" +--- + + +This file exists solely to respond to /search URL with the related `search` layout template. + +No content shown here is rendered, all content is based in the template layouts/doc/search.html + +Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/doc/help/search.en-us.md b/docs/content/doc/help/search.en-us.md index 8d4b0d20cbfd8..cfacfe737e0f5 100644 --- a/docs/content/doc/help/search.en-us.md +++ b/docs/content/doc/help/search.en-us.md @@ -5,12 +5,6 @@ slug: "search" weight: 4 toc: false draft: false -menu: - sidebar: - parent: "help" - name: "Search" - weight: 4 - identifier: "search" sitemap: priority : 0.1 layout: "search" diff --git a/docs/content/doc/help/search.fr-fr.md b/docs/content/doc/help/search.fr-fr.md index 16fff85a98cf0..bfcd6f3c440a9 100644 --- a/docs/content/doc/help/search.fr-fr.md +++ b/docs/content/doc/help/search.fr-fr.md @@ -5,12 +5,6 @@ slug: "search" weight: 4 toc: false draft: false -menu: - sidebar: - parent: "help" - name: "Chercher" - weight: 4 - identifier: "search" sitemap: priority : 0.1 layout: "search" diff --git a/docs/content/doc/help/search.nl-nl.md b/docs/content/doc/help/search.nl-nl.md new file mode 100644 index 0000000000000..cfacfe737e0f5 --- /dev/null +++ b/docs/content/doc/help/search.nl-nl.md @@ -0,0 +1,18 @@ +--- +date: "2019-11-12T16:00:00+02:00" +title: "Search" +slug: "search" +weight: 4 +toc: false +draft: false +sitemap: + priority : 0.1 +layout: "search" +--- + + +This file exists solely to respond to /search URL with the related `search` layout template. + +No content shown here is rendered, all content is based in the template layouts/doc/search.html + +Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/doc/help/search.pt-br.md b/docs/content/doc/help/search.pt-br.md new file mode 100644 index 0000000000000..cfacfe737e0f5 --- /dev/null +++ b/docs/content/doc/help/search.pt-br.md @@ -0,0 +1,18 @@ +--- +date: "2019-11-12T16:00:00+02:00" +title: "Search" +slug: "search" +weight: 4 +toc: false +draft: false +sitemap: + priority : 0.1 +layout: "search" +--- + + +This file exists solely to respond to /search URL with the related `search` layout template. + +No content shown here is rendered, all content is based in the template layouts/doc/search.html + +Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/doc/help/search.zh-cn.md b/docs/content/doc/help/search.zh-cn.md index 52fae9defb8f5..f6243d1e8f473 100644 --- a/docs/content/doc/help/search.zh-cn.md +++ b/docs/content/doc/help/search.zh-cn.md @@ -5,12 +5,6 @@ slug: "search" weight: 4 toc: false draft: false -menu: - sidebar: - parent: "help" - name: "搜索" - weight: 4 - identifier: "search" sitemap: priority : 0.1 layout: "search" diff --git a/docs/content/doc/help/search.zh-tw.md b/docs/content/doc/help/search.zh-tw.md index ef3c74a90d401..335f95571372b 100644 --- a/docs/content/doc/help/search.zh-tw.md +++ b/docs/content/doc/help/search.zh-tw.md @@ -5,12 +5,6 @@ slug: "search" weight: 4 toc: false draft: false -menu: - sidebar: - parent: "help" - name: "搜尋" - weight: 4 - identifier: "search" sitemap: priority : 0.1 layout: "search" diff --git a/docs/layouts/doc/search.html b/docs/layouts/doc/search.html index 736fcaee106f4..90fe96fe5d9c5 100644 --- a/docs/layouts/doc/search.html +++ b/docs/layouts/doc/search.html @@ -11,12 +11,6 @@
-
- -
-
From 98190d0024b32c946454792c8094435e96c08d70 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 8 Aug 2022 21:42:36 +0800 Subject: [PATCH 03/11] Fix disable download button (#20701) --- modules/context/context.go | 2 +- templates/repo/branch/list.tmpl | 2 +- templates/repo/home.tmpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/context/context.go b/modules/context/context.go index 8824911619921..0b9898acef048 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -224,7 +224,7 @@ func (ctx *Context) HTML(status int, name base.TplName) { ctx.Data["TemplateLoadTimes"] = func() string { return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms" } - if err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data); err != nil { + if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil { if status == http.StatusInternalServerError && name == base.TplName("status/500") { ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template") return diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 4a85692a83151..09799fbece577 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -112,7 +112,7 @@ {{svg "octicon-git-branch"}}
{{end}} - {{if (not .IsDeleted)}} + {{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}} diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl index 484d0a8110663..0f00e9284a47f 100644 --- a/templates/repo/issue/milestone_issues.tmpl +++ b/templates/repo/issue/milestone_issues.tmpl @@ -52,9 +52,25 @@ + + + + @@ -66,9 +82,9 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} {{end}} @@ -100,12 +116,12 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl index 9242d0c5ca684..ccfb40684c3f7 100644 --- a/templates/repo/issue/openclose.tmpl +++ b/templates/repo/issue/openclose.tmpl @@ -1,5 +1,5 @@ diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index d1555c16c5547..f4e0674be4071 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -43,7 +43,7 @@ {{end}} {{range .Labels}} - {{.Name | RenderEmoji}} + {{.Name | RenderEmoji}} {{end}} From 2660b0397aaea041c2d1f4d247d65e92a377c379 Mon Sep 17 00:00:00 2001 From: parnic Date: Tue, 9 Aug 2022 00:21:27 +0000 Subject: [PATCH 07/11] [skip ci] Updated translations via Crowdin --- options/locale/locale_es-ES.ini | 13 +++++++++++++ options/locale/locale_fi-FI.ini | 27 +++++++++++++++++++++++++++ options/locale/locale_lv-LV.ini | 5 +++++ options/locale/locale_pt-PT.ini | 5 +++++ options/locale/locale_tr-TR.ini | 5 +++++ options/locale/locale_zh-CN.ini | 8 ++++---- 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 15222272b3341..be3c2fbb34bf6 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -179,6 +179,8 @@ log_root_path_helper=Archivos de registro se escribirán en este directorio. optional_title=Configuración opcional email_title=Configuración de Correo +smtp_addr=Servidor SMTP +smtp_port=Puerto SMTP smtp_from=Enviar correos electrónicos como smtp_from_helper=Dirección de correo electrónico que utilizará Gitea. Introduzca una dirección de correo electrónico normal o utilice el formato "Nombre" . mailer_user=Nombre de usuario SMTP @@ -1061,6 +1063,7 @@ normal_view=Vista normal line=línea lines=líneas +editor.add_file=Añadir archivo editor.new_file=Nuevo Archivo editor.upload_file=Subir archivo editor.edit_file=Editar Archivo @@ -2794,13 +2797,19 @@ config.queue_length=Tamaño de Cola de Envío config.deliver_timeout=Timeout de Entrega config.skip_tls_verify=Saltar verificación TLS +config.mailer_config=Configuración del servidor de correo config.mailer_enabled=Activado +config.mailer_enable_helo=Habilitar HELO config.mailer_name=Nombre +config.mailer_protocol=Protocolo +config.mailer_smtp_addr=Dirección SMTP +config.mailer_smtp_port=Puerto SMTP config.mailer_user=Usuario config.mailer_use_sendmail=Usar Sendmail config.mailer_sendmail_path=Ruta de Sendmail config.mailer_sendmail_args=Argumentos adicionales por Sendmail config.mailer_sendmail_timeout=Tiempo de espera de Sendmail +config.mailer_use_dummy=Dummy config.test_email_placeholder=Correo electrónico (ej. test@ejemplo.com) config.send_test_mail=Enviar prueba de correo config.test_mail_failed=Fallo al enviar correo electrónico de prueba a '%s': %v @@ -3103,6 +3112,10 @@ npm.dependencies.development=Dependencias de desarrollo npm.dependencies.peer=Dependencias de pares npm.dependencies.optional=Dependencias opcionales npm.details.tag=Etiqueta +pub.install=Para instalar el paquete usando Dart, ejecute el siguiente comando: +pub.documentation=Para obtener más información sobre el registro de Pub, consulte la documentación. +pub.details.repository_site=Sitio del repositorio +pub.details.documentation_site=Sitio de documentación pypi.requires=Requiere Python pypi.install=Para instalar el paquete usando pip, ejecute el siguiente comando: pypi.documentation=Para obtener más información sobre el registro PyPI, consulte la documentación. diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 4f47d8afa64a6..0323854339b58 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -664,6 +664,7 @@ visibility_helper=Tee reposta yksityinen fork_repo=Forkkaa repo fork_from=Forkkaa lähteestä fork_visibility_helper=Forkatun repon näkyvyyttä ei voi muuttaa. +clone_in_vsc=Kloonaa VS Codessa download_zip=Lataa ZIP download_tar=Lataa TAR.GZ repo_desc=Kuvaus @@ -801,14 +802,27 @@ commits.ssh_key_fingerprint=SSH avaimen sormenjälki +projects=Projektit projects.description_placeholder=Kuvaus projects.create=Luo projekti projects.title=Otsikko projects.new=Uusi projekti +projects.create_success=Projekti '%s' on luotu. projects.deletion=Poista projekti +projects.deletion_success=Projekti on poistettu. +projects.edit=Muokkaa projektia +projects.modify=Päivitä projekti +projects.edit_success=Projekti '%s' on päivitetty. projects.type.basic_kanban=Yksinkertainen Kanban +projects.type.uncategorized=Luokittelematon +projects.board.edit=Muokkaa luetteloa +projects.board.new_submit=Lähetä projects.board.new=Uusi taulu +projects.board.set_default=Aseta oletukseksi projects.board.delete=Poista taulu +projects.board.color=Väri +projects.open=Avaa +projects.close=Sulje issues.desc=Ongelmien, tehtävien ja merkkipaalujen hallinta. issues.filter_milestones=Suodata merkkipaalu @@ -1110,11 +1124,22 @@ settings.event_repository=Repo settings.event_issue_comment_desc=Ongelman kommentti luotu, muokattu tai poistettu. settings.event_pull_request=Vetopyyntö settings.update_webhook=Päivitä webkoukku +settings.delete_webhook=Poista webkoukku settings.recent_deliveries=Viimeisimmät toimitukset settings.hook_type=Koukkutyyppi settings.slack_token=Pääsymerkki settings.slack_domain=Verkkotunnus settings.slack_channel=Kanava +settings.web_hook_name_gitea=Gitea +settings.web_hook_name_gogs=Gogs +settings.web_hook_name_slack=Slack +settings.web_hook_name_discord=Discord +settings.web_hook_name_dingtalk=DingTalk +settings.web_hook_name_telegram=Telegram +settings.web_hook_name_matrix=Matrix +settings.web_hook_name_feishu=Feishu +settings.web_hook_name_larksuite=Lark Suite +settings.web_hook_name_packagist=Packagist settings.deploy_keys=Deploy avaimet settings.add_deploy_key=Lisää deploy avain settings.title=Otsikko @@ -1141,6 +1166,7 @@ settings.tags=Tagit settings.tags.protection.pattern=Tagin kuvio settings.tags.protection.pattern.description=Voit käyttää yhtä nimeä tai glob-kuviota tai säännöllistä lauseketta, joka täsmää useisiin tageihin. Lue lisää suojatut tagit oppaasta. settings.bot_token=Botti pääsymerkki +settings.matrix.homeserver_url=Kotipalvelimen URL settings.matrix.access_token=Pääsymerkki settings.archive.button=Arkistoi repo settings.archive.header=Arkistoi tämä repo @@ -1390,6 +1416,7 @@ repos.forks=Haarat repos.issues=Ongelmat repos.size=Koko +packages.owner=Omistaja diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index ac7da8dd17600..acc2fad7e8337 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -1063,6 +1063,7 @@ normal_view=Parastais skats line=rinda lines=rindas +editor.add_file=Pievienot editor.new_file=Jauna datne editor.upload_file=Augšupielādēt failu editor.edit_file=Labot failu @@ -3111,6 +3112,10 @@ npm.dependencies.development=Izstrādes atkarības npm.dependencies.peer=Netiešās atkarības npm.dependencies.optional=Neobligātās atkarības npm.details.tag=Tags +pub.install=Lai instalētu Dart pakotni, izpildiet sekojošu komandu: +pub.documentation=Papildus informācija par Pub reģistru pieejama dokumentācijā. +pub.details.repository_site=Repozitorija izmērs +pub.details.documentation_site=Dokumentācijas lapa pypi.requires=Nepieciešams Python pypi.install=Lai instalētu pip pakotni, izpildiet sekojošu komandu: pypi.documentation=Papildus informācija par PyPI reģistru pieejama dokumentācijā. diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 50b92a95c58b8..b0bb9e1b16cc7 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1063,6 +1063,7 @@ normal_view=Vista normal line=linha lines=linhas +editor.add_file=Adicionar ficheiro editor.new_file=Novo ficheiro editor.upload_file=Carregar ficheiro editor.edit_file=Editar ficheiro @@ -3111,6 +3112,10 @@ npm.dependencies.development=Dependências de desenvolvimento npm.dependencies.peer=Dependências de pares npm.dependencies.optional=Dependências opcionais npm.details.tag=Etiqueta +pub.install=Para instalar o pacote usando o Dart, execute o seguinte comando: +pub.documentation=Para obter mais informações sobre o registo Pub, consulte a documentação. +pub.details.repository_site=Página web do repositório +pub.details.documentation_site=Página web da documentação pypi.requires=Requer Python pypi.install=Para instalar o pacote usando o pip, execute o seguinte comando: pypi.documentation=Para obter mais informações sobre o registo do PyPI, consulte a documentação. diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 07565395cdbd0..d33713c684f3c 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1063,6 +1063,7 @@ normal_view=Normal Görünüm line=satır lines=satır +editor.add_file=Dosya Ekle editor.new_file=Yeni dosya editor.upload_file=Dosya Yükle editor.edit_file=Dosyayı Düzenle @@ -3111,6 +3112,10 @@ npm.dependencies.development=Geliştirme Bağımlılıkları npm.dependencies.peer=Eş Bağımlılıkları npm.dependencies.optional=İsteğe Bağlı Bağımlılıklar npm.details.tag=Etiket +pub.install=Paketi Dart ile kurmak için, şu komutu çalıştırın: +pub.documentation=Pub kütüğü hakkında daha fazla bilgi için, belgeye bakabilirsiniz. +pub.details.repository_site=Depo Sitesi +pub.details.documentation_site=Belge Sitesi pypi.requires=Gereken Python pypi.install=Paketi pip ile kurmak için, şu komutu çalıştırın: pypi.documentation=PyPI kütüğü hakkında daha fazla bilgi için, belgeye bakabilirsiniz. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index ef239d0e3fbf9..5d8e427a91256 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -9,7 +9,7 @@ sign_out=退出 sign_up=注册 link_account=链接账户 register=注册 -website=官方网站 +website=网站 version=当前版本 powered_by=Powered by %s page=页面 @@ -20,7 +20,7 @@ active_stopwatch=活动时间跟踪器 create_new=创建… user_profile_and_more=个人信息和配置 signed_in_as=已登录用户 -enable_javascript=使用 JavaScript能使本网站更好的工作。 +enable_javascript=使用 JavaScript 能使本网站更好的工作。 toc=目录 licenses=许可证 return_to_gitea=返回 Gitea @@ -2203,7 +2203,7 @@ release.stable=稳定 release.compare=比较 release.edit=编辑 release.ahead.commits=%d 次提交 -release.ahead.target=到 %s 自发布后 +release.ahead.target=在此版本发布后被加入到 %s release.source_code=源代码 release.new_subheader=版本发布组织项目的版本。 release.edit_subheader=版本发布组织项目的版本。 @@ -2314,7 +2314,7 @@ form.create_org_not_allowed=此账号禁止创建组织 settings=组织设置 settings.options=组织 settings.full_name=组织全名 -settings.website=官方网站 +settings.website=网站 settings.location=所在地区 settings.permission=权限 settings.repoadminchangeteam=仓库管理员可以添加或移除团队的访问权限 From 820031e556af9548aa07e4de2669ca52bd7f63cb Mon Sep 17 00:00:00 2001 From: luzpaz Date: Mon, 8 Aug 2022 21:53:40 -0400 Subject: [PATCH 08/11] Fix typo in source (#20723) Algorightm -> Algorithm Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lunny Xiao --- services/auth/source/oauth2/jwtsigningkey.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/auth/source/oauth2/jwtsigningkey.go b/services/auth/source/oauth2/jwtsigningkey.go index 24f2c41119c16..d6b3c05a4fcbf 100644 --- a/services/auth/source/oauth2/jwtsigningkey.go +++ b/services/auth/source/oauth2/jwtsigningkey.go @@ -31,11 +31,11 @@ import ( // ErrInvalidAlgorithmType represents an invalid algorithm error. type ErrInvalidAlgorithmType struct { - Algorightm string + Algorithm string } func (err ErrInvalidAlgorithmType) Error() string { - return fmt.Sprintf("JWT signing algorithm is not supported: %s", err.Algorightm) + return fmt.Sprintf("JWT signing algorithm is not supported: %s", err.Algorithm) } // JWTSigningKey represents a algorithm/key pair to sign JWTs From 75d96f4a0274c5b7566305d494c176e1a88b1589 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 9 Aug 2022 11:22:24 +0800 Subject: [PATCH 09/11] Refactor legacy git init (#20376) * merge `CheckLFSVersion` into `InitFull` (renamed from `InitWithSyncOnce`) * remove the `Once` during git init, no data-race now * for doctor sub-commands, `InitFull` should only be called in initialization stage Co-authored-by: zeripath Co-authored-by: Lunny Xiao --- cmd/doctor.go | 12 ++- contrib/pr/checkout.go | 1 - integrations/git_test.go | 15 +--- integrations/integration_test.go | 5 +- integrations/lfs_getobject_test.go | 31 -------- integrations/migration-test/migration_test.go | 3 +- models/migrations/migrations_test.go | 6 +- models/unittest/testdb.go | 6 +- modules/doctor/mergebase.go | 3 - modules/doctor/misc.go | 4 - modules/git/git.go | 79 +++++++++---------- modules/git/git_test.go | 2 +- modules/git/lfs.go | 31 -------- routers/init.go | 4 +- 14 files changed, 58 insertions(+), 144 deletions(-) delete mode 100644 modules/git/lfs.go diff --git a/cmd/doctor.go b/cmd/doctor.go index 3f16c6e2a600a..1a15dd2941c57 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/doctor" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -124,13 +125,18 @@ func runRecreateTable(ctx *cli.Context) error { } func runDoctor(ctx *cli.Context) error { + stdCtx, cancel := installSignals() + defer cancel() + + // some doctor sub-commands need to use git command + if err := git.InitFull(stdCtx); err != nil { + return err + } + // Silence the default loggers log.DelNamedLogger("console") log.DelNamedLogger(log.DEFAULT) - stdCtx, cancel := installSignals() - defer cancel() - // Now setup our own logFile := ctx.String("log-file") if !ctx.IsSet("log-file") { diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go index f6d29f3c5b574..65762a91e3b8f 100644 --- a/contrib/pr/checkout.go +++ b/contrib/pr/checkout.go @@ -80,7 +80,6 @@ func runPR() { setting.RunUser = curUser.Username log.Printf("[PR] Loading fixtures data ...\n") - gitea_git.CheckLFSVersion() //models.LoadConfigs() /* setting.Database.Type = "sqlite3" diff --git a/integrations/git_test.go b/integrations/git_test.go index d6bd673822ba9..f9e1eafb65833 100644 --- a/integrations/git_test.go +++ b/integrations/git_test.go @@ -156,11 +156,6 @@ func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) { t.Run("LFS", func(t *testing.T) { defer PrintCurrentTest(t)() - git.CheckLFSVersion() - if !setting.LFS.StartServer { - t.Skip() - return - } prefix := "lfs-data-file-" err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) @@ -226,7 +221,6 @@ func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS s resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) assert.Equal(t, littleSize, resp.Length) - git.CheckLFSVersion() if setting.LFS.StartServer { req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS)) resp := session.MakeRequest(t, req, http.StatusOK) @@ -268,12 +262,9 @@ func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) assert.Equal(t, littleSize, resp.Length) - git.CheckLFSVersion() - if setting.LFS.StartServer { - req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) - resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) - assert.Equal(t, littleSize, resp.Length) - } + req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) + resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, littleSize, resp.Length) if !testing.Short() { req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big)) diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 230f780175c9c..3c379f5c84ef9 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -175,10 +175,9 @@ func initIntegrationTest() { setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" _ = util.RemoveAll(repo_module.LocalCopyPath()) - if err := git.InitOnceWithSync(context.Background()); err != nil { + if err := git.InitFull(context.Background()); err != nil { log.Fatal("git.InitOnceWithSync: %v", err) } - git.CheckLFSVersion() setting.InitDBConfig() if err := storage.Init(); err != nil { @@ -285,7 +284,6 @@ func prepareTestEnv(t testing.TB, skip ...int) func() { assert.NoError(t, unittest.LoadFixtures()) assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { assert.NoError(t, err, "unable to read the new repo root: %v\n", err) @@ -586,7 +584,6 @@ func resetFixtures(t *testing.T) { assert.NoError(t, unittest.LoadFixtures()) assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { assert.NoError(t, err, "unable to read the new repo root: %v\n", err) diff --git a/integrations/lfs_getobject_test.go b/integrations/lfs_getobject_test.go index 4b6bb140d3adc..14a8ac253e2c4 100644 --- a/integrations/lfs_getobject_test.go +++ b/integrations/lfs_getobject_test.go @@ -14,7 +14,6 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" @@ -83,11 +82,6 @@ func checkResponseTestContentEncoding(t *testing.T, content *[]byte, resp *httpt func TestGetLFSSmall(t *testing.T) { defer prepareTestEnv(t)() - git.CheckLFSVersion() - if !setting.LFS.StartServer { - t.Skip() - return - } content := []byte("A very small file\n") resp := storeAndGetLfs(t, &content, nil, http.StatusOK) @@ -96,11 +90,6 @@ func TestGetLFSSmall(t *testing.T) { func TestGetLFSLarge(t *testing.T) { defer prepareTestEnv(t)() - git.CheckLFSVersion() - if !setting.LFS.StartServer { - t.Skip() - return - } content := make([]byte, web.GzipMinSize*10) for i := range content { content[i] = byte(i % 256) @@ -112,11 +101,6 @@ func TestGetLFSLarge(t *testing.T) { func TestGetLFSGzip(t *testing.T) { defer prepareTestEnv(t)() - git.CheckLFSVersion() - if !setting.LFS.StartServer { - t.Skip() - return - } b := make([]byte, web.GzipMinSize*10) for i := range b { b[i] = byte(i % 256) @@ -133,11 +117,6 @@ func TestGetLFSGzip(t *testing.T) { func TestGetLFSZip(t *testing.T) { defer prepareTestEnv(t)() - git.CheckLFSVersion() - if !setting.LFS.StartServer { - t.Skip() - return - } b := make([]byte, web.GzipMinSize*10) for i := range b { b[i] = byte(i % 256) @@ -156,11 +135,6 @@ func TestGetLFSZip(t *testing.T) { func TestGetLFSRangeNo(t *testing.T) { defer prepareTestEnv(t)() - git.CheckLFSVersion() - if !setting.LFS.StartServer { - t.Skip() - return - } content := []byte("123456789\n") resp := storeAndGetLfs(t, &content, nil, http.StatusOK) @@ -169,11 +143,6 @@ func TestGetLFSRangeNo(t *testing.T) { func TestGetLFSRange(t *testing.T) { defer prepareTestEnv(t)() - git.CheckLFSVersion() - if !setting.LFS.StartServer { - t.Skip() - return - } content := []byte("123456789\n") tests := []struct { diff --git a/integrations/migration-test/migration_test.go b/integrations/migration-test/migration_test.go index 20a5c903a92cc..80093d66f1ed8 100644 --- a/integrations/migration-test/migration_test.go +++ b/integrations/migration-test/migration_test.go @@ -82,8 +82,7 @@ func initMigrationTest(t *testing.T) func() { } } - assert.NoError(t, git.InitOnceWithSync(context.Background())) - git.CheckLFSVersion() + assert.NoError(t, git.InitFull(context.Background())) setting.InitDBConfig() setting.NewLogServices(true) return deferFn diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go index 46782f24a1003..53e4f35395640 100644 --- a/models/migrations/migrations_test.go +++ b/models/migrations/migrations_test.go @@ -66,11 +66,10 @@ func TestMain(m *testing.M) { setting.SetCustomPathAndConf("", "", "") setting.LoadForTest() - if err = git.InitOnceWithSync(context.Background()); err != nil { - fmt.Printf("Unable to InitOnceWithSync: %v\n", err) + if err = git.InitFull(context.Background()); err != nil { + fmt.Printf("Unable to InitFull: %v\n", err) os.Exit(1) } - git.CheckLFSVersion() setting.InitDBConfig() setting.NewLogServices(true) @@ -207,7 +206,6 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En deferFn := PrintCurrentTest(t, ourSkip) assert.NoError(t, os.RemoveAll(setting.RepoRootPath)) assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { assert.NoError(t, err, "unable to read the new repo root: %v\n", err) diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 7f9af553747cb..58656f781f137 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -120,11 +120,9 @@ func MainTest(m *testing.M, testOpts *TestOptions) { fatalTestError("util.CopyDir: %v\n", err) } - if err = git.InitOnceWithSync(context.Background()); err != nil { + if err = git.InitFull(context.Background()); err != nil { fatalTestError("git.Init: %v\n", err) } - git.CheckLFSVersion() - ownerDirs, err := os.ReadDir(setting.RepoRootPath) if err != nil { fatalTestError("unable to read the new repo root: %v\n", err) @@ -206,8 +204,6 @@ func PrepareTestEnv(t testing.TB) { assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta") assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath)) - assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again - ownerDirs, err := os.ReadDir(setting.RepoRootPath) assert.NoError(t, err) for _, ownerDir := range ownerDirs { diff --git a/modules/doctor/mergebase.go b/modules/doctor/mergebase.go index 2da91cdcc35f6..46369290a13d7 100644 --- a/modules/doctor/mergebase.go +++ b/modules/doctor/mergebase.go @@ -30,9 +30,6 @@ func iteratePRs(ctx context.Context, repo *repo_model.Repository, each func(*rep } func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) error { - if err := git.InitOnceWithSync(ctx); err != nil { - return err - } numRepos := 0 numPRs := 0 numPRsUpdated := 0 diff --git a/modules/doctor/misc.go b/modules/doctor/misc.go index 24175fcaf4bec..2d2bcb910db4c 100644 --- a/modules/doctor/misc.go +++ b/modules/doctor/misc.go @@ -190,10 +190,6 @@ func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) err } func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) error { - if err := git.InitOnceWithSync(ctx); err != nil { - return err - } - numRepos := 0 numNeedUpdate := 0 numWritten := 0 diff --git a/modules/git/git.go b/modules/git/git.go index b8317396c0150..99849f1f09457 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -15,7 +15,6 @@ import ( "regexp" "runtime" "strings" - "sync" "time" "code.gitea.io/gitea/modules/log" @@ -24,8 +23,8 @@ import ( "github.com/hashicorp/go-version" ) -// GitVersionRequired is the minimum Git version required -const GitVersionRequired = "2.0.0" +// RequiredVersion is the minimum Git version required +const RequiredVersion = "2.0.0" var ( // GitExecutable is the command name of git @@ -43,7 +42,7 @@ var ( // loadGitVersion returns current Git version from shell. Internal usage only. func loadGitVersion() (*version.Version, error) { - // doesn't need RWMutex because its exec by Init() + // doesn't need RWMutex because it's executed by Init() if gitVersion != nil { return gitVersion, nil } @@ -90,7 +89,7 @@ func SetExecutablePath(path string) error { return fmt.Errorf("unable to load git version: %w", err) } - versionRequired, err := version.NewVersion(GitVersionRequired) + versionRequired, err := version.NewVersion(RequiredVersion) if err != nil { return err } @@ -104,7 +103,7 @@ func SetExecutablePath(path string) error { moreHint = "get git: https://git-scm.com/download/linux and https://ius.io" } } - return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), GitVersionRequired, moreHint) + return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), RequiredVersion, moreHint) } return nil @@ -131,7 +130,7 @@ func checkInit() error { return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules") } if DefaultContext != nil { - log.Warn("git module has been initialized already, duplicate init should be fixed") + log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it") } return nil } @@ -140,7 +139,7 @@ func checkInit() error { func HomeDir() string { if setting.Git.HomePath == "" { // strict check, make sure the git module is initialized correctly. - // attention: when the git module is called in gitea sub-command (serv/hook), the log module is not able to show messages to users. + // attention: when the git module is called in gitea sub-command (serv/hook), the log module might not obviously show messages to users/developers. // for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons. log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules") return "" @@ -149,14 +148,14 @@ func HomeDir() string { } // InitSimple initializes git module with a very simple step, no config changes, no global command arguments. -// This method doesn't change anything to filesystem. At the moment, it is only used by "git serv" sub-command, no data-race -// However, in integration test, the sub-command function may be called in the current process, so the InitSimple would be called multiple times, too +// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands. func InitSimple(ctx context.Context) error { if err := checkInit(); err != nil { return err } DefaultContext = ctx + globalCommandArgs = nil if setting.Git.Timeout.Default > 0 { defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second @@ -165,46 +164,46 @@ func InitSimple(ctx context.Context) error { return SetExecutablePath(setting.Git.Path) } -var initOnce sync.Once - -// InitOnceWithSync initializes git module with version check and change global variables, sync gitconfig. -// This method will update the global variables ONLY ONCE (just like git.CheckLFSVersion -- which is not ideal too), -// otherwise there will be data-race problem at the moment. -func InitOnceWithSync(ctx context.Context) (err error) { +// InitFull initializes git module with version check and change global variables, sync gitconfig. +// It should only be called once at the beginning of the program initialization (TestMain/GlobalInitInstalled) as this code makes unsynchronized changes to variables. +func InitFull(ctx context.Context) (err error) { if err = checkInit(); err != nil { return err } - initOnce.Do(func() { - if err = InitSimple(ctx); err != nil { - return - } + if err = InitSimple(ctx); err != nil { + return + } - // when git works with gnupg (commit signing), there should be a stable home for gnupg commands - if _, ok := os.LookupEnv("GNUPGHOME"); !ok { - _ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) - } + // when git works with gnupg (commit signing), there should be a stable home for gnupg commands + if _, ok := os.LookupEnv("GNUPGHOME"); !ok { + _ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) + } - // Since git wire protocol has been released from git v2.18 - if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { - globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") - } + // Since git wire protocol has been released from git v2.18 + if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { + globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") + } - // By default partial clones are disabled, enable them from git v2.22 - if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { - globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true") - } + // By default partial clones are disabled, enable them from git v2.22 + if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { + globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true") + } - // Explicitly disable credential helper, otherwise Git credentials might leak - if CheckGitVersionAtLeast("2.9") == nil { - globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") - } + // Explicitly disable credential helper, otherwise Git credentials might leak + if CheckGitVersionAtLeast("2.9") == nil { + globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") + } - SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil - }) - if err != nil { - return err + SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil + + if setting.LFS.StartServer { + if CheckGitVersionAtLeast("2.1.2") != nil { + return errors.New("LFS server support requires Git >= 2.1.2") + } + globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") } + return syncGitConfig() } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index c5a63de0644c0..091573787871f 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -28,7 +28,7 @@ func testRun(m *testing.M) error { defer util.RemoveAll(gitHomePath) setting.Git.HomePath = gitHomePath - if err = InitOnceWithSync(context.Background()); err != nil { + if err = InitFull(context.Background()); err != nil { return fmt.Errorf("failed to call Init: %w", err) } diff --git a/modules/git/lfs.go b/modules/git/lfs.go deleted file mode 100644 index c5d8354b6dc8c..0000000000000 --- a/modules/git/lfs.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package git - -import ( - "sync" - - logger "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" -) - -var once sync.Once - -// CheckLFSVersion will check lfs version, if not satisfied, then disable it. -func CheckLFSVersion() { - if setting.LFS.StartServer { - // Disable LFS client hooks if installed for the current OS user - // Needs at least git v2.1.2 - if CheckGitVersionAtLeast("2.1.2") != nil { - setting.LFS.StartServer = false - logger.Error("LFS server support needs at least Git v2.1.2") - } else { - once.Do(func() { - globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", - "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") - }) - } - } -} diff --git a/routers/init.go b/routers/init.go index e640ca48453bc..612fc5a83dbbc 100644 --- a/routers/init.go +++ b/routers/init.go @@ -100,10 +100,8 @@ func GlobalInitInstalled(ctx context.Context) { log.Fatal("Gitea is not installed") } - mustInitCtx(ctx, git.InitOnceWithSync) + mustInitCtx(ctx, git.InitFull) log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir()) - - git.CheckLFSVersion() log.Info("AppPath: %s", setting.AppPath) log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("Custom path: %s", setting.CustomPath) From cc6927b2d8337d0bf9024c3d77cff00a9397872f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 9 Aug 2022 06:39:24 +0200 Subject: [PATCH 10/11] Allow multiple files in generic packages (#20661) * Allow multiple files in generic packages. * Add deletion of a single file. * Update docs. * Change version check. Co-authored-by: silverwind Co-authored-by: wxiaoguang --- docs/content/doc/packages/generic.en-us.md | 75 ++++++++++- integrations/api_packages_generic_test.go | 147 ++++++++++++++++----- routers/api/packages/api.go | 15 ++- routers/api/packages/generic/generic.go | 93 ++++++++----- 4 files changed, 254 insertions(+), 76 deletions(-) diff --git a/docs/content/doc/packages/generic.en-us.md b/docs/content/doc/packages/generic.en-us.md index a82058da8ac5c..dd0f5653fff8a 100644 --- a/docs/content/doc/packages/generic.en-us.md +++ b/docs/content/doc/packages/generic.en-us.md @@ -27,7 +27,7 @@ To authenticate to the Package Registry, you need to provide [custom HTTP header ## Publish a package To publish a generic package perform a HTTP PUT operation with the package content in the request body. -You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. +You cannot publish a file with the same name twice to a package. You must delete the existing package version first. ``` PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{package_version}/{file_name} @@ -36,9 +36,9 @@ PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{packa | Parameter | Description | | ----------------- | ----------- | | `owner` | The owner of the package. | -| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). | -| `package_version` | The package version, a non-empty string. | -| `file_name` | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). | +| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), pluses (`+`), or underscores (`_`). | +| `package_version` | The package version, a non-empty string without trailing or leading whitespaces. | +| `file_name` | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), pluses (`+`), or underscores (`_`). | Example request using HTTP Basic authentication: @@ -55,7 +55,8 @@ The server reponds with the following HTTP Status codes. | HTTP Status Code | Meaning | | ----------------- | ------- | | `201 Created` | The package has been published. | -| `400 Bad Request` | The package name and/or version are invalid or a package with the same name and version already exist. | +| `400 Bad Request` | The package name and/or version and/or file name are invalid. | +| `409 Conflict` | A file with the same name exist already in the package. | ## Download a package @@ -80,3 +81,67 @@ Example request using HTTP Basic authentication: curl --user your_username:your_token_or_password \ https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0/file.bin ``` + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `200 OK` | Success | +| `404 Not Found` | The package or file was not found. | + +## Delete a package + +To delete a generic package perform a HTTP DELETE operation. This will delete all files of this version. + +``` +DELETE https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{package_version} +``` + +| Parameter | Description | +| ----------------- | ----------- | +| `owner` | The owner of the package. | +| `package_name` | The package name. | +| `package_version` | The package version. | + +Example request using HTTP Basic authentication: + +```shell +curl --user your_username:your_token_or_password -X DELETE \ + https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0 +``` + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `204 No Content` | Success | +| `404 Not Found` | The package was not found. | + +## Delete a package file + +To delete a file of a generic package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. + +``` +DELETE https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{package_version}/{filename} +``` + +| Parameter | Description | +| ----------------- | ----------- | +| `owner` | The owner of the package. | +| `package_name` | The package name. | +| `package_version` | The package version. | +| `filename` | The filename. | + +Example request using HTTP Basic authentication: + +```shell +curl --user your_username:your_token_or_password -X DELETE \ + https://gitea.example.com/api/packages/testuser/generic/test_package/1.0.0/file.bin +``` + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `204 No Content` | Success | +| `404 Not Found` | The package or file was not found. | diff --git a/integrations/api_packages_generic_test.go b/integrations/api_packages_generic_test.go index adaf99e981ae9..c60a387f533bc 100644 --- a/integrations/api_packages_generic_test.go +++ b/integrations/api_packages_generic_test.go @@ -23,16 +23,16 @@ func TestPackageGeneric(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) packageName := "te-st_pac.kage" - packageVersion := "1.0.3" + packageVersion := "1.0.3-te st" filename := "fi-le_na.me" content := []byte{1, 2, 3} - url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, filename) + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion) t.Run("Upload", func(t *testing.T) { defer PrintCurrentTest(t)() - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) + req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content)) AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusCreated) @@ -55,54 +55,139 @@ func TestPackageGeneric(t *testing.T) { pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) assert.NoError(t, err) assert.Equal(t, int64(len(content)), pb.Size) - }) - t.Run("UploadExists", func(t *testing.T) { - defer PrintCurrentTest(t)() + t.Run("Exists", func(t *testing.T) { + defer PrintCurrentTest(t)() - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) - AddBasicAuthHeader(req, user.Name) - MakeRequest(t, req, http.StatusBadRequest) + req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusConflict) + }) + + t.Run("Additional", func(t *testing.T) { + defer PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url+"/dummy.bin", bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + // Check deduplication + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 2) + assert.Equal(t, pfs[0].BlobID, pfs[1].BlobID) + }) + + t.Run("InvalidParameter", func(t *testing.T) { + defer PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, "invalid+package name", packageVersion, filename), bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, "%20test ", filename), bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, "inval+id.na me"), bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + }) }) t.Run("Download", func(t *testing.T) { defer PrintCurrentTest(t)() - req := NewRequest(t, "GET", url) + checkDownloadCount := func(count int64) { + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, count, pvs[0].DownloadCount) + } + + checkDownloadCount(0) + + req := NewRequest(t, "GET", url+"/"+filename) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, content, resp.Body.Bytes()) - pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) - assert.NoError(t, err) - assert.Len(t, pvs, 1) - assert.Equal(t, int64(1), pvs[0].DownloadCount) + checkDownloadCount(1) + + req = NewRequest(t, "GET", url+"/dummy.bin") + MakeRequest(t, req, http.StatusOK) + + checkDownloadCount(2) + + t.Run("NotExists", func(t *testing.T) { + defer PrintCurrentTest(t)() + + req := NewRequest(t, "GET", url+"/not.found") + MakeRequest(t, req, http.StatusNotFound) + }) }) t.Run("Delete", func(t *testing.T) { defer PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", url) - AddBasicAuthHeader(req, user.Name) - MakeRequest(t, req, http.StatusOK) + t.Run("File", func(t *testing.T) { + defer PrintCurrentTest(t)() - pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) - assert.NoError(t, err) - assert.Empty(t, pvs) - }) + req := NewRequest(t, "DELETE", url+"/"+filename) + MakeRequest(t, req, http.StatusUnauthorized) - t.Run("DownloadNotExists", func(t *testing.T) { - defer PrintCurrentTest(t)() + req = NewRequest(t, "DELETE", url+"/"+filename) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) - req := NewRequest(t, "GET", url) - MakeRequest(t, req, http.StatusNotFound) - }) + req = NewRequest(t, "GET", url+"/"+filename) + MakeRequest(t, req, http.StatusNotFound) - t.Run("DeleteNotExists", func(t *testing.T) { - defer PrintCurrentTest(t)() + req = NewRequest(t, "DELETE", url+"/"+filename) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) - req := NewRequest(t, "DELETE", url) - AddBasicAuthHeader(req, user.Name) - MakeRequest(t, req, http.StatusNotFound) + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + t.Run("RemovesVersion", func(t *testing.T) { + defer PrintCurrentTest(t)() + + req = NewRequest(t, "DELETE", url+"/dummy.bin") + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Empty(t, pvs) + }) + }) + + t.Run("Version", func(t *testing.T) { + defer PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "DELETE", url) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", url) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Empty(t, pvs) + + req = NewRequest(t, "GET", url+"/"+filename) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "DELETE", url) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + }) }) } diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 4b274860dcdff..84bdce30fabad 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -156,12 +156,15 @@ func Routes() *web.Route { }) }) r.Group("/generic", func() { - r.Group("/{packagename}/{packageversion}/{filename}", func() { - r.Get("", generic.DownloadPackageFile) - r.Group("", func() { - r.Put("", generic.UploadPackage) - r.Delete("", generic.DeletePackage) - }, reqPackageAccess(perm.AccessModeWrite)) + r.Group("/{packagename}/{packageversion}", func() { + r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage) + r.Group("/{filename}", func() { + r.Get("", generic.DownloadPackageFile) + r.Group("", func() { + r.Put("", generic.UploadPackage) + r.Delete("", generic.DeletePackageFile) + }, reqPackageAccess(perm.AccessModeWrite)) + }) }) }) r.Group("/helm", func() { diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index 9a3a185d9da50..79e5afb03cae3 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -31,22 +31,16 @@ func apiError(ctx *context.Context, status int, obj interface{}) { // DownloadPackageFile serves the specific generic package. func DownloadPackageFile(ctx *context.Context) { - packageName, packageVersion, filename, err := sanitizeParameters(ctx) - if err != nil { - apiError(ctx, http.StatusBadRequest, err) - return - } - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, PackageType: packages_model.TypeGeneric, - Name: packageName, - Version: packageVersion, + Name: ctx.Params("packagename"), + Version: ctx.Params("packageversion"), }, &packages_service.PackageFileInfo{ - Filename: filename, + Filename: ctx.Params("filename"), }, ) if err != nil { @@ -65,9 +59,17 @@ func DownloadPackageFile(ctx *context.Context) { // UploadPackage uploads the specific generic package. // Duplicated packages get rejected. func UploadPackage(ctx *context.Context) { - packageName, packageVersion, filename, err := sanitizeParameters(ctx) - if err != nil { - apiError(ctx, http.StatusBadRequest, err) + packageName := ctx.Params("packagename") + filename := ctx.Params("filename") + + if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) { + apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name or filename")) + return + } + + packageVersion := ctx.Params("packageversion") + if packageVersion != strings.TrimSpace(packageVersion) { + apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version")) return } @@ -88,7 +90,7 @@ func UploadPackage(ctx *context.Context) { } defer buf.Close() - _, _, err = packages_service.CreatePackageAndAddFile( + _, _, err = packages_service.CreatePackageOrAddFileToExisting( &packages_service.PackageCreationInfo{ PackageInfo: packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -107,8 +109,8 @@ func UploadPackage(ctx *context.Context) { }, ) if err != nil { - if err == packages_model.ErrDuplicatePackageVersion { - apiError(ctx, http.StatusBadRequest, err) + if err == packages_model.ErrDuplicatePackageFile { + apiError(ctx, http.StatusConflict, err) return } apiError(ctx, http.StatusInternalServerError, err) @@ -120,19 +122,13 @@ func UploadPackage(ctx *context.Context) { // DeletePackage deletes the specific generic package. func DeletePackage(ctx *context.Context) { - packageName, packageVersion, _, err := sanitizeParameters(ctx) - if err != nil { - apiError(ctx, http.StatusBadRequest, err) - return - } - - err = packages_service.RemovePackageVersionByNameAndVersion( + err := packages_service.RemovePackageVersionByNameAndVersion( ctx.Doer, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, PackageType: packages_model.TypeGeneric, - Name: packageName, - Version: packageVersion, + Name: ctx.Params("packagename"), + Version: ctx.Params("packageversion"), }, ) if err != nil { @@ -144,21 +140,50 @@ func DeletePackage(ctx *context.Context) { return } - ctx.Status(http.StatusOK) + ctx.Status(http.StatusNoContent) } -func sanitizeParameters(ctx *context.Context) (string, string, string, error) { - packageName := ctx.Params("packagename") - filename := ctx.Params("filename") +// DeletePackageFile deletes the specific file of a generic package. +func DeletePackageFile(ctx *context.Context) { + pv, pf, err := func() (*packages_model.PackageVersion, *packages_model.PackageFile, error) { + pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeGeneric, ctx.Params("packagename"), ctx.Params("packageversion")) + if err != nil { + return nil, nil, err + } - if !packageNameRegex.MatchString(packageName) || !filenameRegex.MatchString(filename) { - return "", "", "", errors.New("Invalid package name or filename") + pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey) + if err != nil { + return nil, nil, err + } + + return pv, pf, nil + }() + if err != nil { + if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { + apiError(ctx, http.StatusNotFound, err) + return + } + apiError(ctx, http.StatusInternalServerError, err) + return } - packageVersion := strings.TrimSpace(ctx.Params("packageversion")) - if packageVersion == "" { - return "", "", "", errors.New("Invalid package version") + pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + if len(pfs) == 1 { + if err := packages_service.RemovePackageVersion(ctx.Doer, pv); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + } else { + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } } - return packageName, packageVersion, filename, nil + ctx.Status(http.StatusNoContent) } From fba20550f917a808846256d279ac3b4f9e302936 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 9 Aug 2022 09:23:43 +0200 Subject: [PATCH 11/11] Add support for `npm unpublish` (#20688) --- docs/content/doc/packages/npm.en-us.md | 21 +++++ integrations/api_packages_npm_test.go | 106 +++++++++++++++++++------ routers/api/packages/api.go | 18 ++++- routers/api/packages/npm/npm.go | 57 +++++++++++++ 4 files changed, 175 insertions(+), 27 deletions(-) diff --git a/docs/content/doc/packages/npm.en-us.md b/docs/content/doc/packages/npm.en-us.md index 9ab4ac900cfb4..122f306ee5e3a 100644 --- a/docs/content/doc/packages/npm.en-us.md +++ b/docs/content/doc/packages/npm.en-us.md @@ -67,6 +67,26 @@ npm publish You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. +## Unpublish a package + +Delete a package by running the following command: + +```shell +npm unpublish {package_name}[@{package_version}] +``` + +| Parameter | Description | +| ----------------- | ----------- | +| `package_name` | The package name. | +| `package_version` | The package version. | + +For example: + +```shell +npm unpublish @test/test_package +npm unpublish @test/test_package@1.0.0 +``` + ## Install a package To install a package from the package registry, execute the following command: @@ -113,6 +133,7 @@ The tag name must not be a valid version. All tag names which are parsable as a npm install npm ci npm publish +npm unpublish npm dist-tag npm view ``` diff --git a/integrations/api_packages_npm_test.go b/integrations/api_packages_npm_test.go index ad88ac5da6764..23f13866bf50b 100644 --- a/integrations/api_packages_npm_test.go +++ b/integrations/api_packages_npm_test.go @@ -36,33 +36,36 @@ func TestPackageNpm(t *testing.T) { packageDescription := "Test Description" data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA" - upload := `{ - "_id": "` + packageName + `", - "name": "` + packageName + `", - "description": "` + packageDescription + `", - "dist-tags": { - "` + packageTag + `": "` + packageVersion + `" - }, - "versions": { - "` + packageVersion + `": { + + buildUpload := func(version string) string { + return `{ + "_id": "` + packageName + `", "name": "` + packageName + `", - "version": "` + packageVersion + `", "description": "` + packageDescription + `", - "author": { - "name": "` + packageAuthor + `" + "dist-tags": { + "` + packageTag + `": "` + version + `" + }, + "versions": { + "` + version + `": { + "name": "` + packageName + `", + "version": "` + version + `", + "description": "` + packageDescription + `", + "author": { + "name": "` + packageAuthor + `" + }, + "dist": { + "integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==", + "shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90" + } + } }, - "dist": { - "integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==", - "shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90" + "_attachments": { + "` + packageName + `-` + version + `.tgz": { + "data": "` + data + `" + } } - } - }, - "_attachments": { - "` + packageName + `-` + packageVersion + `.tgz": { - "data": "` + data + `" - } - } - }` + }` + } root := fmt.Sprintf("/api/packages/%s/npm/%s", user.Name, url.QueryEscape(packageName)) tagsRoot := fmt.Sprintf("/api/packages/%s/npm/-/package/%s/dist-tags", user.Name, url.QueryEscape(packageName)) @@ -71,7 +74,7 @@ func TestPackageNpm(t *testing.T) { t.Run("Upload", func(t *testing.T) { defer PrintCurrentTest(t)() - req := NewRequestWithBody(t, "PUT", root, strings.NewReader(upload)) + req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion))) req = addTokenAuthHeader(req, token) MakeRequest(t, req, http.StatusCreated) @@ -103,7 +106,7 @@ func TestPackageNpm(t *testing.T) { t.Run("UploadExists", func(t *testing.T) { defer PrintCurrentTest(t)() - req := NewRequestWithBody(t, "PUT", root, strings.NewReader(upload)) + req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion))) req = addTokenAuthHeader(req, token) MakeRequest(t, req, http.StatusBadRequest) }) @@ -219,4 +222,57 @@ func TestPackageNpm(t *testing.T) { test(t, http.StatusOK, "dummy") test(t, http.StatusOK, packageTag2) }) + + t.Run("Delete", func(t *testing.T) { + defer PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion+"-dummy"))) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "PUT", root+"/-rev/dummy") + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "PUT", root+"/-rev/dummy") + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + t.Run("Version", func(t *testing.T) { + defer PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 2) + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename)) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + }) + + t.Run("Full", func(t *testing.T) { + defer PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + req := NewRequest(t, "DELETE", root+"/-rev/dummy") + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", root+"/-rev/dummy") + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 0) + }) + }) } diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 84bdce30fabad..39ba41cdfbf1f 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -198,12 +198,26 @@ func Routes() *web.Route { r.Group("/@{scope}/{id}", func() { r.Get("", npm.PackageMetadata) r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage) - r.Get("/-/{version}/{filename}", npm.DownloadPackageFile) + r.Group("/-/{version}/{filename}", func() { + r.Get("", npm.DownloadPackageFile) + r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) + }) + r.Group("/-rev/{revision}", func() { + r.Delete("", npm.DeletePackage) + r.Put("", npm.DeletePreview) + }, reqPackageAccess(perm.AccessModeWrite)) }) r.Group("/{id}", func() { r.Get("", npm.PackageMetadata) r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage) - r.Get("/-/{version}/{filename}", npm.DownloadPackageFile) + r.Group("/-/{version}/{filename}", func() { + r.Get("", npm.DownloadPackageFile) + r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion) + }) + r.Group("/-rev/{revision}", func() { + r.Delete("", npm.DeletePackage) + r.Put("", npm.DeletePreview) + }, reqPackageAccess(perm.AccessModeWrite)) }) r.Group("/-/package/@{scope}/{id}/dist-tags", func() { r.Get("", npm.ListPackageTags) diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 152edc681a048..d5ba70f9645bc 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -164,6 +164,63 @@ func UploadPackage(ctx *context.Context) { ctx.Status(http.StatusCreated) } +// DeletePreview does nothing +// The client tells the server what package version it knows about after deleting a version. +func DeletePreview(ctx *context.Context) { + ctx.Status(http.StatusOK) +} + +// DeletePackageVersion deletes the package version +func DeletePackageVersion(ctx *context.Context) { + packageName := packageNameFromParams(ctx) + packageVersion := ctx.Params("version") + + err := packages_service.RemovePackageVersionByNameAndVersion( + ctx.Doer, + &packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeNpm, + Name: packageName, + Version: packageVersion, + }, + ) + if err != nil { + if err == packages_model.ErrPackageNotExist { + apiError(ctx, http.StatusNotFound, err) + return + } + apiError(ctx, http.StatusInternalServerError, err) + return + } + + ctx.Status(http.StatusOK) +} + +// DeletePackage deletes the package and all versions +func DeletePackage(ctx *context.Context) { + packageName := packageNameFromParams(ctx) + + pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + if len(pvs) == 0 { + apiError(ctx, http.StatusNotFound, err) + return + } + + for _, pv := range pvs { + if err := packages_service.RemovePackageVersion(ctx.Doer, pv); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + } + + ctx.Status(http.StatusOK) +} + // ListPackageTags returns all tags for a package func ListPackageTags(ctx *context.Context) { packageName := packageNameFromParams(ctx)