From 54d9816502cb5709cfd7df9b43fb7b02c5c88033 Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 11 Aug 2022 00:20:58 +0000 Subject: [PATCH 01/46] [skip ci] Updated translations via Crowdin --- options/locale/locale_fi-FI.ini | 48 +++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 0323854339..2cf11f3b60 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -239,6 +239,7 @@ show_only_archived=Näytetään vain arkistoidut show_only_unarchived=Näytetään vain arkistoimattomat show_private=Yksityinen +show_both_private_public=Näytetään sekä julkiset että yksityiset show_only_private=Näytetään vain yksityiset show_only_public=Näytetään vain julkiset @@ -661,6 +662,7 @@ template_helper=Tee reposta mallipohja visibility=Näkyvyys visibility_description=Vain omistaja tai organisaation jäsenet, jos heillä on oikeudet, voivat nähdä sen. visibility_helper=Tee reposta yksityinen +visibility_helper_forced=Sivuston ylläpitäjä pakottaa uudet repot olemaan yksityisiä. fork_repo=Forkkaa repo fork_from=Forkkaa lähteestä fork_visibility_helper=Forkatun repon näkyvyyttä ei voi muuttaa. @@ -1087,6 +1089,7 @@ settings.danger_zone=Vaaravyöhyke settings.new_owner_has_same_repo=Uudella omistajalla on jo samanniminen repo. Ole hyvä ja valitse toinen nimi. settings.transfer=Siirrä omistajuus settings.transfer_form_title=Syötä repon nimi vahvistuksena: +settings.transfer_notices_3=- Jos arkisto on yksityinen ja se siirretään yksittäiselle käyttäjälle, tämä toiminto varmistaa, että käyttäjällä on ainakin lukuoikeudet (ja muuttaa käyttöoikeuksia tarvittaessa). settings.transfer_owner=Uusi omistaja settings.wiki_delete=Poista Wiki data settings.wiki_delete_desc=Repon wikin data poistaminen on pysyvä eikä voi peruuttaa. @@ -1140,14 +1143,24 @@ 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.deploy_keys=Julkaisuavaimet +settings.add_deploy_key=Lisää julkaisuavain +settings.deploy_key_desc=Julkaisuavaimilla on vain-luku oikeudet repoon. +settings.is_writable_info=Salli tämän julkaisuavaimen puskea repoon. +settings.no_deploy_keys=Julkaisuavaimia ei ole käytössä vielä. settings.title=Otsikko settings.deploy_key_content=Sisältö +settings.key_been_used=Julkaisuavain identtisellä sisällöllä on jo käytössä. +settings.key_name_used=Julkaisuavain samalla nimellä on jo olemassa. +settings.add_key_success=Julkaisuavain '%s' on lisätty. +settings.deploy_key_deletion=Poista julkaisuavain +settings.deploy_key_deletion_desc=Julkaisuavaimen poistaminen kumoaa sen pääsyn tähän repoon. Jatketaanko? +settings.deploy_key_deletion_success=Julkaisuavain on poistettu. settings.branches=Haarat settings.protected_branch=Haaran suojaus settings.branch_protection=Haaran '%s' suojaus settings.protect_this_branch=Ota haaran suojaus käyttöön +settings.protect_whitelist_deploy_keys=Lisää julkaisuavaimet sallittujen listalle mahdollistaaksesi repohin kirjoituksen. settings.protect_whitelist_users=Lista käyttäjistä joilla työntö oikeus: settings.protect_whitelist_search_users=Etsi käyttäjiä… settings.protect_merge_whitelist_committers_desc=Salli vain listaan merkittyjen käyttäjien ja tiimien yhdistää vetopyynnöt tähän haaraan. @@ -1171,7 +1184,28 @@ settings.matrix.access_token=Pääsymerkki settings.archive.button=Arkistoi repo settings.archive.header=Arkistoi tämä repo settings.lfs=LFS +settings.lfs_filelist=LFS-tiedostot tallennettu tähän repoon +settings.lfs_no_lfs_files=LFS-tiedostoja ei ole tallennettu tähän repoon. +settings.lfs_findcommits=Etsi commitit +settings.lfs_lfs_file_no_commits=LFS-tiedostolle ei löytynyt committeja +settings.lfs_noattribute=Tällä polulla ei ole lukittavaa attribuuttia oletushaarassa +settings.lfs_delete=Poista LFS-tiedosto OID:lla %s +settings.lfs_delete_warning=LFS-tiedoston poistaminen voi aiheuttaa 'object does not exists'-virheitä checkouttattaessa. Oletko varma? +settings.lfs_findpointerfiles=Etsi osoitintiedostoja +settings.lfs_locks=Lukot +settings.lfs_invalid_locking_path=Virheellinen polku: %s +settings.lfs_invalid_lock_directory=Hakemistoa ei voida lukita: %s +settings.lfs_lock_already_exists=Lukitus on jo olemassa: %s +settings.lfs_lock_path=Lukittavan tiedostopolku... +settings.lfs_locks_no_locks=Ei lukkoja +settings.lfs_lock_file_no_exist=Lukittua tiedostoa ei ole olemassa oletushaarassa +settings.lfs_force_unlock=Pakota lukituksen avaus +settings.lfs_pointers.found=Löytyi %d blob osoitinta - %d yhdistettyö, %d yhdistämätöntä (%d puuttuu varastosta) +settings.lfs_pointers.sha=Blob SHA settings.lfs_pointers.oid=OID +settings.lfs_pointers.inRepo=Repossa +settings.lfs_pointers.exists=Löytyy varastosta +settings.lfs_pointers.accessible=Saatavilla käyttäjälle diff.browse_source=Selaa lähdekoodia diff.parent=vanhempi @@ -1388,8 +1422,18 @@ users.allow_git_hook=Voi luoda Git koukkuja users.allow_create_organization=Voi luoda organisaatioita users.update_profile=Päivitä käyttäjän tili users.delete_account=Poista käyttäjän tili +users.list_status_filter.menu_text=Suodata +users.list_status_filter.reset=Tyhjennä +users.list_status_filter.is_active=Aktiivinen +users.list_status_filter.not_active=Ei-aktiivinen +users.list_status_filter.is_admin=Ylläpitäjä +users.list_status_filter.not_admin=Ei ylläpitäjä users.list_status_filter.is_restricted=Rajoitettu users.list_status_filter.not_restricted=Ei rajoitettu +users.list_status_filter.is_prohibit_login=Kirjautuminen estetty +users.list_status_filter.not_prohibit_login=Kirjautuminen sallittu +users.list_status_filter.is_2fa_enabled=2FA käytössä +users.list_status_filter.not_2fa_enabled=2FA ei käytössä emails.email_manage_panel=Käyttäjien sähköpostien hallinta emails.primary=Ensisijainen From 57f1ea03661945f75cf2ff11120eb07447692c99 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 11 Aug 2022 05:54:34 +0200 Subject: [PATCH 02/46] Fix loading button with invalid form (#20754) Previously, if a invalid form was submitted (for example issue with no title), the form could not be re-submitted again because the button would not stay stuck in loading state. Fix that by hooking the 'submit' event instead which triggers only when the form is valid. Co-authored-by: Lunny Xiao --- web_src/js/features/common-global.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 1776f6577d..259e409add 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -142,16 +142,12 @@ export function initGlobalCommon() { } }); - // loading-button this logic used to prevent push one form more than one time - $(document).on('click', '.button.loading-button', function (e) { - const $btn = $(this); - - if ($btn.hasClass('loading')) { - e.preventDefault(); - return false; - } - - $btn.addClass('loading disabled'); + // prevent multiple form submissions on forms containing .loading-button + document.addEventListener('submit', (e) => { + const btn = e.target.querySelector('.loading-button'); + if (!btn) return; + if (btn.classList.contains('loading')) return e.preventDefault(); + btn.classList.add('loading'); }); } From 2b4d43dd4d7388ff73e99c143008ad4663142e61 Mon Sep 17 00:00:00 2001 From: Kiel Hurley Date: Thu, 11 Aug 2022 17:04:09 +1200 Subject: [PATCH 03/46] Add SAML SP status to Feature Comparison docs (#20743) * Add SAML SP integration Add current SAML 2.0 Service Provider (SP) status. RhodeCode EE supports SAML, CE does not. Included issue links for both Gitea and Gogs, as corporate users will likely be interested in the status of both. * Add SAML SP status to comparison for other translations Co-authored-by: Lauris BH Co-authored-by: Lunny Xiao Co-authored-by: wxiaoguang --- docs/content/doc/features/comparison.en-us.md | 1 + docs/content/doc/features/comparison.zh-cn.md | 1 + docs/content/doc/features/comparison.zh-tw.md | 1 + 3 files changed, 3 insertions(+) diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md index 36180e3f5b..db13c87d5f 100644 --- a/docs/content/doc/features/comparison.en-us.md +++ b/docs/content/doc/features/comparison.en-us.md @@ -122,6 +122,7 @@ _Symbols used in table:_ | AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | +| SAML 2.0 service provider | [✘](https://github.com/go-gitea/gitea/issues/5512) | [✘](https://github.com/gogs/gogs/issues/1221) | ✓ | ✓ | ✓ | ✓ | ✘ | | OpenId Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | | OAuth 2.0 integration (external authorization) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✓ | | Act as OAuth 2.0 provider | [✓](https://github.com/go-gitea/gitea/pull/5378) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | diff --git a/docs/content/doc/features/comparison.zh-cn.md b/docs/content/doc/features/comparison.zh-cn.md index 15ff4a6560..f0eac22a26 100644 --- a/docs/content/doc/features/comparison.zh-cn.md +++ b/docs/content/doc/features/comparison.zh-cn.md @@ -120,6 +120,7 @@ _表格中的符号含义:_ | 集成 AD / LDAP | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | 支持多个 LDAP / AD 服务 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | LDAP 用户同步 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | +| SAML 2.0 service provider | [✘](https://github.com/go-gitea/gitea/issues/5512) | [✘](https://github.com/gogs/gogs/issues/1221) | ✓ | ✓ | ✓ | ✓ | ✘ | | 支持 OpenId 连接 | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | | 集成 OAuth 2.0(外部授权) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✓ | | 作为 OAuth 2.0 provider | [✓](https://github.com/go-gitea/gitea/pull/5378) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | diff --git a/docs/content/doc/features/comparison.zh-tw.md b/docs/content/doc/features/comparison.zh-tw.md index a2604c9052..015955f0a8 100644 --- a/docs/content/doc/features/comparison.zh-tw.md +++ b/docs/content/doc/features/comparison.zh-tw.md @@ -121,6 +121,7 @@ menu: | 整合 AD / LDAP | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | 支援多重 LDAP / AD 伺服器 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | 同步 LDAP 使用者 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | +| SAML 2.0 service provider | [✘](https://github.com/go-gitea/gitea/issues/5512) | [✘](https://github.com/gogs/gogs/issues/1221) | ✓ | ✓ | ✓ | ✓ | ✘ | | 支援 OpenId Connect | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | | 整合 OAuth 2.0 (外部驗證) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✓ | | 成為 OAuth 2.0 提供者 | [✓](https://github.com/go-gitea/gitea/pull/5378) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | From c81b26b0e52f55fcdf94b50a14bca3dfc375e2a9 Mon Sep 17 00:00:00 2001 From: oliverpool <3864879+oliverpool@users.noreply.github.com> Date: Thu, 11 Aug 2022 17:48:23 +0200 Subject: [PATCH 04/46] refactor webhook *NewPost (#20729) * refactor webhook *NewPost * remove empty values * always show errs.Message * remove utils.IsValidSlackChannel * move IsValidSlackChannel to services/webhook package * binding: handle empty Message case * make IsValidSlackChannel more strict --- modules/web/middleware/binding.go | 11 +- routers/api/v1/utils/hook.go | 6 +- routers/utils/utils.go | 19 - routers/utils/utils_test.go | 17 - routers/web/repo/webhook.go | 577 +++++++----------------------- services/forms/repo_form.go | 14 +- services/webhook/slack.go | 11 + services/webhook/slack_test.go | 19 + 8 files changed, 179 insertions(+), 495 deletions(-) diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 88a3920f6e..636e655b9e 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -136,7 +136,16 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl case validation.ErrRegexPattern: data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) default: - data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification + msg := errs[0].Classification + if msg != "" && errs[0].Message != "" { + msg += ": " + } + + msg += errs[0].Message + if msg == "" { + msg = l.Tr("form.unknown_error") + } + data["ErrorMsg"] = trName + ": " + msg } return errs } diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index f0dc595ad5..ba008f587c 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -15,7 +15,6 @@ import ( "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/routers/utils" webhook_service "code.gitea.io/gitea/services/webhook" ) @@ -141,14 +140,15 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel") return nil, false } + channel = strings.TrimSpace(channel) - if !utils.IsValidSlackChannel(channel) { + if !webhook_service.IsValidSlackChannel(channel) { ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name") return nil, false } meta, err := json.Marshal(&webhook_service.SlackMeta{ - Channel: strings.TrimSpace(channel), + Channel: channel, Username: form.Config["username"], IconURL: form.Config["icon_url"], Color: form.Config["color"], diff --git a/routers/utils/utils.go b/routers/utils/utils.go index f15bc1e62e..66eaa1d9ce 100644 --- a/routers/utils/utils.go +++ b/routers/utils/utils.go @@ -20,25 +20,6 @@ func RemoveUsernameParameterSuffix(name string) string { return name } -// IsValidSlackChannel validates a channel name conforms to what slack expects. -// It makes sure a channel name cannot be empty and invalid ( only an # ) -func IsValidSlackChannel(channelName string) bool { - switch len(strings.TrimSpace(channelName)) { - case 0: - return false - case 1: - // Keep default behaviour where a channel name is still - // valid without an # - // But if it contains only an #, it should be regarded as - // invalid - if channelName[0] == '#' { - return false - } - } - - return true -} - // SanitizeFlashErrorString will sanitize a flash error string func SanitizeFlashErrorString(x string) string { return strings.ReplaceAll(html.EscapeString(x), "\n", "
") diff --git a/routers/utils/utils_test.go b/routers/utils/utils_test.go index f49ed77b6f..42cf948e30 100644 --- a/routers/utils/utils_test.go +++ b/routers/utils/utils_test.go @@ -18,23 +18,6 @@ func TestRemoveUsernameParameterSuffix(t *testing.T) { assert.Equal(t, "", RemoveUsernameParameterSuffix("")) } -func TestIsValidSlackChannel(t *testing.T) { - tt := []struct { - channelName string - expected bool - }{ - {"gitea", true}, - {" ", false}, - {"#", false}, - {"gitea ", true}, - {" gitea", true}, - } - - for _, v := range tt { - assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName)) - } -} - func TestIsExternalURL(t *testing.T) { setting.AppURL = "https://try.gitea.io/" type test struct { diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index a9b14ee21f..d4419a1e10 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -185,14 +185,22 @@ func ParseHookEvent(form forms.WebhookForm) *webhook.HookEvent { } } -// GiteaHooksNewPost response for creating Gitea webhook -func GiteaHooksNewPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewWebhookForm) +type webhookCreationParams struct { + URL string + ContentType webhook.HookContentType + Secret string + HTTPMethod string + WebhookForm forms.WebhookForm + Type string + Meta interface{} +} + +func createWebhook(ctx *context.Context, params webhookCreationParams) { ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooksNew"] = true ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.GITEA + ctx.Data["HookType"] = params.Type orCtx, err := getOrgRepoCtx(ctx) if err != nil { @@ -206,20 +214,25 @@ func GiteaHooksNewPost(ctx *context.Context) { return } - contentType := webhook.ContentTypeJSON - if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { - contentType = webhook.ContentTypeForm + var meta []byte + if params.Meta != nil { + meta, err = json.Marshal(params.Meta) + if err != nil { + ctx.ServerError("Marshal", err) + return + } } w := &webhook.Webhook{ RepoID: orCtx.RepoID, - URL: form.PayloadURL, - HTTPMethod: form.HTTPMethod, - ContentType: contentType, - Secret: form.Secret, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.GITEA, + URL: params.URL, + HTTPMethod: params.HTTPMethod, + ContentType: params.ContentType, + Secret: params.Secret, + HookEvent: ParseHookEvent(params.WebhookForm), + IsActive: params.WebhookForm.Active, + Type: params.Type, + Meta: string(meta), OrgID: orCtx.OrgID, IsSystemWebhook: orCtx.IsSystemWebhook, } @@ -235,503 +248,175 @@ func GiteaHooksNewPost(ctx *context.Context) { ctx.Redirect(orCtx.Link) } +// GiteaHooksNewPost response for creating Gitea webhook +func GiteaHooksNewPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.NewWebhookForm) + + contentType := webhook.ContentTypeJSON + if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { + contentType = webhook.ContentTypeForm + } + + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: contentType, + Secret: form.Secret, + HTTPMethod: form.HTTPMethod, + WebhookForm: form.WebhookForm, + Type: webhook.GITEA, + }) +} + // GogsHooksNewPost response for creating webhook func GogsHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewGogshookForm) - newGogsWebhookPost(ctx, *form, webhook.GOGS) -} - -// newGogsWebhookPost response for creating gogs hook -func newGogsWebhookPost(ctx *context.Context, form forms.NewGogshookForm, kind webhook.HookType) { - ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.GOGS - - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - ctx.Data["BaseLink"] = orCtx.LinkNew - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } contentType := webhook.ContentTypeJSON if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { contentType = webhook.ContentTypeForm } - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: contentType, - Secret: form.Secret, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: kind, - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: contentType, + Secret: form.Secret, + WebhookForm: form.WebhookForm, + Type: webhook.GOGS, + }) } // DiscordHooksNewPost response for creating discord hook func DiscordHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewDiscordHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.DISCORD - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.DiscordMeta{ - Username: form.Username, - IconURL: form.IconURL, + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.DISCORD, + Meta: &webhook_service.DiscordMeta{ + Username: form.Username, + IconURL: form.IconURL, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.DISCORD, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // DingtalkHooksNewPost response for creating dingtalk hook func DingtalkHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewDingtalkHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.DINGTALK - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.DINGTALK, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.DINGTALK, + }) } // TelegramHooksNewPost response for creating telegram hook func TelegramHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewTelegramHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.TELEGRAM - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.TelegramMeta{ - BotToken: form.BotToken, - ChatID: form.ChatID, + createWebhook(ctx, webhookCreationParams{ + URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.TELEGRAM, + Meta: &webhook_service.TelegramMeta{ + BotToken: form.BotToken, + ChatID: form.ChatID, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.TELEGRAM, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // MatrixHooksNewPost response for creating a Matrix hook func MatrixHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewMatrixHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.MATRIX - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.MatrixMeta{ - HomeserverURL: form.HomeserverURL, - Room: form.RoomID, - AccessToken: form.AccessToken, - MessageType: form.MessageType, + createWebhook(ctx, webhookCreationParams{ + URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), + ContentType: webhook.ContentTypeJSON, + HTTPMethod: http.MethodPut, + WebhookForm: form.WebhookForm, + Type: webhook.MATRIX, + Meta: &webhook_service.MatrixMeta{ + HomeserverURL: form.HomeserverURL, + Room: form.RoomID, + AccessToken: form.AccessToken, + MessageType: form.MessageType, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), - ContentType: webhook.ContentTypeJSON, - HTTPMethod: "PUT", - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.MATRIX, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // MSTeamsHooksNewPost response for creating MS Teams hook func MSTeamsHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.MSTEAMS - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.MSTEAMS, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.MSTEAMS, + }) } // SlackHooksNewPost response for creating slack hook func SlackHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewSlackHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.SLACK - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - if form.HasInvalidChannel() { - ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name")) - ctx.Redirect(orCtx.LinkNew + "/slack/new") - return - } - - meta, err := json.Marshal(&webhook_service.SlackMeta{ - Channel: strings.TrimSpace(form.Channel), - Username: form.Username, - IconURL: form.IconURL, - Color: form.Color, + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.SLACK, + Meta: &webhook_service.SlackMeta{ + Channel: strings.TrimSpace(form.Channel), + Username: form.Username, + IconURL: form.IconURL, + Color: form.Color, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.SLACK, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // FeishuHooksNewPost response for creating feishu hook func FeishuHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewFeishuHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.FEISHU - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.FEISHU, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.FEISHU, + }) } // WechatworkHooksNewPost response for creating wechatwork hook func WechatworkHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.WECHATWORK - - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.WECHATWORK, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.WECHATWORK, + }) } // PackagistHooksNewPost response for creating packagist hook func PackagistHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewPackagistHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.PACKAGIST - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.PackagistMeta{ - Username: form.Username, - APIToken: form.APIToken, - PackageURL: form.PackageURL, + createWebhook(ctx, webhookCreationParams{ + URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.PACKAGIST, + Meta: &webhook_service.PackagistMeta{ + Username: form.Username, + APIToken: form.APIToken, + PackageURL: form.PackageURL, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.PACKAGIST, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { @@ -894,12 +579,6 @@ func SlackHooksEditPost(ctx *context.Context) { return } - if form.HasInvalidChannel() { - ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) - return - } - meta, err := json.Marshal(&webhook_service.SlackMeta{ Channel: strings.TrimSpace(form.Channel), Username: form.Username, diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index afecc205f3..7a4a2123eb 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -17,7 +17,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/routers/utils" + "code.gitea.io/gitea/services/webhook" "gitea.com/go-chi/binding" ) @@ -305,14 +305,16 @@ type NewSlackHookForm struct { // Validate validates the fields func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetContext(req) + if !webhook.IsValidSlackChannel(strings.TrimSpace(f.Channel)) { + errs = append(errs, binding.Error{ + FieldNames: []string{"Channel"}, + Classification: "", + Message: ctx.Tr("repo.settings.add_webhook.invalid_channel_name"), + }) + } return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// HasInvalidChannel validates the channel name is in the right format -func (f NewSlackHookForm) HasInvalidChannel() bool { - return !utils.IsValidSlackChannel(f.Channel) -} - // NewDiscordHookForm form for creating discord hook type NewDiscordHookForm struct { PayloadURL string `binding:"Required;ValidUrl"` diff --git a/services/webhook/slack.go b/services/webhook/slack.go index 11e1d3c081..e3d0d406de 100644 --- a/services/webhook/slack.go +++ b/services/webhook/slack.go @@ -7,6 +7,7 @@ package webhook import ( "errors" "fmt" + "regexp" "strings" webhook_model "code.gitea.io/gitea/models/webhook" @@ -286,3 +287,13 @@ func GetSlackPayload(p api.Payloader, event webhook_model.HookEventType, meta st return convertPayloader(s, p, event) } + +var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`) + +// IsValidSlackChannel validates a channel name conforms to what slack expects: +// https://api.slack.com/methods/conversations.rename#naming +// Conversation names can only contain lowercase letters, numbers, hyphens, and underscores, and must be 80 characters or less. +// Gitea accepts if it starts with a #. +func IsValidSlackChannel(name string) bool { + return slackChannel.MatchString(name) +} diff --git a/services/webhook/slack_test.go b/services/webhook/slack_test.go index 8278afb69a..0f08785d25 100644 --- a/services/webhook/slack_test.go +++ b/services/webhook/slack_test.go @@ -170,3 +170,22 @@ func TestSlackJSONPayload(t *testing.T) { require.NoError(t, err) assert.NotEmpty(t, json) } + +func TestIsValidSlackChannel(t *testing.T) { + tt := []struct { + channelName string + expected bool + }{ + {"gitea", true}, + {"#gitea", true}, + {" ", false}, + {"#", false}, + {" #", false}, + {"gitea ", false}, + {" gitea", false}, + } + + for _, v := range tt { + assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName)) + } +} From d26b652260fa50c14e0f0202b3f91b4b8d899671 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 12 Aug 2022 13:16:05 +0800 Subject: [PATCH 05/46] Fix disabled open in vscode menu when disabling download source from UI (#20713) --- templates/repo/home.tmpl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index d7ce2240a0..5c3eec8ab0 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -115,19 +115,19 @@ {{if eq $n 0}} {{end}} {{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }} From d30e02255f531200e57735b8b69fa389cbfad97a Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 12 Aug 2022 19:51:33 +0200 Subject: [PATCH 06/46] Slightly reduce exclamation icon size (#20753) It seemed a tad to big compared to other icons. Shrink it slightly. Co-authored-by: Lauris BH Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick --- public/img/svg/gitea-exclamation.svg | 2 +- web_src/svg/gitea-exclamation.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/img/svg/gitea-exclamation.svg b/public/img/svg/gitea-exclamation.svg index 22176010dc..d6c86136b3 100644 --- a/public/img/svg/gitea-exclamation.svg +++ b/public/img/svg/gitea-exclamation.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/web_src/svg/gitea-exclamation.svg b/web_src/svg/gitea-exclamation.svg index c83cb4158e..b4b8795382 100644 --- a/web_src/svg/gitea-exclamation.svg +++ b/web_src/svg/gitea-exclamation.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 74515d3d17c578ee0083111951188cb159fd049b Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 12 Aug 2022 22:13:31 +0200 Subject: [PATCH 07/46] Remove debug output when coverage fails (#20733) * Remove debug output when coverage fails When coverage fails, it logs megabytes of debug to stdout, which seems to break the drone ui as well as the log output download in drone, presumably because of the size. I think with removal of this print, we should still see any errors created by gocovmerge.go, but a few CI runs may be necessary to get it to fail again. * Update Makefile * restart ci * restart ci * restart ci * restart ci Co-authored-by: techknowlogick --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 77aac782e1..44e245d225 100644 --- a/Makefile +++ b/Makefile @@ -364,7 +364,7 @@ test\#%: coverage: grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out - $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all || (echo "gocovmerge failed"; echo "integration.coverage.out"; cat integration.coverage.out; echo "coverage.out"; cat coverage.out; exit 1) + $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all .PHONY: unit-test-coverage unit-test-coverage: From 20b3a904504d401cfc192b80440d76d787af94d9 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 12 Aug 2022 23:03:41 +0200 Subject: [PATCH 08/46] Remove useless JS operation for relative time tooltips (#20756) This operation that shifts the content from title to data-content is useless when we can directly render the expected HTML instead. This change does prevent these tooltips from working when the user has JS disabled in their browser, but I think we made it clear by now that JS is required for gitea to work properly. Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick --- integrations/repo_test.go | 2 +- modules/timeutil/since.go | 4 ++-- modules/timeutil/since_test.go | 2 +- web_src/js/features/common-global.js | 8 -------- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/integrations/repo_test.go b/integrations/repo_test.go index 872d3f24d1..c2ac6183f0 100644 --- a/integrations/repo_test.go +++ b/integrations/repo_test.go @@ -64,7 +64,7 @@ func testViewRepo(t *testing.T) { } }) - f.commitTime, _ = s.Find("span.time-since").Attr("title") + f.commitTime, _ = s.Find("span.time-since").Attr("data-content") items = append(items, f) }) diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index 5e89b0faa2..8473d45051 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -234,7 +234,7 @@ func TimeSince(then time.Time, lang translation.Locale) template.HTML { } func htmlTimeSince(then, now time.Time, lang translation.Locale) template.HTML { - return template.HTML(fmt.Sprintf(`%s`, + return template.HTML(fmt.Sprintf(`%s`, then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang.Language())), timeSince(then, now, lang))) } @@ -245,7 +245,7 @@ func TimeSinceUnix(then TimeStamp, lang translation.Locale) template.HTML { } func htmlTimeSinceUnix(then, now TimeStamp, lang translation.Locale) template.HTML { - return template.HTML(fmt.Sprintf(`%s`, + return template.HTML(fmt.Sprintf(`%s`, then.FormatInLocation(GetTimeFormat(lang.Language()), setting.DefaultUILocation), timeSinceUnix(int64(then), int64(now), lang))) } diff --git a/modules/timeutil/since_test.go b/modules/timeutil/since_test.go index 8bdb9d7546..dfcf9cb01d 100644 --- a/modules/timeutil/since_test.go +++ b/modules/timeutil/since_test.go @@ -119,7 +119,7 @@ func TestHtmlTimeSince(t *testing.T) { // test that `diff` yields a result containing `expected` test := func(expected string, diff time.Duration) { actual := htmlTimeSince(BaseDate, BaseDate.Add(diff), translation.NewLocale("en-US")) - assert.Contains(t, actual, `title="Sat Jan 1 00:00:00 UTC 2000"`) + assert.Contains(t, actual, `data-content="Sat Jan 1 00:00:00 UTC 2000"`) assert.Contains(t, actual, expected) } test("1 second", time.Second) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 259e409add..a3aebc0246 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -70,14 +70,6 @@ export function initGlobalTooltips() { } export function initGlobalCommon() { - // Show exact time - $('.time-since').each(function () { - $(this) - .addClass('tooltip') - .attr('data-content', $(this).attr('title')) - .attr('title', ''); - }); - // Undo Safari emoji glitch fix at high enough zoom levels if (navigator.userAgent.match('Safari')) { $(window).resize(() => { From bbce94ee9131d5c6861dc343800469f7b0689d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?PEN=C2=B2?= Date: Sat, 13 Aug 2022 09:34:53 +0800 Subject: [PATCH 09/46] Move the official website link at the footer of gitea (#20777) --- templates/base/footer_content.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index dfc0646610..88dec014f6 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -1,7 +1,7 @@