loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request '[gitea] cherry-pick' (#2397) from earl-warren/forgejo:wip-gitea-cherry-pick into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2397
Reviewed-by: Gusted <gusted@noreply.codeberg.org>

+794 -334
+2 -2
Makefile
··· 156 156 FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml 157 157 158 158 SWAGGER_SPEC := templates/swagger/v1_json.tmpl 159 - SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g 160 - SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g 159 + SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g 160 + SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g 161 161 SWAGGER_EXCLUDE := code.gitea.io/sdk 162 162 SWAGGER_NEWLINE_COMMAND := -e '$$a\' 163 163 SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g
+3 -1
modules/actions/github.go
··· 55 55 case webhook_module.HookEventPullRequest, 56 56 webhook_module.HookEventPullRequestSync, 57 57 webhook_module.HookEventPullRequestAssign, 58 - webhook_module.HookEventPullRequestLabel: 58 + webhook_module.HookEventPullRequestLabel, 59 + webhook_module.HookEventPullRequestReviewRequest, 60 + webhook_module.HookEventPullRequestMilestone: 59 61 return true 60 62 61 63 default:
+5 -3
modules/actions/workflows.go
··· 186 186 webhook_module.HookEventPullRequest, 187 187 webhook_module.HookEventPullRequestSync, 188 188 webhook_module.HookEventPullRequestAssign, 189 - webhook_module.HookEventPullRequestLabel: 189 + webhook_module.HookEventPullRequestLabel, 190 + webhook_module.HookEventPullRequestReviewRequest, 191 + webhook_module.HookEventPullRequestMilestone: 190 192 return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt) 191 193 192 194 case // pull_request_review ··· 362 364 } else { 363 365 // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request 364 366 // Actions with the same name: 365 - // opened, edited, closed, reopened, assigned, unassigned 367 + // opened, edited, closed, reopened, assigned, unassigned, review_requested, review_request_removed, milestoned, demilestoned 366 368 // Actions need to be converted: 367 369 // synchronized -> synchronize 368 370 // label_updated -> labeled 369 371 // label_cleared -> unlabeled 370 372 // Unsupported activity types: 371 - // converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled 373 + // converted_to_draft, ready_for_review, locked, unlocked, auto_merge_enabled, auto_merge_disabled, enqueued, dequeued 372 374 373 375 action := prPayload.Action 374 376 switch action {
+14
modules/context/context_response.go
··· 90 90 } 91 91 } 92 92 93 + // JSONTemplate renders the template as JSON response 94 + // keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape 95 + func (ctx *Context) JSONTemplate(tmpl base.TplName) { 96 + t, err := ctx.Render.TemplateLookup(string(tmpl), nil) 97 + if err != nil { 98 + ctx.ServerError("unable to find template", err) 99 + return 100 + } 101 + ctx.Resp.Header().Set("Content-Type", "application/json") 102 + if err = t.Execute(ctx.Resp, ctx.Data); err != nil { 103 + ctx.ServerError("unable to execute template", err) 104 + } 105 + } 106 + 93 107 // RenderToString renders the template content to a string 94 108 func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) { 95 109 var buf strings.Builder
-14
modules/context/context_template.go
··· 5 5 6 6 import ( 7 7 "context" 8 - "errors" 9 8 "time" 10 - 11 - "code.gitea.io/gitea/modules/log" 12 9 ) 13 10 14 11 var _ context.Context = TemplateContext(nil) ··· 36 33 func (c TemplateContext) Value(key any) any { 37 34 return c.parentContext().Value(key) 38 35 } 39 - 40 - // DataRaceCheck checks whether the template context function "ctx()" returns the consistent context 41 - // as the current template's rendering context (request context), to help to find data race issues as early as possible. 42 - // When the code is proven to be correct and stable, this function should be removed. 43 - func (c TemplateContext) DataRaceCheck(dataCtx context.Context) (string, error) { 44 - if c.parentContext() != dataCtx { 45 - log.Error("TemplateContext.DataRaceCheck: parent context mismatch\n%s", log.Stack(2)) 46 - return "", errors.New("parent context mismatch") 47 - } 48 - return "", nil 49 - }
+5 -1
modules/templates/helper.go
··· 38 38 "Safe": Safe, 39 39 "Escape": Escape, 40 40 "QueryEscape": url.QueryEscape, 41 - "JSEscape": template.JSEscapeString, 41 + "JSEscape": JSEscapeSafe, 42 42 "Str2html": Str2html, // TODO: rename it to SanitizeHTML 43 43 "URLJoin": util.URLJoin, 44 44 "DotEscape": DotEscape, ··· 212 212 return v 213 213 } 214 214 panic(fmt.Sprintf("unexpected type %T", s)) 215 + } 216 + 217 + func JSEscapeSafe(s string) template.HTML { 218 + return template.HTML(template.JSEscapeString(s)) 215 219 } 216 220 217 221 func RenderEmojiPlain(s any) any {
+4
modules/templates/helper_test.go
··· 52 52 "", 53 53 "Insuficient\n--\nSeparators") 54 54 } 55 + 56 + func TestJSEscapeSafe(t *testing.T) { 57 + assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`)) 58 + }
+66
modules/translation/i18n/i18n_test.go
··· 4 4 package i18n 5 5 6 6 import ( 7 + "html/template" 7 8 "strings" 8 9 "testing" 9 10 ··· 80 81 assert.Equal(t, "11", lang1.TrString("a")) 81 82 assert.Equal(t, "21", lang1.TrString("b")) 82 83 assert.Equal(t, "22", lang1.TrString("c")) 84 + } 85 + 86 + type stringerPointerReceiver struct { 87 + s string 88 + } 89 + 90 + func (s *stringerPointerReceiver) String() string { 91 + return s.s 92 + } 93 + 94 + type stringerStructReceiver struct { 95 + s string 96 + } 97 + 98 + func (s stringerStructReceiver) String() string { 99 + return s.s 100 + } 101 + 102 + type errorStructReceiver struct { 103 + s string 104 + } 105 + 106 + func (e errorStructReceiver) Error() string { 107 + return e.s 108 + } 109 + 110 + type errorPointerReceiver struct { 111 + s string 112 + } 113 + 114 + func (e *errorPointerReceiver) Error() string { 115 + return e.s 116 + } 117 + 118 + func TestLocaleWithTemplate(t *testing.T) { 119 + ls := NewLocaleStore() 120 + assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", []byte(`key=<a>%s</a>`), nil)) 121 + lang1, _ := ls.Locale("lang1") 122 + 123 + tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML}) 124 + tmpl = template.Must(tmpl.Parse(`{{tr "key" .var}}`)) 125 + 126 + cases := []struct { 127 + in any 128 + want string 129 + }{ 130 + {"<str>", "<a>&lt;str&gt;</a>"}, 131 + {[]byte("<bytes>"), "<a>[60 98 121 116 101 115 62]</a>"}, 132 + {template.HTML("<html>"), "<a><html></a>"}, 133 + {stringerPointerReceiver{"<stringerPointerReceiver>"}, "<a>{&lt;stringerPointerReceiver&gt;}</a>"}, 134 + {&stringerPointerReceiver{"<stringerPointerReceiver ptr>"}, "<a>&lt;stringerPointerReceiver ptr&gt;</a>"}, 135 + {stringerStructReceiver{"<stringerStructReceiver>"}, "<a>&lt;stringerStructReceiver&gt;</a>"}, 136 + {&stringerStructReceiver{"<stringerStructReceiver ptr>"}, "<a>&lt;stringerStructReceiver ptr&gt;</a>"}, 137 + {errorStructReceiver{"<errorStructReceiver>"}, "<a>&lt;errorStructReceiver&gt;</a>"}, 138 + {&errorStructReceiver{"<errorStructReceiver ptr>"}, "<a>&lt;errorStructReceiver ptr&gt;</a>"}, 139 + {errorPointerReceiver{"<errorPointerReceiver>"}, "<a>{&lt;errorPointerReceiver&gt;}</a>"}, 140 + {&errorPointerReceiver{"<errorPointerReceiver ptr>"}, "<a>&lt;errorPointerReceiver ptr&gt;</a>"}, 141 + } 142 + 143 + buf := &strings.Builder{} 144 + for _, c := range cases { 145 + buf.Reset() 146 + assert.NoError(t, tmpl.Execute(buf, map[string]any{"var": c.in})) 147 + assert.Equal(t, c.want, buf.String()) 148 + } 83 149 } 84 150 85 151 func TestLocaleStoreQuirks(t *testing.T) {
+5 -3
modules/translation/i18n/localestore.go
··· 136 136 args := slices.Clone(trArgs) 137 137 for i, v := range args { 138 138 switch v := v.(type) { 139 + case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML: 140 + // for most basic types (including template.HTML which is safe), just do nothing and use it 139 141 case string: 140 - args[i] = template.HTML(template.HTMLEscapeString(v)) 142 + args[i] = template.HTMLEscapeString(v) 141 143 case fmt.Stringer: 142 144 args[i] = template.HTMLEscapeString(v.String()) 143 - default: // int, float, include template.HTML 144 - // do nothing, just use it 145 + default: 146 + args[i] = template.HTMLEscapeString(fmt.Sprint(v)) 145 147 } 146 148 } 147 149 return template.HTML(l.TrString(trKey, args...))
+17
options/license/Brian-Gladman-2-Clause
··· 1 + Copyright (C) 1998-2013, Brian Gladman, Worcester, UK. All 2 + rights reserved. 3 + 4 + The redistribution and use of this software (with or without 5 + changes) is allowed without the payment of fees or royalties 6 + provided that: 7 + 8 + source code distributions include the above copyright notice, 9 + this list of conditions and the following disclaimer; 10 + 11 + binary distributions include the above copyright notice, this 12 + list of conditions and the following disclaimer in their 13 + documentation. 14 + 15 + This software is provided 'as is' with no explicit or implied 16 + warranties in respect of its operation, including, but not limited 17 + to, correctness and fitness for purpose.
+11
options/license/CMU-Mach-nodoc
··· 1 + Copyright (C) 2002 Naval Research Laboratory (NRL/CCS) 2 + 3 + Permission to use, copy, modify and distribute this software and 4 + its documentation is hereby granted, provided that both the 5 + copyright notice and this permission notice appear in all copies of 6 + the software, derivative works or modified versions, and any 7 + portions thereof. 8 + 9 + NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND 10 + DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER 11 + RESULTING FROM THE USE OF THIS SOFTWARE.
+1
options/license/GNOME-examples-exception
··· 1 + As a special exception, the copyright holders give you permission to copy, modify, and distribute the example code contained in this document under the terms of your choosing, without restriction.
+16
options/license/Gmsh-exception
··· 1 + The copyright holders of Gmsh give you permission to combine Gmsh 2 + with code included in the standard release of Netgen (from Joachim 3 + Sch"oberl), METIS (from George Karypis at the University of 4 + Minnesota), OpenCASCADE (from Open CASCADE S.A.S) and ParaView 5 + (from Kitware, Inc.) under their respective licenses. You may copy 6 + and distribute such a system following the terms of the GNU GPL for 7 + Gmsh and the licenses of the other code concerned, provided that 8 + you include the source code of that other code when and as the GNU 9 + GPL requires distribution of source code. 10 + 11 + Note that people who make modified versions of Gmsh are not 12 + obligated to grant this special exception for their modified 13 + versions; it is their choice whether to do so. The GNU General 14 + Public License gives permission to release a modified version 15 + without this exception; this exception also makes it possible to 16 + release a modified version which carries forward this exception.
+13
options/license/HPND-Fenneberg-Livingston
··· 1 + Copyright (C) 1995,1996,1997,1998 Lars Fenneberg <lf@elemental.net> 2 + 3 + Permission to use, copy, modify, and distribute this software for any 4 + purpose and without fee is hereby granted, provided that this copyright and 5 + permission notice appear on all copies and supporting documentation, the 6 + name of Lars Fenneberg not be used in advertising or publicity pertaining to 7 + distribution of the program without specific prior permission, and notice be 8 + given in supporting documentation that copying and distribution is by 9 + permission of Lars Fenneberg. 10 + 11 + Lars Fenneberg makes no representations about the suitability of this 12 + software for any purpose. It is provided "as is" without express or implied 13 + warranty.
+9
options/license/HPND-INRIA-IMAG
··· 1 + This software is available with usual "research" terms with 2 + the aim of retain credits of the software. Permission to use, 3 + copy, modify and distribute this software for any purpose and 4 + without fee is hereby granted, provided that the above copyright 5 + notice and this permission notice appear in all copies, and 6 + the name of INRIA, IMAG, or any contributor not be used in 7 + advertising or publicity pertaining to this material without 8 + the prior explicit permission. The software is provided "as 9 + is" without any warranties, support or liabilities of any kind.
+25
options/license/Mackerras-3-Clause
··· 1 + Copyright (c) 1995 Eric Rosenquist. All rights reserved. 2 + 3 + Redistribution and use in source and binary forms, with or without 4 + modification, are permitted provided that the following conditions 5 + are met: 6 + 7 + 1. Redistributions of source code must retain the above copyright 8 + notice, this list of conditions and the following disclaimer. 9 + 10 + 2. Redistributions in binary form must reproduce the above copyright 11 + notice, this list of conditions and the following disclaimer in 12 + the documentation and/or other materials provided with the 13 + distribution. 14 + 15 + 3. The name(s) of the authors of this software must not be used to 16 + endorse or promote products derived from this software without 17 + prior written permission. 18 + 19 + THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO 20 + THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 21 + AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY 22 + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 24 + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 25 + OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+25
options/license/Mackerras-3-Clause-acknowledgment
··· 1 + Copyright (c) 1993-2002 Paul Mackerras. All rights reserved. 2 + 3 + Redistribution and use in source and binary forms, with or without 4 + modification, are permitted provided that the following conditions 5 + are met: 6 + 7 + 1. Redistributions of source code must retain the above copyright 8 + notice, this list of conditions and the following disclaimer. 9 + 10 + 2. The name(s) of the authors of this software must not be used to 11 + endorse or promote products derived from this software without 12 + prior written permission. 13 + 14 + 3. Redistributions of any form whatsoever must retain the following 15 + acknowledgment: 16 + "This product includes software developed by Paul Mackerras 17 + <paulus@ozlabs.org>". 18 + 19 + THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO 20 + THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 21 + AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY 22 + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 24 + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 25 + OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+33
options/license/OpenVision
··· 1 + Copyright, OpenVision Technologies, Inc., 1993-1996, All Rights 2 + Reserved 3 + 4 + WARNING: Retrieving the OpenVision Kerberos Administration system 5 + source code, as described below, indicates your acceptance of the 6 + following terms. If you do not agree to the following terms, do 7 + not retrieve the OpenVision Kerberos administration system. 8 + 9 + You may freely use and distribute the Source Code and Object Code 10 + compiled from it, with or without modification, but this Source 11 + Code is provided to you "AS IS" EXCLUSIVE OF ANY WARRANTY, 12 + INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY OR 13 + FITNESS FOR A PARTICULAR PURPOSE, OR ANY OTHER WARRANTY, WHETHER 14 + EXPRESS OR IMPLIED. IN NO EVENT WILL OPENVISION HAVE ANY LIABILITY 15 + FOR ANY LOST PROFITS, LOSS OF DATA OR COSTS OF PROCUREMENT OF 16 + SUBSTITUTE GOODS OR SERVICES, OR FOR ANY SPECIAL, INDIRECT, OR 17 + CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, INCLUDING, 18 + WITHOUT LIMITATION, THOSE RESULTING FROM THE USE OF THE SOURCE 19 + CODE, OR THE FAILURE OF THE SOURCE CODE TO PERFORM, OR FOR ANY 20 + OTHER REASON. 21 + 22 + OpenVision retains all copyrights in the donated Source Code. 23 + OpenVision also retains copyright to derivative works of the Source 24 + Code, whether created by OpenVision or by a third party. The 25 + OpenVision copyright notice must be preserved if derivative works 26 + are made based on the donated Source Code. 27 + 28 + OpenVision Technologies, Inc. has donated this Kerberos 29 + Administration system to MIT for inclusion in the standard Kerberos 30 + 5 distribution. This donation underscores our commitment to 31 + continuing Kerberos technology development and our gratitude for 32 + the valuable work which has been performed by MIT and the Kerberos 33 + community.
+13
options/license/Sun-PPP
··· 1 + Copyright (c) 2001 by Sun Microsystems, Inc. 2 + All rights reserved. 3 + 4 + Non-exclusive rights to redistribute, modify, translate, and use 5 + this software in source and binary forms, in whole or in part, is 6 + hereby granted, provided that the above copyright notice is 7 + duplicated in any source form, and that neither the name of the 8 + copyright holder nor the author is used to endorse or promote 9 + products derived from this software. 10 + 11 + THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 12 + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 13 + WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+19
options/license/UMich-Merit
··· 1 + [C] The Regents of the University of Michigan and Merit Network, Inc. 1992, 2 + 1993, 1994, 1995 All Rights Reserved 3 + 4 + Permission to use, copy, modify, and distribute this software and its 5 + documentation for any purpose and without fee is hereby granted, provided 6 + that the above copyright notice and this permission notice appear in all 7 + copies of the software and derivative works or modified versions thereof, 8 + and that both the copyright notice and this permission and disclaimer 9 + notice appear in supporting documentation. 10 + 11 + THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 12 + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF 13 + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE 14 + UNIVERSITY OF MICHIGAN AND MERIT NETWORK, INC. DO NOT WARRANT THAT THE 15 + FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR 16 + THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the 17 + University of Michigan and Merit Network, Inc. shall not be liable for any 18 + special, indirect, incidental or consequential damages with respect to any 19 + claim by Licensee or any third party arising from use of the software.
+11
options/license/bcrypt-Solar-Designer
··· 1 + Written by Solar Designer <solar at openwall.com> in 1998-2014. 2 + No copyright is claimed, and the software is hereby placed in the public 3 + domain. In case this attempt to disclaim copyright and place the software 4 + in the public domain is deemed null and void, then the software is 5 + Copyright (c) 1998-2014 Solar Designer and it is hereby released to the 6 + general public under the following terms: 7 + 8 + Redistribution and use in source and binary forms, with or without 9 + modification, are permitted. 10 + 11 + There's ABSOLUTELY NO WARRANTY, express or implied.
+6
options/license/gtkbook
··· 1 + Copyright 2005 Syd Logan, All Rights Reserved 2 + 3 + This code is distributed without warranty. You are free to use 4 + this code for any purpose, however, if this code is republished or 5 + redistributed in its original form, as hardcopy or electronically, 6 + then you must include this copyright notice along with the code.
+6
options/license/softSurfer
··· 1 + Copyright 2001, softSurfer (www.softsurfer.com) 2 + This code may be freely used and modified for any purpose 3 + providing that this copyright notice is included with it. 4 + SoftSurfer makes no warranty for this code, and cannot be held 5 + liable for any real or imagined damage resulting from its use. 6 + Users of this code must verify correctness for their application.
+7 -5
options/locale/locale_en-US.ini
··· 2023 2023 activity.git_stats_deletion_1 = %d deletion 2024 2024 activity.git_stats_deletion_n = %d deletions 2025 2025 2026 - contributors = Contributors 2027 2026 contributors.contribution_type.filter_label = Contribution type: 2028 2027 contributors.contribution_type.commits = Commits 2029 2028 contributors.contribution_type.additions = Additions 2030 2029 contributors.contribution_type.deletions = Deletions 2031 - contributors.loading_title = Loading contributions... 2032 - contributors.loading_title_failed = Could not load contributions 2033 - contributors.loading_info = This might take a bit… 2034 - contributors.component_failed_to_load = An unexpected error happened. 2035 2030 2036 2031 search = Search 2037 2032 search.search_repo = Search repository ··· 2651 2646 error.csv.too_large = Can't render this file because it is too large. 2652 2647 error.csv.unexpected = Can't render this file because it contains an unexpected character in line %d and column %d. 2653 2648 error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d. 2649 + 2650 + [graphs] 2651 + component_loading = Loading %s... 2652 + component_loading_failed = Could not load %s 2653 + component_loading_info = This might take a bit… 2654 + component_failed_to_load = An unexpected error happened. 2655 + contributors.what = contributions 2654 2656 2655 2657 [org] 2656 2658 org_name_holder = Organization Name
+1 -1
routers/api/v1/api.go
··· 8 8 // 9 9 // Schemes: https, http 10 10 // BasePath: /api/v1 11 - // Version: {{AppVer | JSEscape | Safe}} 11 + // Version: {{AppVer | JSEscape}} 12 12 // License: MIT http://opensource.org/licenses/MIT 13 13 // 14 14 // Consumes:
+1 -9
routers/web/auth/oauth.go
··· 579 579 580 580 // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities 581 581 func OIDCWellKnown(ctx *context.Context) { 582 - t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown", nil) 583 - if err != nil { 584 - ctx.ServerError("unable to find template", err) 585 - return 586 - } 587 - ctx.Resp.Header().Set("Content-Type", "application/json") 588 582 ctx.Data["SigningKey"] = oauth2.DefaultSigningKey 589 - if err = t.Execute(ctx.Resp, ctx.Data); err != nil { 590 - ctx.ServerError("unable to execute template", err) 591 - } 583 + ctx.JSONTemplate("user/auth/oidc_wellknown") 592 584 } 593 585 594 586 // OIDCKeys generates the JSON Web Key Set
+1 -1
routers/web/repo/contributors.go
··· 18 18 19 19 // Contributors render the page to show repository contributors graph 20 20 func Contributors(ctx *context.Context) { 21 - ctx.Data["Title"] = ctx.Tr("repo.contributors") 21 + ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.contributors") 22 22 23 23 ctx.Data["PageIsActivity"] = true 24 24 ctx.Data["PageIsContributors"] = true
+30
routers/web/repo/pull.go
··· 662 662 } 663 663 664 664 if pb != nil && pb.EnableStatusCheck { 665 + 666 + var missingRequiredChecks []string 667 + for _, requiredContext := range pb.StatusCheckContexts { 668 + contextFound := false 669 + matchesRequiredContext := createRequiredContextMatcher(requiredContext) 670 + for _, presentStatus := range commitStatuses { 671 + if matchesRequiredContext(presentStatus.Context) { 672 + contextFound = true 673 + break 674 + } 675 + } 676 + 677 + if !contextFound { 678 + missingRequiredChecks = append(missingRequiredChecks, requiredContext) 679 + } 680 + } 681 + ctx.Data["MissingRequiredChecks"] = missingRequiredChecks 682 + 665 683 ctx.Data["is_context_required"] = func(context string) bool { 666 684 for _, c := range pb.StatusCheckContexts { 667 685 if c == context { ··· 728 746 ctx.Data["NumCommits"] = len(compareInfo.Commits) 729 747 ctx.Data["NumFiles"] = compareInfo.NumFiles 730 748 return compareInfo 749 + } 750 + 751 + func createRequiredContextMatcher(requiredContext string) func(string) bool { 752 + if gp, err := glob.Compile(requiredContext); err == nil { 753 + return func(contextToCheck string) bool { 754 + return gp.Match(contextToCheck) 755 + } 756 + } 757 + 758 + return func(contextToCheck string) bool { 759 + return requiredContext == contextToCheck 760 + } 731 761 } 732 762 733 763 type pullCommitList struct {
+87 -87
routers/web/repo/release.go
··· 12 12 13 13 "code.gitea.io/gitea/models" 14 14 "code.gitea.io/gitea/models/db" 15 + git_model "code.gitea.io/gitea/models/git" 15 16 repo_model "code.gitea.io/gitea/models/repo" 16 17 "code.gitea.io/gitea/models/unit" 17 18 user_model "code.gitea.io/gitea/models/user" ··· 67 68 return nil 68 69 } 69 70 70 - // Releases render releases list page 71 - func Releases(ctx *context.Context) { 72 - ctx.Data["PageIsReleaseList"] = true 73 - ctx.Data["Title"] = ctx.Tr("repo.release.releases") 74 - ctx.Data["IsViewBranch"] = false 75 - ctx.Data["IsViewTag"] = true 76 - // Disable the showCreateNewBranch form in the dropdown on this page. 77 - ctx.Data["CanCreateBranch"] = false 78 - ctx.Data["HideBranchesInDropdown"] = true 71 + type ReleaseInfo struct { 72 + Release *repo_model.Release 73 + CommitStatus *git_model.CommitStatus 74 + CommitStatuses []*git_model.CommitStatus 75 + } 79 76 80 - listOptions := db.ListOptions{ 81 - Page: ctx.FormInt("page"), 82 - PageSize: ctx.FormInt("limit"), 83 - } 84 - if listOptions.PageSize == 0 { 85 - listOptions.PageSize = setting.Repository.Release.DefaultPagingNum 86 - } 87 - if listOptions.PageSize > setting.API.MaxResponseItems { 88 - listOptions.PageSize = setting.API.MaxResponseItems 89 - } 90 - 91 - writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) 92 - ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived 93 - 94 - opts := repo_model.FindReleasesOptions{ 95 - ListOptions: listOptions, 96 - // only show draft releases for users who can write, read-only users shouldn't see draft releases. 97 - IncludeDrafts: writeAccess, 98 - RepoID: ctx.Repo.Repository.ID, 99 - } 100 - 77 + func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) ([]*ReleaseInfo, error) { 101 78 releases, err := db.Find[repo_model.Release](ctx, opts) 102 79 if err != nil { 103 - ctx.ServerError("GetReleasesByRepoID", err) 104 - return 80 + return nil, err 105 81 } 106 82 107 83 for _, release := range releases { ··· 109 85 } 110 86 111 87 if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil { 112 - ctx.ServerError("GetReleaseAttachments", err) 113 - return 88 + return nil, err 114 89 } 115 90 116 91 // Temporary cache commits count of used branches to speed up. ··· 121 96 } 122 97 var ok bool 123 98 99 + canReadActions := ctx.Repo.CanRead(unit.TypeActions) 100 + 101 + releaseInfos := make([]*ReleaseInfo, 0, len(releases)) 124 102 for _, r := range releases { 125 103 if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { 126 104 r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID) ··· 128 106 if user_model.IsErrUserNotExist(err) { 129 107 r.Publisher = user_model.NewGhostUser() 130 108 } else { 131 - ctx.ServerError("GetUserByID", err) 132 - return 109 + return nil, err 133 110 } 134 111 } 135 112 cacheUsers[r.PublisherID] = r.Publisher ··· 144 121 Ctx: ctx, 145 122 }, r.Note) 146 123 if err != nil { 147 - ctx.ServerError("RenderString", err) 148 - return 124 + return nil, err 149 125 } 150 126 151 - if r.IsDraft { 152 - continue 127 + if !r.IsDraft { 128 + if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil { 129 + return nil, err 130 + } 153 131 } 154 132 155 - if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil { 156 - ctx.ServerError("calReleaseNumCommitsBehind", err) 157 - return 133 + info := &ReleaseInfo{ 134 + Release: r, 135 + } 136 + 137 + if canReadActions { 138 + statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptions{ListAll: true}) 139 + if err != nil { 140 + return nil, err 141 + } 142 + 143 + info.CommitStatus = git_model.CalcCommitStatus(statuses) 144 + info.CommitStatuses = statuses 158 145 } 146 + 147 + releaseInfos = append(releaseInfos, info) 148 + } 149 + 150 + return releaseInfos, nil 151 + } 152 + 153 + // Releases render releases list page 154 + func Releases(ctx *context.Context) { 155 + ctx.Data["PageIsReleaseList"] = true 156 + ctx.Data["Title"] = ctx.Tr("repo.release.releases") 157 + ctx.Data["IsViewBranch"] = false 158 + ctx.Data["IsViewTag"] = true 159 + // Disable the showCreateNewBranch form in the dropdown on this page. 160 + ctx.Data["CanCreateBranch"] = false 161 + ctx.Data["HideBranchesInDropdown"] = true 162 + 163 + listOptions := db.ListOptions{ 164 + Page: ctx.FormInt("page"), 165 + PageSize: ctx.FormInt("limit"), 166 + } 167 + if listOptions.PageSize == 0 { 168 + listOptions.PageSize = setting.Repository.Release.DefaultPagingNum 169 + } 170 + if listOptions.PageSize > setting.API.MaxResponseItems { 171 + listOptions.PageSize = setting.API.MaxResponseItems 172 + } 173 + 174 + writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) 175 + ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived 176 + 177 + releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{ 178 + ListOptions: listOptions, 179 + // only show draft releases for users who can write, read-only users shouldn't see draft releases. 180 + IncludeDrafts: writeAccess, 181 + RepoID: ctx.Repo.Repository.ID, 182 + }) 183 + if err != nil { 184 + ctx.ServerError("getReleaseInfos", err) 185 + return 159 186 } 160 187 161 188 ctx.Data["Releases"] = releases 162 189 163 190 numReleases := ctx.Data["NumReleases"].(int64) 164 - pager := context.NewPagination(int(numReleases), opts.PageSize, opts.Page, 5) 191 + pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5) 165 192 pager.SetDefaultParams(ctx) 166 193 ctx.Data["Page"] = pager 167 194 ··· 249 276 writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) 250 277 ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived 251 278 252 - release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Params("*")) 279 + releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{ 280 + ListOptions: db.ListOptions{Page: 1, PageSize: 1}, 281 + RepoID: ctx.Repo.Repository.ID, 282 + TagNames: []string{ctx.Params("*")}, 283 + // only show draft releases for users who can write, read-only users shouldn't see draft releases. 284 + IncludeDrafts: writeAccess, 285 + }) 253 286 if err != nil { 254 - if repo_model.IsErrReleaseNotExist(err) { 255 - ctx.NotFound("GetRelease", err) 256 - return 257 - } 258 - ctx.ServerError("GetReleasesByRepoID", err) 287 + ctx.ServerError("getReleaseInfos", err) 259 288 return 260 289 } 290 + if len(releases) != 1 { 291 + ctx.NotFound("SingleRelease", err) 292 + return 293 + } 294 + 295 + release := releases[0].Release 296 + 261 297 ctx.Data["PageIsSingleTag"] = release.IsTag 262 298 if release.IsTag { 263 299 ctx.Data["Title"] = release.TagName ··· 265 301 ctx.Data["Title"] = release.Title 266 302 } 267 303 268 - release.Repo = ctx.Repo.Repository 269 - 270 - err = repo_model.GetReleaseAttachments(ctx, release) 271 - if err != nil { 272 - ctx.ServerError("GetReleaseAttachments", err) 273 - return 274 - } 275 - 276 - release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID) 277 - if err != nil { 278 - if user_model.IsErrUserNotExist(err) { 279 - release.Publisher = user_model.NewGhostUser() 280 - } else { 281 - ctx.ServerError("GetUserByID", err) 282 - return 283 - } 284 - } 285 - if !release.IsDraft { 286 - if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil { 287 - ctx.ServerError("calReleaseNumCommitsBehind", err) 288 - return 289 - } 290 - } 291 - release.Note, err = markdown.RenderString(&markup.RenderContext{ 292 - Links: markup.Links{ 293 - Base: ctx.Repo.RepoLink, 294 - }, 295 - Metas: ctx.Repo.Repository.ComposeMetas(ctx), 296 - GitRepo: ctx.Repo.GitRepo, 297 - Ctx: ctx, 298 - }, release.Note) 299 - if err != nil { 300 - ctx.ServerError("RenderString", err) 301 - return 302 - } 303 - 304 - ctx.Data["Releases"] = []*repo_model.Release{release} 304 + ctx.Data["Releases"] = releases 305 305 ctx.HTML(http.StatusOK, tplReleasesList) 306 306 } 307 307
+3 -1
routers/web/shared/user/header.go
··· 4 4 package user 5 5 6 6 import ( 7 + "net/url" 8 + 7 9 "code.gitea.io/gitea/models/db" 8 10 "code.gitea.io/gitea/models/organization" 9 11 access_model "code.gitea.io/gitea/models/perm/access" ··· 37 39 ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID) 38 40 ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID) 39 41 ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate 40 - ctx.Data["UserLocationMapURL"] = setting.Service.UserLocationMapURL 42 + ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location) 41 43 42 44 // Show OpenID URIs 43 45 openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)
+1 -13
routers/web/swagger_json.go
··· 4 4 package web 5 5 6 6 import ( 7 - "code.gitea.io/gitea/modules/base" 8 7 "code.gitea.io/gitea/modules/context" 9 8 ) 10 - 11 - // tplSwaggerV1Json swagger v1 json template 12 - const tplSwaggerV1Json base.TplName = "swagger/v1_json" 13 9 14 10 // SwaggerV1Json render swagger v1 json 15 11 func SwaggerV1Json(ctx *context.Context) { 16 - t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json), nil) 17 - if err != nil { 18 - ctx.ServerError("unable to find template", err) 19 - return 20 - } 21 - ctx.Resp.Header().Set("Content-Type", "application/json") 22 - if err = t.Execute(ctx.Resp, ctx.Data); err != nil { 23 - ctx.ServerError("unable to execute template", err) 24 - } 12 + ctx.JSONTemplate("swagger/v1_json") 25 13 }
+3
services/actions/commit_status.go
··· 64 64 return fmt.Errorf("head of pull request is missing in event payload") 65 65 } 66 66 sha = payload.PullRequest.Head.Sha 67 + case webhook_module.HookEventRelease: 68 + event = string(run.Event) 69 + sha = run.CommitSHA 67 70 default: 68 71 return nil 69 72 }
+108 -9
services/actions/notifier.go
··· 55 55 }).Notify(withMethod(ctx, "NewIssue")) 56 56 } 57 57 58 + // IssueChangeContent notifies change content of issue 59 + func (n *actionsNotifier) IssueChangeContent(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldContent string) { 60 + ctx = withMethod(ctx, "IssueChangeContent") 61 + 62 + var err error 63 + if err = issue.LoadRepo(ctx); err != nil { 64 + log.Error("LoadRepo: %v", err) 65 + return 66 + } 67 + 68 + permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster) 69 + if issue.IsPull { 70 + if err = issue.LoadPullRequest(ctx); err != nil { 71 + log.Error("loadPullRequest: %v", err) 72 + return 73 + } 74 + newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequest). 75 + WithDoer(doer). 76 + WithPayload(&api.PullRequestPayload{ 77 + Action: api.HookIssueEdited, 78 + Index: issue.Index, 79 + PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), 80 + Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}), 81 + Sender: convert.ToUser(ctx, doer, nil), 82 + }). 83 + WithPullRequest(issue.PullRequest). 84 + Notify(ctx) 85 + return 86 + } 87 + newNotifyInputFromIssue(issue, webhook_module.HookEventIssues). 88 + WithDoer(doer). 89 + WithPayload(&api.IssuePayload{ 90 + Action: api.HookIssueEdited, 91 + Index: issue.Index, 92 + Issue: convert.ToAPIIssue(ctx, issue), 93 + Repository: convert.ToRepo(ctx, issue.Repo, permission), 94 + Sender: convert.ToUser(ctx, doer, nil), 95 + }). 96 + Notify(ctx) 97 + } 98 + 58 99 // IssueChangeStatus notifies close or reopen issue to notifiers 59 100 func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, _ *issues_model.Comment, isClosed bool) { 60 101 ctx = withMethod(ctx, "IssueChangeStatus") ··· 101 142 Notify(ctx) 102 143 } 103 144 145 + // IssueChangeAssignee notifies assigned or unassigned to notifiers 146 + func (n *actionsNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) { 147 + ctx = withMethod(ctx, "IssueChangeAssignee") 148 + 149 + var action api.HookIssueAction 150 + if removed { 151 + action = api.HookIssueUnassigned 152 + } else { 153 + action = api.HookIssueAssigned 154 + } 155 + notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestAssign, action) 156 + } 157 + 158 + // IssueChangeMilestone notifies assignee to notifiers 159 + func (n *actionsNotifier) IssueChangeMilestone(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) { 160 + ctx = withMethod(ctx, "IssueChangeMilestone") 161 + 162 + var action api.HookIssueAction 163 + if issue.MilestoneID > 0 { 164 + action = api.HookIssueMilestoned 165 + } else { 166 + action = api.HookIssueDemilestoned 167 + } 168 + notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestMilestone, action) 169 + } 170 + 104 171 func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, 105 172 _, _ []*issues_model.Label, 106 173 ) { 107 174 ctx = withMethod(ctx, "IssueChangeLabels") 175 + notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestLabel, api.HookIssueLabelUpdated) 176 + } 108 177 178 + func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, event webhook_module.HookEventType, action api.HookIssueAction) { 109 179 var err error 110 180 if err = issue.LoadRepo(ctx); err != nil { 111 181 log.Error("LoadRepo: %v", err) ··· 117 187 return 118 188 } 119 189 120 - permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster) 121 190 if issue.IsPull { 122 191 if err = issue.LoadPullRequest(ctx); err != nil { 123 192 log.Error("loadPullRequest: %v", err) 124 193 return 125 194 } 126 - if err = issue.PullRequest.LoadIssue(ctx); err != nil { 127 - log.Error("LoadIssue: %v", err) 128 - return 129 - } 130 - newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestLabel). 195 + newNotifyInputFromIssue(issue, event). 131 196 WithDoer(doer). 132 197 WithPayload(&api.PullRequestPayload{ 133 - Action: api.HookIssueLabelUpdated, 198 + Action: action, 134 199 Index: issue.Index, 135 200 PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), 136 201 Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}), ··· 140 205 Notify(ctx) 141 206 return 142 207 } 143 - newNotifyInputFromIssue(issue, webhook_module.HookEventIssueLabel). 208 + permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster) 209 + newNotifyInputFromIssue(issue, event). 144 210 WithDoer(doer). 145 211 WithPayload(&api.IssuePayload{ 146 - Action: api.HookIssueLabelUpdated, 212 + Action: action, 147 213 Index: issue.Index, 148 214 Issue: convert.ToAPIIssue(ctx, issue), 149 215 Repository: convert.ToRepo(ctx, issue.Repo, permission), ··· 303 369 Content: review.Content, 304 370 }, 305 371 }).Notify(ctx) 372 + } 373 + 374 + func (n *actionsNotifier) PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) { 375 + if !issue.IsPull { 376 + log.Warn("PullRequestReviewRequest: issue is not a pull request: %v", issue.ID) 377 + return 378 + } 379 + 380 + ctx = withMethod(ctx, "PullRequestReviewRequest") 381 + 382 + permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) 383 + if err := issue.LoadPullRequest(ctx); err != nil { 384 + log.Error("LoadPullRequest failed: %v", err) 385 + return 386 + } 387 + var action api.HookIssueAction 388 + if isRequest { 389 + action = api.HookIssueReviewRequested 390 + } else { 391 + action = api.HookIssueReviewRequestRemoved 392 + } 393 + newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestReviewRequest). 394 + WithDoer(doer). 395 + WithPayload(&api.PullRequestPayload{ 396 + Action: action, 397 + Index: issue.Index, 398 + PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), 399 + RequestedReviewer: convert.ToUser(ctx, reviewer, nil), 400 + Repository: convert.ToRepo(ctx, issue.Repo, permission), 401 + Sender: convert.ToUser(ctx, doer, nil), 402 + }). 403 + WithPullRequest(issue.PullRequest). 404 + Notify(ctx) 306 405 } 307 406 308 407 func (*actionsNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
+1 -1
services/auth/source/db/source.go
··· 18 18 return nil 19 19 } 20 20 21 - // ToDB exports an SMTPConfig to a serialized format. 21 + // ToDB exports the config to a byte slice to be saved into database (this method is just dummy and does nothing for DB source) 22 22 func (source *Source) ToDB() ([]byte, error) { 23 23 return nil, nil 24 24 }
+4
services/pull/commit_status.go
··· 52 52 } 53 53 } 54 54 55 + if matchedCount != len(requiredContexts) { 56 + return structs.CommitStatusPending 57 + } 58 + 55 59 if matchedCount == 0 { 56 60 status := git_model.CalcCommitStatus(commitStatuses) 57 61 if status != nil {
+6 -6
services/repository/push.go
··· 321 321 return nil 322 322 } 323 323 324 - lowerTags := make([]string, 0, len(tags)) 325 - for _, tag := range tags { 326 - lowerTags = append(lowerTags, strings.ToLower(tag)) 327 - } 328 - 329 324 releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{ 330 325 RepoID: repo.ID, 331 - TagNames: lowerTags, 326 + TagNames: tags, 332 327 }) 333 328 if err != nil { 334 329 return fmt.Errorf("db.Find[repo_model.Release]: %w", err) ··· 336 331 relMap := make(map[string]*repo_model.Release) 337 332 for _, rel := range releases { 338 333 relMap[rel.LowerTagName] = rel 334 + } 335 + 336 + lowerTags := make([]string, 0, len(tags)) 337 + for _, tag := range tags { 338 + lowerTags = append(lowerTags, strings.ToLower(tag)) 339 339 } 340 340 341 341 newReleases := make([]*repo_model.Release, 0, len(lowerTags)-len(relMap))
+1 -1
templates/admin/packages/list.tmpl
··· 88 88 {{ctx.Locale.Tr "packages.settings.delete"}} 89 89 </div> 90 90 <div class="content"> 91 - {{ctx.Locale.Tr "packages.settings.delete.notice" `<span class="name"></span>` `<span class="dataVersion"></span>` | Safe}} 91 + {{ctx.Locale.Tr "packages.settings.delete.notice" (`<span class="name"></span>`|Safe) (`<span class="dataVersion"></span>`|Safe)}} 92 92 </div> 93 93 {{template "base/modal_actions_confirm" .}} 94 94 </div>
+1 -1
templates/admin/repo/list.tmpl
··· 101 101 </div> 102 102 <div class="content"> 103 103 <p>{{ctx.Locale.Tr "repo.settings.delete_desc"}}</p> 104 - {{ctx.Locale.Tr "repo.settings.delete_notices_2" `<span class="name"></span>` | Safe}}<br> 104 + {{ctx.Locale.Tr "repo.settings.delete_notices_2" (`<span class="name"></span>`|Safe)}}<br> 105 105 {{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}<br> 106 106 </div> 107 107 {{template "base/modal_actions_confirm" .}}
+1 -1
templates/admin/stacktrace.tmpl
··· 39 39 {{ctx.Locale.Tr "admin.monitor.process.cancel"}} 40 40 </div> 41 41 <div class="content"> 42 - <p>{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" `<span class="name"></span>` | Safe}}</p> 42 + <p>{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" (`<span class="name"></span>`|Safe)}}</p> 43 43 <p>{{ctx.Locale.Tr "admin.monitor.process.cancel_desc"}}</p> 44 44 </div> 45 45 {{template "base/modal_actions_confirm" .}}
+1 -1
templates/admin/user/new.tmpl
··· 26 26 <div class="inline field {{if .Err_Visibility}}error{{end}}"> 27 27 <span class="inline required field"><label for="visibility">{{ctx.Locale.Tr "settings.visibility"}}</label></span> 28 28 <div class="ui selection type dropdown"> 29 - <input type="hidden" id="visibility" name="visibility" value="{{if .visibility}}{{.visibility}}{{else}}{{printf "%d" .DefaultUserVisibilityMode}}{{end}}"> 29 + <input type="hidden" id="visibility" name="visibility" value="{{if .visibility}}{{printf "%d" .visibility}}{{else}}{{printf "%d" .DefaultUserVisibilityMode}}{{end}}"> 30 30 <div class="text"> 31 31 {{if .DefaultUserVisibilityMode.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}} 32 32 {{if .DefaultUserVisibilityMode.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
-1
templates/base/footer.tmpl
··· 16 16 <script src="{{AssetUrlPrefix}}/js/index.js?v={{AssetVersion}}" onerror="alert('Failed to load asset files from ' + this.src + '. Please make sure the asset files can be accessed.')"></script> 17 17 18 18 {{template "custom/footer" .}} 19 - {{ctx.DataRaceCheck $.Context}} 20 19 </body> 21 20 </html>
-1
templates/base/head.tmpl
··· 31 31 {{template "custom/header" .}} 32 32 </head> 33 33 <body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-ext="morph" hx-push-url="false"> 34 - {{ctx.DataRaceCheck $.Context}} 35 34 {{template "custom/body_outer_pre" .}} 36 35 37 36 <div class="full height">
+2 -2
templates/org/member/members.tmpl
··· 73 73 {{ctx.Locale.Tr "org.members.leave"}} 74 74 </div> 75 75 <div class="content"> 76 - <p>{{ctx.Locale.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p> 76 + <p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|Safe)}}</p> 77 77 </div> 78 78 {{template "base/modal_actions_confirm" .}} 79 79 </div> ··· 82 82 {{ctx.Locale.Tr "org.members.remove"}} 83 83 </div> 84 84 <div class="content"> 85 - <p>{{ctx.Locale.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataOrganizationName"></span>` | Safe}}</p> 85 + <p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|Safe) (`<span class="dataOrganizationName"></span>`|Safe)}}</p> 86 86 </div> 87 87 {{template "base/modal_actions_confirm" .}} 88 88 </div>
+1 -1
templates/org/team/members.tmpl
··· 81 81 {{ctx.Locale.Tr "org.members.remove"}} 82 82 </div> 83 83 <div class="content"> 84 - <p>{{ctx.Locale.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataTeamName"></span>` | Safe}}</p> 84 + <p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|Safe) (`<span class="dataTeamName"></span>`|Safe)}}</p> 85 85 </div> 86 86 {{template "base/modal_actions_confirm" .}} 87 87 </div>
+1 -1
templates/org/team/sidebar.tmpl
··· 88 88 {{ctx.Locale.Tr "org.teams.leave"}} 89 89 </div> 90 90 <div class="content"> 91 - <p>{{ctx.Locale.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p> 91 + <p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|Safe)}}</p> 92 92 </div> 93 93 {{template "base/modal_actions_confirm" .}} 94 94 </div>
+1 -1
templates/org/team/teams.tmpl
··· 49 49 {{ctx.Locale.Tr "org.teams.leave"}} 50 50 </div> 51 51 <div class="content"> 52 - <p>{{ctx.Locale.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p> 52 + <p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|Safe)}}</p> 53 53 </div> 54 54 {{template "base/modal_actions_confirm" .}} 55 55 </div>
+3 -3
templates/repo/commit_page.tmpl
··· 88 88 {{.CsrfTokenHtml}} 89 89 <div class="field"> 90 90 <label> 91 - {{ctx.Locale.Tr "repo.branch.new_branch_from" `<span class="text" id="modal-create-branch-from-span"></span>` | Safe}} 91 + {{ctx.Locale.Tr "repo.branch.new_branch_from" (`<span class="text" id="modal-create-branch-from-span"></span>`|Safe)}} 92 92 </label> 93 93 </div> 94 94 <div class="required field"> ··· 113 113 <input type="hidden" name="create_tag" value="true"> 114 114 <div class="field"> 115 115 <label> 116 - {{ctx.Locale.Tr "repo.tag.create_tag_from" `<span class="text" id="modal-create-tag-from-span"></span>` | Safe}} 116 + {{ctx.Locale.Tr "repo.tag.create_tag_from" (`<span class="text" id="modal-create-tag-from-span"></span>`|Safe)}} 117 117 </label> 118 118 </div> 119 119 <div class="required field"> ··· 184 184 </div> 185 185 </div> 186 186 {{if .Commit.Signature}} 187 - <div class="ui bottom attached message gt-text-left gt-df gt-ac gt-sb commit-header-row gt-fw {{$class}}"> 187 + <div class="ui bottom attached message gt-text-left gt-df gt-ac gt-sb commit-header-row gt-fw gt-mb-0 {{$class}}"> 188 188 <div class="gt-df gt-ac"> 189 189 {{if .Verification.Verified}} 190 190 {{if ne .Verification.SigningUser.ID 0}}
+2 -2
templates/repo/commit_statuses.tmpl
··· 1 1 {{if .Statuses}} 2 2 {{if and (eq (len .Statuses) 1) .Status.TargetURL}} 3 - <a class="gt-vm gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}"> 3 + <a class="gt-vm {{.AdditionalClasses}} gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}"> 4 4 {{template "repo/commit_status" .Status}} 5 5 </a> 6 6 {{else}} 7 - <span class="gt-vm" data-tippy="commit-statuses" tabindex="0"> 7 + <span class="gt-vm {{.AdditionalClasses}}" data-tippy="commit-statuses" tabindex="0"> 8 8 {{template "repo/commit_status" .Status}} 9 9 </span> 10 10 {{end}}
+4 -4
templates/repo/contributors.tmpl
··· 4 4 data-locale-contribution-type-commits="{{ctx.Locale.Tr "repo.contributors.contribution_type.commits"}}" 5 5 data-locale-contribution-type-additions="{{ctx.Locale.Tr "repo.contributors.contribution_type.additions"}}" 6 6 data-locale-contribution-type-deletions="{{ctx.Locale.Tr "repo.contributors.contribution_type.deletions"}}" 7 - data-locale-loading-title="{{ctx.Locale.Tr "repo.contributors.loading_title"}}" 8 - data-locale-loading-title-failed="{{ctx.Locale.Tr "repo.contributors.loading_title_failed"}}" 9 - data-locale-loading-info="{{ctx.Locale.Tr "repo.contributors.loading_info"}}" 10 - data-locale-component-failed-to-load="{{ctx.Locale.Tr "repo.contributors.component_failed_to_load"}}" 7 + data-locale-loading-title="{{ctx.Locale.Tr "graphs.component_loading" (ctx.Locale.Tr "graphs.contributors.what")}}" 8 + data-locale-loading-title-failed="{{ctx.Locale.Tr "graphs.component_loading_failed" (ctx.Locale.Tr "graphs.contributors.what")}}" 9 + data-locale-loading-info="{{ctx.Locale.Tr "graphs.component_loading_info"}}" 10 + data-locale-component-failed-to-load="{{ctx.Locale.Tr "graphs.component_failed_to_load"}}" 11 11 > 12 12 </div> 13 13 {{end}}
+1 -1
templates/repo/diff/box.tmpl
··· 1 1 {{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}} 2 2 <div> 3 3 <div class="diff-detail-box diff-box"> 4 - <div class="gt-df gt-ac gt-fw"> 4 + <div class="gt-df gt-ac gt-fw gt-gap-3 gt-ml-1"> 5 5 {{if $showFileTree}} 6 6 <button class="diff-toggle-file-tree-button not-mobile btn interact-fg" data-show-text="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}" data-hide-text="{{ctx.Locale.Tr "repo.diff.hide_file_tree"}}"> 7 7 {{/* the icon meaning is reversed here, "octicon-sidebar-collapse" means show the file tree */}}
+2 -2
templates/repo/issue/view_content/comments.tmpl
··· 112 112 {{template "shared/user/authorlink" .Poster}} 113 113 {{$link := printf "%s/commit/%s" $.Repository.Link ($.Issue.PullRequest.MergedCommitID|PathEscape)}} 114 114 {{if eq $.Issue.PullRequest.Status 3}} 115 - {{ctx.Locale.Tr "repo.issues.comment_manually_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID)) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape)) $createdStr | Safe}} 115 + {{ctx.Locale.Tr "repo.issues.comment_manually_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID) | Safe) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape) | Safe) $createdStr}} 116 116 {{else}} 117 - {{ctx.Locale.Tr "repo.issues.comment_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID)) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape)) $createdStr | Safe}} 117 + {{ctx.Locale.Tr "repo.issues.comment_pull_merged_at" (printf `<a class="ui sha" href="%[1]s"><b>%[2]s</b></a>` ($link|Escape) (ShortSha $.Issue.PullRequest.MergedCommitID) | Safe) (printf "<b>%[1]s</b>" ($.BaseTarget|Escape) | Safe) $createdStr}} 118 118 {{end}} 119 119 </span> 120 120 </div>
+2 -1
templates/repo/issue/view_content/pull.tmpl
··· 24 24 {{template "repo/pulls/status" (dict 25 25 "CommitStatus" .LatestCommitStatus 26 26 "CommitStatuses" .LatestCommitStatuses 27 + "MissingRequiredChecks" .MissingRequiredChecks 27 28 "ShowHideChecks" true 28 29 "is_context_required" .is_context_required 29 30 )}} ··· 38 39 {{ctx.Locale.Tr "repo.pulls.merged_success"}} 39 40 </h3> 40 41 <div class="merge-section-info"> 41 - {{ctx.Locale.Tr "repo.pulls.merged_info_text" (printf "<code>%s</code>" (.HeadTarget | Escape)) | Str2html}} 42 + {{ctx.Locale.Tr "repo.pulls.merged_info_text" (printf "<code>%s</code>" (.HeadTarget | Escape) | Safe)}} 42 43 </div> 43 44 </div> 44 45 <div class="item-section-right">
+9 -1
templates/repo/pulls/status.tmpl
··· 2 2 Template Attributes: 3 3 * CommitStatus: summary of all commit status state 4 4 * CommitStatuses: all commit status elements 5 + * MissingRequiredChecks: commit check contexts that are required by branch protection but not present 5 6 * ShowHideChecks: whether use a button to show/hide the checks 6 7 * is_context_required: Used in pull request commit status check table 7 8 */}} ··· 9 10 {{if .CommitStatus}} 10 11 <div class="commit-status-panel"> 11 12 <div class="ui top attached header commit-status-header"> 12 - {{if eq .CommitStatus.State "pending"}} 13 + {{if or (eq .CommitStatus.State "pending") (.MissingRequiredChecks)}} 13 14 {{ctx.Locale.Tr "repo.pulls.status_checking"}} 14 15 {{else if eq .CommitStatus.State "success"}} 15 16 {{ctx.Locale.Tr "repo.pulls.status_checks_success"}} ··· 44 45 {{end}} 45 46 <span>{{if .TargetURL}}<a href="{{.TargetURL}}">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span> 46 47 </div> 48 + </div> 49 + {{end}} 50 + {{range .MissingRequiredChecks}} 51 + <div class="commit-status-item"> 52 + {{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}} 53 + <div class="status-context gt-ellipsis">{{.}}</div> 54 + <div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div> 47 55 </div> 48 56 {{end}} 49 57 </div>
+76 -76
templates/repo/release/list.tmpl
··· 5 5 {{template "base/alert" .}} 6 6 {{template "repo/release_tag_header" .}} 7 7 <ul id="release-list"> 8 - {{range $idx, $release := .Releases}} 8 + {{range $idx, $info := .Releases}} 9 + {{$release := $info.Release}} 9 10 <li class="ui grid"> 10 11 <div class="ui four wide column meta"> 11 - <a class="muted" href="{{if not (and .Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}}</a> 12 - {{if and .Sha1 ($.Permission.CanRead $.UnitTypeCode)}} 13 - <a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a> 14 - {{template "repo/branch_dropdown" dict "root" $ "release" .}} 15 - {{end}} 12 + <a class="muted" href="{{if not (and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{$release.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$release.TagName}}</a> 13 + {{if and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode)}} 14 + <a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{$release.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha $release.Sha1}}</a> 15 + {{template "repo/branch_dropdown" dict "root" $ "release" $release}} 16 + {{end}} 16 17 </div> 17 18 <div class="ui twelve wide column detail"> 18 - <div class="gt-df gt-ac gt-sb gt-fw gt-mb-3"> 19 - <h4 class="release-list-title gt-word-break"> 20 - <a href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{.Title}}</a> 21 - {{if .IsDraft}} 22 - <span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span> 23 - {{else if .IsPrerelease}} 24 - <span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span> 25 - {{else}} 26 - <span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span> 27 - {{end}} 28 - </h4> 29 - <div> 30 - {{if $.CanCreateRelease}} 31 - <a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{.TagName | PathEscapeSegments}}" rel="nofollow"> 32 - {{svg "octicon-pencil"}} 33 - </a> 34 - {{end}} 35 - </div> 36 - </div> 37 - <p class="text grey"> 38 - <span class="author"> 39 - {{if .OriginalAuthor}} 40 - {{svg (MigrationIcon .Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{.OriginalAuthor}} 41 - {{else if .Publisher}} 42 - {{ctx.AvatarUtils.Avatar .Publisher 20 "gt-mr-2"}} 43 - <a href="{{.Publisher.HomeLink}}">{{.Publisher.GetDisplayName}}</a> 19 + <div class="gt-df gt-ac gt-sb gt-fw gt-mb-3"> 20 + <h4 class="release-list-title gt-word-break"> 21 + <a href="{{$.RepoLink}}/releases/tag/{{$release.TagName | PathEscapeSegments}}">{{$release.Title}}</a> 22 + {{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "gt-df"}} 23 + {{if $release.IsDraft}} 24 + <span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span> 25 + {{else if $release.IsPrerelease}} 26 + <span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span> 44 27 {{else}} 45 - Ghost 28 + <span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span> 46 29 {{end}} 47 - </span> 48 - <span class="released"> 49 - {{ctx.Locale.Tr "repo.released_this"}} 50 - </span> 51 - {{if .CreatedUnix}} 52 - <span class="time">{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span> 53 - {{end}} 54 - {{if and (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} 55 - | <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" .TargetBehind}}</span> 30 + </h4> 31 + <div> 32 + {{if $.CanCreateRelease}} 33 + <a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{$release.TagName | PathEscapeSegments}}" rel="nofollow"> 34 + {{svg "octicon-pencil"}} 35 + </a> 56 36 {{end}} 57 - </p> 58 - <div class="markup desc"> 59 - {{Str2html .Note}} 60 37 </div> 61 - <div class="divider"></div> 62 - <details class="download" {{if eq $idx 0}}open{{end}}> 63 - <summary class="gt-my-4"> 64 - {{ctx.Locale.Tr "repo.release.downloads"}} 65 - </summary> 66 - <ul class="list"> 67 - {{if and (not $.DisableDownloadSourceArchives) (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} 68 - <li> 69 - <a class="archive-link" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a> 70 - </li> 71 - <li> 72 - <a class="archive-link" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a> 73 - </li> 74 - {{end}} 75 - {{if .Attachments}} 76 - {{range .Attachments}} 77 - <li> 78 - <a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download> 79 - <strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong> 80 - </a> 81 - <div> 82 - <span class="text grey">{{.Size | FileSize}}</span> 83 - <span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}"> 84 - {{svg "octicon-info"}} 85 - </span> 86 - </div> 87 - </li> 88 - {{end}} 89 - {{end}} 90 - </ul> 91 - </details> 38 + </div> 39 + <p class="text grey"> 40 + <span class="author"> 41 + {{if $release.OriginalAuthor}} 42 + {{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{$release.OriginalAuthor}} 43 + {{else if $release.Publisher}} 44 + {{ctx.AvatarUtils.Avatar $release.Publisher 20 "gt-mr-2"}} 45 + <a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a> 46 + {{else}} 47 + Ghost 48 + {{end}} 49 + </span> 50 + <span class="released"> 51 + {{ctx.Locale.Tr "repo.released_this"}} 52 + </span> 53 + {{if $release.CreatedUnix}} 54 + <span class="time">{{TimeSinceUnix $release.CreatedUnix ctx.Locale}}</span> 55 + {{end}} 56 + {{if and (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} 57 + | <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span> 58 + {{end}} 59 + </p> 60 + <div class="markup desc"> 61 + {{Str2html $release.Note}} 62 + </div> 63 + <div class="divider"></div> 64 + <details class="download" {{if eq $idx 0}}open{{end}}> 65 + <summary class="gt-my-4"> 66 + {{ctx.Locale.Tr "repo.release.downloads"}} 67 + </summary> 68 + <ul class="list"> 69 + {{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} 70 + <li> 71 + <a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a> 72 + </li> 73 + <li> 74 + <a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a> 75 + </li> 76 + {{end}} 77 + {{range $release.Attachments}} 78 + <li> 79 + <a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download> 80 + <strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong> 81 + </a> 82 + <div> 83 + <span class="text grey">{{.Size | FileSize}}</span> 84 + <span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}"> 85 + {{svg "octicon-info"}} 86 + </span> 87 + </div> 88 + </li> 89 + {{end}} 90 + </ul> 91 + </details> 92 92 <div class="dot"></div> 93 93 </div> 94 94 </li>
+1 -1
templates/repo/settings/webhook/settings.tmpl
··· 263 263 <label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label> 264 264 <input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}> 265 265 {{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}} 266 - <span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" "<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | Str2html}}</span> 266 + <span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | Safe)}}</span> 267 267 {{end}} 268 268 </div> 269 269
+2 -3
templates/shared/user/profile_big_avatar.tmpl
··· 31 31 <li> 32 32 {{svg "octicon-location"}} 33 33 <span class="gt-f1">{{.ContextUser.Location}}</span> 34 - {{if .UserLocationMapURL}} 35 - {{/* We presume that the UserLocationMapURL is safe, as it is provided by the site administrator. */}} 36 - <a href="{{.UserLocationMapURL | Safe}}{{.ContextUser.Location | QueryEscape}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}"> 34 + {{if .ContextUserLocationMapURL}} 35 + <a href="{{.ContextUserLocationMapURL}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}"> 37 36 {{svg "octicon-link-external"}} 38 37 </a> 39 38 {{end}}
+2 -2
templates/swagger/v1_json.tmpl
··· 19 19 "name": "MIT", 20 20 "url": "http://opensource.org/licenses/MIT" 21 21 }, 22 - "version": "{{AppVer | JSEscape | Safe}}" 22 + "version": "{{AppVer | JSEscape}}" 23 23 }, 24 - "basePath": "{{AppSubUrl | JSEscape | Safe}}/api/v1", 24 + "basePath": "{{AppSubUrl | JSEscape}}/api/v1", 25 25 "paths": { 26 26 "/activitypub/user-id/{user-id}": { 27 27 "get": {
+7 -7
templates/user/auth/oidc_wellknown.tmpl
··· 1 1 { 2 - "issuer": "{{AppUrl | JSEscape | Safe}}", 3 - "authorization_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/authorize", 4 - "token_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/access_token", 5 - "jwks_uri": "{{AppUrl | JSEscape | Safe}}login/oauth/keys", 6 - "userinfo_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/userinfo", 7 - "introspection_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/introspect", 2 + "issuer": "{{AppUrl | JSEscape}}", 3 + "authorization_endpoint": "{{AppUrl | JSEscape}}login/oauth/authorize", 4 + "token_endpoint": "{{AppUrl | JSEscape}}login/oauth/access_token", 5 + "jwks_uri": "{{AppUrl | JSEscape}}login/oauth/keys", 6 + "userinfo_endpoint": "{{AppUrl | JSEscape}}login/oauth/userinfo", 7 + "introspection_endpoint": "{{AppUrl | JSEscape}}login/oauth/introspect", 8 8 "response_types_supported": [ 9 9 "code", 10 10 "id_token" 11 11 ], 12 12 "id_token_signing_alg_values_supported": [ 13 - "{{.SigningKey.SigningMethod.Alg | JSEscape | Safe}}" 13 + "{{.SigningKey.SigningMethod.Alg | JSEscape}}" 14 14 ], 15 15 "subject_types_supported": [ 16 16 "public"
+1 -1
templates/user/settings/organization.tmpl
··· 47 47 {{ctx.Locale.Tr "org.members.leave"}} 48 48 </div> 49 49 <div class="content"> 50 - <p>{{ctx.Locale.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p> 50 + <p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|Safe)}}</p> 51 51 </div> 52 52 {{template "base/modal_actions_confirm" .}} 53 53 </div>
+6
web_src/css/base.css
··· 21 21 --border-radius-circle: 50%; 22 22 --opacity-disabled: 0.55; 23 23 --height-loading: 16rem; 24 + --min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */ 24 25 --tab-size: 4; 25 26 } 26 27 ··· 490 491 .ui.menu .ui.dropdown .menu > .active.item { 491 492 color: var(--color-text) !important; 492 493 background: var(--color-active) !important; 494 + } 495 + 496 + .ui.form textarea:not([rows]) { 497 + height: var(--min-height-textarea); /* override fomantic default 12em */ 498 + min-height: var(--min-height-textarea); /* override fomantic default 8em */ 493 499 } 494 500 495 501 /* styles from removed fomantic transition module */
+2 -3
web_src/css/editor/combomarkdowneditor.css
··· 37 37 .combo-markdown-editor textarea.markdown-text-editor { 38 38 display: block; 39 39 width: 100%; 40 - min-height: 200px; 41 - max-height: calc(100vh - 200px); 40 + max-height: calc(100vh - var(--min-height-textarea)); 42 41 resize: vertical; 43 42 } 44 43 45 44 .combo-markdown-editor .CodeMirror-scroll { 46 - max-height: calc(100vh - 200px); 45 + max-height: calc(100vh - var(--min-height-textarea)); 47 46 } 48 47 49 48 /* use the same styles as markup/content.css */
+1 -17
web_src/css/repo.css
··· 1498 1498 background: var(--color-body); 1499 1499 } 1500 1500 1501 - @media (max-width: 991.98px) { 1502 - .repository .diff-detail-box { 1503 - flex-direction: row; 1504 - } 1505 - } 1506 - 1507 1501 @media (max-width: 480px) { 1508 1502 .repository .diff-detail-box { 1509 1503 flex-wrap: wrap; ··· 1528 1522 color: var(--color-red); 1529 1523 } 1530 1524 1531 - @media (max-width: 991.98px) { 1525 + @media (max-width: 800px) { 1532 1526 .repository .diff-detail-box .diff-detail-stats { 1533 1527 display: none !important; 1534 1528 } ··· 1538 1532 display: flex; 1539 1533 align-items: center; 1540 1534 gap: 0.25em; 1541 - flex-wrap: wrap; 1542 1535 justify-content: end; 1543 1536 } 1544 1537 ··· 1546 1539 .diff-detail-actions .button { 1547 1540 margin-left: 0 !important; 1548 1541 margin-right: 0 !important; 1549 - } 1550 - 1551 - @media (max-width: 480px) { 1552 - .repository .diff-detail-box .diff-detail-actions { 1553 - padding-top: 0.25rem; 1554 - } 1555 - .repository .diff-detail-box .diff-detail-actions .ui.button:not(.btn-submit) { 1556 - padding: 0 0.5rem; 1557 - } 1558 1542 } 1559 1543 1560 1544 .repository .diff-detail-box span.status {
+17 -3
web_src/js/features/comp/ImagePaste.js
··· 1 1 import $ from 'jquery'; 2 + import {htmlEscape} from 'escape-goat'; 2 3 import {POST} from '../../modules/fetch.js'; 4 + import {imageInfo} from '../../utils/image.js'; 3 5 4 6 async function uploadFile(file, uploadUrl) { 5 7 const formData = new FormData(); ··· 109 111 110 112 const placeholder = `![${name}](uploading ...)`; 111 113 editor.insertPlaceholder(placeholder); 112 - const data = await uploadFile(img, uploadUrl); 113 - editor.replacePlaceholder(placeholder, `![${name}](/attachments/${data.uuid})`); 114 + 115 + const {uuid} = await uploadFile(img, uploadUrl); 116 + const {width, dppx} = await imageInfo(img); 114 117 115 - const $input = $(`<input name="files" type="hidden">`).attr('id', data.uuid).val(data.uuid); 118 + const url = `/attachments/${uuid}`; 119 + let text; 120 + if (width > 0 && dppx > 1) { 121 + // Scale down images from HiDPI monitors. This uses the <img> tag because it's the only 122 + // method to change image size in Markdown that is supported by all implementations. 123 + text = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(name)}" src="${htmlEscape(url)}">`; 124 + } else { 125 + text = `![${name}](${url})`; 126 + } 127 + editor.replacePlaceholder(placeholder, text); 128 + 129 + const $input = $(`<input name="files" type="hidden">`).attr('id', uuid).val(uuid); 116 130 $files.append($input); 117 131 } 118 132 };
+8 -9
web_src/js/features/repo-migrate.js
··· 1 - import $ from 'jquery'; 2 1 import {hideElem, showElem} from '../utils/dom.js'; 3 2 import {GET, POST} from '../modules/fetch.js'; 4 3 5 4 const {appSubUrl} = window.config; 6 5 7 6 export function initRepoMigrationStatusChecker() { 8 - const $repoMigrating = $('#repo_migrating'); 9 - if (!$repoMigrating.length) return; 7 + const repoMigrating = document.getElementById('repo_migrating'); 8 + if (!repoMigrating) return; 10 9 11 - $('#repo_migrating_retry').on('click', doMigrationRetry); 10 + document.getElementById('repo_migrating_retry').addEventListener('click', doMigrationRetry); 12 11 13 - const task = $repoMigrating.attr('data-migrating-task-id'); 12 + const task = repoMigrating.getAttribute('data-migrating-task-id'); 14 13 15 - // returns true if the refresh still need to be called after a while 14 + // returns true if the refresh still needs to be called after a while 16 15 const refresh = async () => { 17 16 const res = await GET(`${appSubUrl}/user/task/${task}`); 18 17 if (res.status !== 200) return true; // continue to refresh if network error occurs ··· 21 20 22 21 // for all status 23 22 if (data.message) { 24 - $('#repo_migrating_progress_message').text(data.message); 23 + document.getElementById('repo_migrating_progress_message').textContent = data.message; 25 24 } 26 25 27 26 // TaskStatusFinished ··· 37 36 showElem('#repo_migrating_retry'); 38 37 showElem('#repo_migrating_failed'); 39 38 showElem('#repo_migrating_failed_image'); 40 - $('#repo_migrating_failed_error').text(data.message); 39 + document.getElementById('repo_migrating_failed_error').textContent = data.message; 41 40 return false; 42 41 } 43 42 ··· 59 58 } 60 59 61 60 async function doMigrationRetry(e) { 62 - await POST($(e.target).attr('data-migrating-task-retry-url')); 61 + await POST(e.target.getAttribute('data-migrating-task-retry-url')); 63 62 window.location.reload(); 64 63 }
-28
web_src/js/features/user-auth.js
··· 1 - import $ from 'jquery'; 2 1 import {checkAppUrl} from './common-global.js'; 3 2 4 3 export function initUserAuthOauth2() { ··· 21 20 }); 22 21 } 23 22 } 24 - 25 - export function initUserAuthLinkAccountView() { 26 - const $lnkUserPage = $('.page-content.user.link-account'); 27 - if ($lnkUserPage.length === 0) { 28 - return false; 29 - } 30 - 31 - const $signinTab = $lnkUserPage.find('.item[data-tab="auth-link-signin-tab"]'); 32 - const $signUpTab = $lnkUserPage.find('.item[data-tab="auth-link-signup-tab"]'); 33 - const $signInView = $lnkUserPage.find('.tab[data-tab="auth-link-signin-tab"]'); 34 - const $signUpView = $lnkUserPage.find('.tab[data-tab="auth-link-signup-tab"]'); 35 - 36 - $signUpTab.on('click', () => { 37 - $signinTab.removeClass('active'); 38 - $signInView.removeClass('active'); 39 - $signUpTab.addClass('active'); 40 - $signUpView.addClass('active'); 41 - return false; 42 - }); 43 - 44 - $signinTab.on('click', () => { 45 - $signUpTab.removeClass('active'); 46 - $signUpView.removeClass('active'); 47 - $signinTab.addClass('active'); 48 - $signInView.addClass('active'); 49 - }); 50 - }
+1 -2
web_src/js/index.js
··· 23 23 import {initCommentContent, initMarkupContent} from './markup/content.js'; 24 24 import {initPdfViewer} from './render/pdf.js'; 25 25 26 - import {initUserAuthLinkAccountView, initUserAuthOauth2} from './features/user-auth.js'; 26 + import {initUserAuthOauth2} from './features/user-auth.js'; 27 27 import { 28 28 initRepoIssueDue, 29 29 initRepoIssueReferenceRepositorySearch, ··· 178 178 initCommitStatuses(); 179 179 initCaptcha(); 180 180 181 - initUserAuthLinkAccountView(); 182 181 initUserAuthOauth2(); 183 182 initUserAuthWebAuthn(); 184 183 initUserAuthWebAuthnRegister();
+47
web_src/js/utils/image.js
··· 1 + export async function pngChunks(blob) { 2 + const uint8arr = new Uint8Array(await blob.arrayBuffer()); 3 + const chunks = []; 4 + if (uint8arr.length < 12) return chunks; 5 + const view = new DataView(uint8arr.buffer); 6 + if (view.getBigUint64(0) !== 9894494448401390090n) return chunks; 7 + 8 + const decoder = new TextDecoder(); 9 + let index = 8; 10 + while (index < uint8arr.length) { 11 + const len = view.getUint32(index); 12 + chunks.push({ 13 + name: decoder.decode(uint8arr.slice(index + 4, index + 8)), 14 + data: uint8arr.slice(index + 8, index + 8 + len), 15 + }); 16 + index += len + 12; 17 + } 18 + 19 + return chunks; 20 + } 21 + 22 + // decode a image and try to obtain width and dppx. If will never throw but instead 23 + // return default values. 24 + export async function imageInfo(blob) { 25 + let width = 0; // 0 means no width could be determined 26 + let dppx = 1; // 1 dot per pixel for non-HiDPI screens 27 + 28 + if (blob.type === 'image/png') { // only png is supported currently 29 + try { 30 + for (const {name, data} of await pngChunks(blob)) { 31 + const view = new DataView(data.buffer); 32 + if (name === 'IHDR' && data?.length) { 33 + // extract width from mandatory IHDR chunk 34 + width = view.getUint32(0); 35 + } else if (name === 'pHYs' && data?.length) { 36 + // extract dppx from optional pHYs chunk, assuming pixels are square 37 + const unit = view.getUint8(8); 38 + if (unit === 1) { 39 + dppx = Math.round(view.getUint32(0) / 39.3701) / 72; // meter to inch to dppx 40 + } 41 + } 42 + } 43 + } catch {} 44 + } 45 + 46 + return {width, dppx}; 47 + }
+29
web_src/js/utils/image.test.js
··· 1 + import {pngChunks, imageInfo} from './image.js'; 2 + 3 + const pngNoPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAADUlEQVQIHQECAP3/AAAAAgABzePRKwAAAABJRU5ErkJggg=='; 4 + const pngPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAEElEQVQI12OQNZcAIgYIBQAL8gGxdzzM0A=='; 5 + const pngEmpty = 'data:image/png;base64,'; 6 + 7 + async function dataUriToBlob(datauri) { 8 + return await (await globalThis.fetch(datauri)).blob(); 9 + } 10 + 11 + test('pngChunks', async () => { 12 + expect(await pngChunks(await dataUriToBlob(pngNoPhys))).toEqual([ 13 + {name: 'IHDR', data: new Uint8Array([0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 0, 0, 0])}, 14 + {name: 'IDAT', data: new Uint8Array([8, 29, 1, 2, 0, 253, 255, 0, 0, 0, 2, 0, 1])}, 15 + {name: 'IEND', data: new Uint8Array([])}, 16 + ]); 17 + expect(await pngChunks(await dataUriToBlob(pngPhys))).toEqual([ 18 + {name: 'IHDR', data: new Uint8Array([0, 0, 0, 2, 0, 0, 0, 2, 8, 2, 0, 0, 0])}, 19 + {name: 'pHYs', data: new Uint8Array([0, 0, 22, 37, 0, 0, 22, 37, 1])}, 20 + {name: 'IDAT', data: new Uint8Array([8, 215, 99, 144, 53, 151, 0, 34, 6, 8, 5, 0, 11, 242, 1, 177])}, 21 + ]); 22 + expect(await pngChunks(await dataUriToBlob(pngEmpty))).toEqual([]); 23 + }); 24 + 25 + test('imageInfo', async () => { 26 + expect(await imageInfo(await dataUriToBlob(pngNoPhys))).toEqual({width: 1, dppx: 1}); 27 + expect(await imageInfo(await dataUriToBlob(pngPhys))).toEqual({width: 2, dppx: 2}); 28 + expect(await imageInfo(await dataUriToBlob(pngEmpty))).toEqual({width: 0, dppx: 1}); 29 + });