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 9ce6fe1a13eb..13461915e6f3 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 854190462bc3..1e256aa8650c 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 88406da5df9d..d9544bd30177 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 000000000000..cfacfe737e0f --- /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 8d4b0d20cbfd..cfacfe737e0f 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 16fff85a98cf..bfcd6f3c440a 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 000000000000..cfacfe737e0f --- /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 000000000000..cfacfe737e0f --- /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 52fae9defb8f..f6243d1e8f47 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 ef3c74a90d40..335f95571372 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 736fcaee106f..90fe96fe5d9c 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 882491161992..0b9898acef04 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 4a85692a8315..09799fbece57 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 484d0a811066..0f00e9284a47 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 9242d0c5ca68..ccfb40684c3f 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 d1555c16c554..f4e0674be407 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 15222272b334..be3c2fbb34bf 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 4f47d8afa64a..0323854339b5 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 ac7da8dd1760..acc2fad7e833 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 50b92a95c58b..b0bb9e1b16cc 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 07565395cdbd..d33713c684f3 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 ef239d0e3fbf..5d8e427a9125 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 24f2c41119c1..d6b3c05a4fcb 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 3f16c6e2a600..1a15dd2941c5 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 f6d29f3c5b57..65762a91e3b8 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 d6bd673822ba..f9e1eafb6583 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 230f780175c9..3c379f5c84ef 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 4b6bb140d3ad..14a8ac253e2c 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 20a5c903a92c..80093d66f1ed 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 46782f24a100..53e4f3539564 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 7f9af553747c..58656f781f13 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 2da91cdcc35f..46369290a13d 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 24175fcaf4be..2d2bcb910db4 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 b8317396c015..99849f1f0945 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 c5a63de0644c..091573787871 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 c5d8354b6dc8..000000000000 --- 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 e640ca48453b..612fc5a83dbb 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 a82058da8ac5..dd0f5653fff8 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 adaf99e981ae..c60a387f533b 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 4b274860dcdf..84bdce30faba 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 9a3a185d9da5..79e5afb03cae 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 9ab4ac900cfb..122f306ee5e3 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 ad88ac5da676..23f13866bf50 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 84bdce30faba..39ba41cdfbf1 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 152edc681a04..d5ba70f9645b 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)