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>
This commit is contained in:
Earl Warren 2024-02-19 23:03:10 +00:00
commit 0533022d63
68 changed files with 810 additions and 350 deletions

View file

@ -156,8 +156,8 @@ endif
FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml
SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC := templates/swagger/v1_json.tmpl
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_EXCLUDE := code.gitea.io/sdk SWAGGER_EXCLUDE := code.gitea.io/sdk
SWAGGER_NEWLINE_COMMAND := -e '$$a\' SWAGGER_NEWLINE_COMMAND := -e '$$a\'
SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g SWAGGER_SPEC_BRANDING := s|Gitea API|Forgejo API|g

View file

@ -55,7 +55,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent
case webhook_module.HookEventPullRequest, case webhook_module.HookEventPullRequest,
webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestSync,
webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestAssign,
webhook_module.HookEventPullRequestLabel: webhook_module.HookEventPullRequestLabel,
webhook_module.HookEventPullRequestReviewRequest,
webhook_module.HookEventPullRequestMilestone:
return true return true
default: default:

View file

@ -186,7 +186,9 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequest,
webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestSync,
webhook_module.HookEventPullRequestAssign, webhook_module.HookEventPullRequestAssign,
webhook_module.HookEventPullRequestLabel: webhook_module.HookEventPullRequestLabel,
webhook_module.HookEventPullRequestReviewRequest,
webhook_module.HookEventPullRequestMilestone:
return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt) return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt)
case // pull_request_review case // pull_request_review
@ -362,13 +364,13 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
} else { } else {
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
// Actions with the same name: // Actions with the same name:
// opened, edited, closed, reopened, assigned, unassigned // opened, edited, closed, reopened, assigned, unassigned, review_requested, review_request_removed, milestoned, demilestoned
// Actions need to be converted: // Actions need to be converted:
// synchronized -> synchronize // synchronized -> synchronize
// label_updated -> labeled // label_updated -> labeled
// label_cleared -> unlabeled // label_cleared -> unlabeled
// Unsupported activity types: // Unsupported activity types:
// converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled // converted_to_draft, ready_for_review, locked, unlocked, auto_merge_enabled, auto_merge_disabled, enqueued, dequeued
action := prPayload.Action action := prPayload.Action
switch action { switch action {

View file

@ -90,6 +90,20 @@ func (ctx *Context) HTML(status int, name base.TplName) {
} }
} }
// JSONTemplate renders the template as JSON response
// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
func (ctx *Context) JSONTemplate(tmpl base.TplName) {
t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
if err != nil {
ctx.ServerError("unable to find template", err)
return
}
ctx.Resp.Header().Set("Content-Type", "application/json")
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
ctx.ServerError("unable to execute template", err)
}
}
// RenderToString renders the template content to a string // RenderToString renders the template content to a string
func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) { func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) {
var buf strings.Builder var buf strings.Builder

View file

@ -5,10 +5,7 @@ package context
import ( import (
"context" "context"
"errors"
"time" "time"
"code.gitea.io/gitea/modules/log"
) )
var _ context.Context = TemplateContext(nil) var _ context.Context = TemplateContext(nil)
@ -36,14 +33,3 @@ func (c TemplateContext) Err() error {
func (c TemplateContext) Value(key any) any { func (c TemplateContext) Value(key any) any {
return c.parentContext().Value(key) return c.parentContext().Value(key)
} }
// DataRaceCheck checks whether the template context function "ctx()" returns the consistent context
// as the current template's rendering context (request context), to help to find data race issues as early as possible.
// When the code is proven to be correct and stable, this function should be removed.
func (c TemplateContext) DataRaceCheck(dataCtx context.Context) (string, error) {
if c.parentContext() != dataCtx {
log.Error("TemplateContext.DataRaceCheck: parent context mismatch\n%s", log.Stack(2))
return "", errors.New("parent context mismatch")
}
return "", nil
}

View file

@ -38,7 +38,7 @@ func NewFuncMap() template.FuncMap {
"Safe": Safe, "Safe": Safe,
"Escape": Escape, "Escape": Escape,
"QueryEscape": url.QueryEscape, "QueryEscape": url.QueryEscape,
"JSEscape": template.JSEscapeString, "JSEscape": JSEscapeSafe,
"Str2html": Str2html, // TODO: rename it to SanitizeHTML "Str2html": Str2html, // TODO: rename it to SanitizeHTML
"URLJoin": util.URLJoin, "URLJoin": util.URLJoin,
"DotEscape": DotEscape, "DotEscape": DotEscape,
@ -214,6 +214,10 @@ func Escape(s any) template.HTML {
panic(fmt.Sprintf("unexpected type %T", s)) panic(fmt.Sprintf("unexpected type %T", s))
} }
func JSEscapeSafe(s string) template.HTML {
return template.HTML(template.JSEscapeString(s))
}
func RenderEmojiPlain(s any) any { func RenderEmojiPlain(s any) any {
switch v := s.(type) { switch v := s.(type) {
case string: case string:

View file

@ -52,3 +52,7 @@ func TestSubjectBodySeparator(t *testing.T) {
"", "",
"Insuficient\n--\nSeparators") "Insuficient\n--\nSeparators")
} }
func TestJSEscapeSafe(t *testing.T) {
assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`))
}

View file

@ -4,6 +4,7 @@
package i18n package i18n
import ( import (
"html/template"
"strings" "strings"
"testing" "testing"
@ -82,6 +83,71 @@ c=22
assert.Equal(t, "22", lang1.TrString("c")) assert.Equal(t, "22", lang1.TrString("c"))
} }
type stringerPointerReceiver struct {
s string
}
func (s *stringerPointerReceiver) String() string {
return s.s
}
type stringerStructReceiver struct {
s string
}
func (s stringerStructReceiver) String() string {
return s.s
}
type errorStructReceiver struct {
s string
}
func (e errorStructReceiver) Error() string {
return e.s
}
type errorPointerReceiver struct {
s string
}
func (e *errorPointerReceiver) Error() string {
return e.s
}
func TestLocaleWithTemplate(t *testing.T) {
ls := NewLocaleStore()
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", []byte(`key=<a>%s</a>`), nil))
lang1, _ := ls.Locale("lang1")
tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML})
tmpl = template.Must(tmpl.Parse(`{{tr "key" .var}}`))
cases := []struct {
in any
want string
}{
{"<str>", "<a>&lt;str&gt;</a>"},
{[]byte("<bytes>"), "<a>[60 98 121 116 101 115 62]</a>"},
{template.HTML("<html>"), "<a><html></a>"},
{stringerPointerReceiver{"<stringerPointerReceiver>"}, "<a>{&lt;stringerPointerReceiver&gt;}</a>"},
{&stringerPointerReceiver{"<stringerPointerReceiver ptr>"}, "<a>&lt;stringerPointerReceiver ptr&gt;</a>"},
{stringerStructReceiver{"<stringerStructReceiver>"}, "<a>&lt;stringerStructReceiver&gt;</a>"},
{&stringerStructReceiver{"<stringerStructReceiver ptr>"}, "<a>&lt;stringerStructReceiver ptr&gt;</a>"},
{errorStructReceiver{"<errorStructReceiver>"}, "<a>&lt;errorStructReceiver&gt;</a>"},
{&errorStructReceiver{"<errorStructReceiver ptr>"}, "<a>&lt;errorStructReceiver ptr&gt;</a>"},
{errorPointerReceiver{"<errorPointerReceiver>"}, "<a>{&lt;errorPointerReceiver&gt;}</a>"},
{&errorPointerReceiver{"<errorPointerReceiver ptr>"}, "<a>&lt;errorPointerReceiver ptr&gt;</a>"},
}
buf := &strings.Builder{}
for _, c := range cases {
buf.Reset()
assert.NoError(t, tmpl.Execute(buf, map[string]any{"var": c.in}))
assert.Equal(t, c.want, buf.String())
}
}
func TestLocaleStoreQuirks(t *testing.T) { func TestLocaleStoreQuirks(t *testing.T) {
const nl = "\n" const nl = "\n"
q := func(q1, s string, q2 ...string) string { q := func(q1, s string, q2 ...string) string {

View file

@ -136,12 +136,14 @@ func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML {
args := slices.Clone(trArgs) args := slices.Clone(trArgs)
for i, v := range args { for i, v := range args {
switch v := v.(type) { switch v := v.(type) {
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
// for most basic types (including template.HTML which is safe), just do nothing and use it
case string: case string:
args[i] = template.HTML(template.HTMLEscapeString(v)) args[i] = template.HTMLEscapeString(v)
case fmt.Stringer: case fmt.Stringer:
args[i] = template.HTMLEscapeString(v.String()) args[i] = template.HTMLEscapeString(v.String())
default: // int, float, include template.HTML default:
// do nothing, just use it args[i] = template.HTMLEscapeString(fmt.Sprint(v))
} }
} }
return template.HTML(l.TrString(trKey, args...)) return template.HTML(l.TrString(trKey, args...))

View file

@ -0,0 +1,17 @@
Copyright (C) 1998-2013, Brian Gladman, Worcester, UK. All
rights reserved.
The redistribution and use of this software (with or without
changes) is allowed without the payment of fees or royalties
provided that:
source code distributions include the above copyright notice,
this list of conditions and the following disclaimer;
binary distributions include the above copyright notice, this
list of conditions and the following disclaimer in their
documentation.
This software is provided 'as is' with no explicit or implied
warranties in respect of its operation, including, but not limited
to, correctness and fitness for purpose.

View file

@ -0,0 +1,11 @@
Copyright (C) 2002 Naval Research Laboratory (NRL/CCS)
Permission to use, copy, modify and distribute this software and
its documentation is hereby granted, provided that both the
copyright notice and this permission notice appear in all copies of
the software, derivative works or modified versions, and any
portions thereof.
NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND
DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
RESULTING FROM THE USE OF THIS SOFTWARE.

View file

@ -0,0 +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.

View file

@ -0,0 +1,16 @@
The copyright holders of Gmsh give you permission to combine Gmsh
with code included in the standard release of Netgen (from Joachim
Sch"oberl), METIS (from George Karypis at the University of
Minnesota), OpenCASCADE (from Open CASCADE S.A.S) and ParaView
(from Kitware, Inc.) under their respective licenses. You may copy
and distribute such a system following the terms of the GNU GPL for
Gmsh and the licenses of the other code concerned, provided that
you include the source code of that other code when and as the GNU
GPL requires distribution of source code.
Note that people who make modified versions of Gmsh are not
obligated to grant this special exception for their modified
versions; it is their choice whether to do so. The GNU General
Public License gives permission to release a modified version
without this exception; this exception also makes it possible to
release a modified version which carries forward this exception.

View file

@ -0,0 +1,13 @@
Copyright (C) 1995,1996,1997,1998 Lars Fenneberg <lf@elemental.net>
Permission to use, copy, modify, and distribute this software for any
purpose and without fee is hereby granted, provided that this copyright and
permission notice appear on all copies and supporting documentation, the
name of Lars Fenneberg not be used in advertising or publicity pertaining to
distribution of the program without specific prior permission, and notice be
given in supporting documentation that copying and distribution is by
permission of Lars Fenneberg.
Lars Fenneberg makes no representations about the suitability of this
software for any purpose. It is provided "as is" without express or implied
warranty.

View file

@ -0,0 +1,9 @@
This software is available with usual "research" terms with
the aim of retain credits of the software. Permission to use,
copy, modify and distribute this software for any purpose and
without fee is hereby granted, provided that the above copyright
notice and this permission notice appear in all copies, and
the name of INRIA, IMAG, or any contributor not be used in
advertising or publicity pertaining to this material without
the prior explicit permission. The software is provided "as
is" without any warranties, support or liabilities of any kind.

View file

@ -0,0 +1,25 @@
Copyright (c) 1995 Eric Rosenquist. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. The name(s) of the authors of this software must not be used to
endorse or promote products derived from this software without
prior written permission.
THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View file

@ -0,0 +1,25 @@
Copyright (c) 1993-2002 Paul Mackerras. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. The name(s) of the authors of this software must not be used to
endorse or promote products derived from this software without
prior written permission.
3. Redistributions of any form whatsoever must retain the following
acknowledgment:
"This product includes software developed by Paul Mackerras
<paulus@ozlabs.org>".
THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View file

@ -0,0 +1,33 @@
Copyright, OpenVision Technologies, Inc., 1993-1996, All Rights
Reserved
WARNING: Retrieving the OpenVision Kerberos Administration system
source code, as described below, indicates your acceptance of the
following terms. If you do not agree to the following terms, do
not retrieve the OpenVision Kerberos administration system.
You may freely use and distribute the Source Code and Object Code
compiled from it, with or without modification, but this Source
Code is provided to you "AS IS" EXCLUSIVE OF ANY WARRANTY,
INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY OR
FITNESS FOR A PARTICULAR PURPOSE, OR ANY OTHER WARRANTY, WHETHER
EXPRESS OR IMPLIED. IN NO EVENT WILL OPENVISION HAVE ANY LIABILITY
FOR ANY LOST PROFITS, LOSS OF DATA OR COSTS OF PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES, OR FOR ANY SPECIAL, INDIRECT, OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, INCLUDING,
WITHOUT LIMITATION, THOSE RESULTING FROM THE USE OF THE SOURCE
CODE, OR THE FAILURE OF THE SOURCE CODE TO PERFORM, OR FOR ANY
OTHER REASON.
OpenVision retains all copyrights in the donated Source Code.
OpenVision also retains copyright to derivative works of the Source
Code, whether created by OpenVision or by a third party. The
OpenVision copyright notice must be preserved if derivative works
are made based on the donated Source Code.
OpenVision Technologies, Inc. has donated this Kerberos
Administration system to MIT for inclusion in the standard Kerberos
5 distribution. This donation underscores our commitment to
continuing Kerberos technology development and our gratitude for
the valuable work which has been performed by MIT and the Kerberos
community.

13
options/license/Sun-PPP Normal file
View file

@ -0,0 +1,13 @@
Copyright (c) 2001 by Sun Microsystems, Inc.
All rights reserved.
Non-exclusive rights to redistribute, modify, translate, and use
this software in source and binary forms, in whole or in part, is
hereby granted, provided that the above copyright notice is
duplicated in any source form, and that neither the name of the
copyright holder nor the author is used to endorse or promote
products derived from this software.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

View file

@ -0,0 +1,19 @@
[C] The Regents of the University of Michigan and Merit Network, Inc. 1992,
1993, 1994, 1995 All Rights Reserved
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted, provided
that the above copyright notice and this permission notice appear in all
copies of the software and derivative works or modified versions thereof,
and that both the copyright notice and this permission and disclaimer
notice appear in supporting documentation.
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
UNIVERSITY OF MICHIGAN AND MERIT NETWORK, INC. DO NOT WARRANT THAT THE
FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
University of Michigan and Merit Network, Inc. shall not be liable for any
special, indirect, incidental or consequential damages with respect to any
claim by Licensee or any third party arising from use of the software.

View file

@ -0,0 +1,11 @@
Written by Solar Designer <solar at openwall.com> in 1998-2014.
No copyright is claimed, and the software is hereby placed in the public
domain. In case this attempt to disclaim copyright and place the software
in the public domain is deemed null and void, then the software is
Copyright (c) 1998-2014 Solar Designer and it is hereby released to the
general public under the following terms:
Redistribution and use in source and binary forms, with or without
modification, are permitted.
There's ABSOLUTELY NO WARRANTY, express or implied.

6
options/license/gtkbook Normal file
View file

@ -0,0 +1,6 @@
Copyright 2005 Syd Logan, All Rights Reserved
This code is distributed without warranty. You are free to use
this code for any purpose, however, if this code is republished or
redistributed in its original form, as hardcopy or electronically,
then you must include this copyright notice along with the code.

View file

@ -0,0 +1,6 @@
Copyright 2001, softSurfer (www.softsurfer.com)
This code may be freely used and modified for any purpose
providing that this copyright notice is included with it.
SoftSurfer makes no warranty for this code, and cannot be held
liable for any real or imagined damage resulting from its use.
Users of this code must verify correctness for their application.

View file

@ -2023,15 +2023,10 @@ activity.git_stats_and_deletions = and
activity.git_stats_deletion_1 = %d deletion activity.git_stats_deletion_1 = %d deletion
activity.git_stats_deletion_n = %d deletions activity.git_stats_deletion_n = %d deletions
contributors = Contributors
contributors.contribution_type.filter_label = Contribution type: contributors.contribution_type.filter_label = Contribution type:
contributors.contribution_type.commits = Commits contributors.contribution_type.commits = Commits
contributors.contribution_type.additions = Additions contributors.contribution_type.additions = Additions
contributors.contribution_type.deletions = Deletions contributors.contribution_type.deletions = Deletions
contributors.loading_title = Loading contributions...
contributors.loading_title_failed = Could not load contributions
contributors.loading_info = This might take a bit…
contributors.component_failed_to_load = An unexpected error happened.
search = Search search = Search
search.search_repo = Search repository search.search_repo = Search repository
@ -2652,6 +2647,13 @@ error.csv.too_large = Can't render this file because it is too large.
error.csv.unexpected = Can't render this file because it contains an unexpected character in line %d and column %d. error.csv.unexpected = Can't render this file because it contains an unexpected character in line %d and column %d.
error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d. error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d.
[graphs]
component_loading = Loading %s...
component_loading_failed = Could not load %s
component_loading_info = This might take a bit…
component_failed_to_load = An unexpected error happened.
contributors.what = contributions
[org] [org]
org_name_holder = Organization Name org_name_holder = Organization Name
org_full_name_holder = Organization Full Name org_full_name_holder = Organization Full Name

View file

@ -8,7 +8,7 @@
// //
// Schemes: https, http // Schemes: https, http
// BasePath: /api/v1 // BasePath: /api/v1
// Version: {{AppVer | JSEscape | Safe}} // Version: {{AppVer | JSEscape}}
// License: MIT http://opensource.org/licenses/MIT // License: MIT http://opensource.org/licenses/MIT
// //
// Consumes: // Consumes:

View file

@ -579,16 +579,8 @@ func GrantApplicationOAuth(ctx *context.Context) {
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
func OIDCWellKnown(ctx *context.Context) { func OIDCWellKnown(ctx *context.Context) {
t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown", nil)
if err != nil {
ctx.ServerError("unable to find template", err)
return
}
ctx.Resp.Header().Set("Content-Type", "application/json")
ctx.Data["SigningKey"] = oauth2.DefaultSigningKey ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
if err = t.Execute(ctx.Resp, ctx.Data); err != nil { ctx.JSONTemplate("user/auth/oidc_wellknown")
ctx.ServerError("unable to execute template", err)
}
} }
// OIDCKeys generates the JSON Web Key Set // OIDCKeys generates the JSON Web Key Set

View file

@ -18,7 +18,7 @@ const (
// Contributors render the page to show repository contributors graph // Contributors render the page to show repository contributors graph
func Contributors(ctx *context.Context) { func Contributors(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.contributors") ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.contributors")
ctx.Data["PageIsActivity"] = true ctx.Data["PageIsActivity"] = true
ctx.Data["PageIsContributors"] = true ctx.Data["PageIsContributors"] = true

View file

@ -662,6 +662,24 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
} }
if pb != nil && pb.EnableStatusCheck { if pb != nil && pb.EnableStatusCheck {
var missingRequiredChecks []string
for _, requiredContext := range pb.StatusCheckContexts {
contextFound := false
matchesRequiredContext := createRequiredContextMatcher(requiredContext)
for _, presentStatus := range commitStatuses {
if matchesRequiredContext(presentStatus.Context) {
contextFound = true
break
}
}
if !contextFound {
missingRequiredChecks = append(missingRequiredChecks, requiredContext)
}
}
ctx.Data["MissingRequiredChecks"] = missingRequiredChecks
ctx.Data["is_context_required"] = func(context string) bool { ctx.Data["is_context_required"] = func(context string) bool {
for _, c := range pb.StatusCheckContexts { for _, c := range pb.StatusCheckContexts {
if c == context { if c == context {
@ -730,6 +748,18 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return compareInfo return compareInfo
} }
func createRequiredContextMatcher(requiredContext string) func(string) bool {
if gp, err := glob.Compile(requiredContext); err == nil {
return func(contextToCheck string) bool {
return gp.Match(contextToCheck)
}
}
return func(contextToCheck string) bool {
return requiredContext == contextToCheck
}
}
type pullCommitList struct { type pullCommitList struct {
Commits []pull_service.CommitInfo `json:"commits"` Commits []pull_service.CommitInfo `json:"commits"`
LastReviewCommitSha string `json:"last_review_commit_sha"` LastReviewCommitSha string `json:"last_review_commit_sha"`

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -67,6 +68,88 @@ func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model
return nil return nil
} }
type ReleaseInfo struct {
Release *repo_model.Release
CommitStatus *git_model.CommitStatus
CommitStatuses []*git_model.CommitStatus
}
func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) ([]*ReleaseInfo, error) {
releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
return nil, err
}
for _, release := range releases {
release.Repo = ctx.Repo.Repository
}
if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil {
return nil, err
}
// Temporary cache commits count of used branches to speed up.
countCache := make(map[string]int64)
cacheUsers := make(map[int64]*user_model.User)
if ctx.Doer != nil {
cacheUsers[ctx.Doer.ID] = ctx.Doer
}
var ok bool
canReadActions := ctx.Repo.CanRead(unit.TypeActions)
releaseInfos := make([]*ReleaseInfo, 0, len(releases))
for _, r := range releases {
if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
r.Publisher = user_model.NewGhostUser()
} else {
return nil, err
}
}
cacheUsers[r.PublisherID] = r.Publisher
}
r.Note, err = markdown.RenderString(&markup.RenderContext{
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, r.Note)
if err != nil {
return nil, err
}
if !r.IsDraft {
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
return nil, err
}
}
info := &ReleaseInfo{
Release: r,
}
if canReadActions {
statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptions{ListAll: true})
if err != nil {
return nil, err
}
info.CommitStatus = git_model.CalcCommitStatus(statuses)
info.CommitStatuses = statuses
}
releaseInfos = append(releaseInfos, info)
}
return releaseInfos, nil
}
// Releases render releases list page // Releases render releases list page
func Releases(ctx *context.Context) { func Releases(ctx *context.Context) {
ctx.Data["PageIsReleaseList"] = true ctx.Data["PageIsReleaseList"] = true
@ -91,77 +174,21 @@ func Releases(ctx *context.Context) {
writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
opts := repo_model.FindReleasesOptions{ releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{
ListOptions: listOptions, ListOptions: listOptions,
// only show draft releases for users who can write, read-only users shouldn't see draft releases. // only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: writeAccess, IncludeDrafts: writeAccess,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
} })
releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil { if err != nil {
ctx.ServerError("GetReleasesByRepoID", err) ctx.ServerError("getReleaseInfos", err)
return return
} }
for _, release := range releases {
release.Repo = ctx.Repo.Repository
}
if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil {
ctx.ServerError("GetReleaseAttachments", err)
return
}
// Temporary cache commits count of used branches to speed up.
countCache := make(map[string]int64)
cacheUsers := make(map[int64]*user_model.User)
if ctx.Doer != nil {
cacheUsers[ctx.Doer.ID] = ctx.Doer
}
var ok bool
for _, r := range releases {
if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
r.Publisher = user_model.NewGhostUser()
} else {
ctx.ServerError("GetUserByID", err)
return
}
}
cacheUsers[r.PublisherID] = r.Publisher
}
r.Note, err = markdown.RenderString(&markup.RenderContext{
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, r.Note)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
if r.IsDraft {
continue
}
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
ctx.ServerError("calReleaseNumCommitsBehind", err)
return
}
}
ctx.Data["Releases"] = releases ctx.Data["Releases"] = releases
numReleases := ctx.Data["NumReleases"].(int64) numReleases := ctx.Data["NumReleases"].(int64)
pager := context.NewPagination(int(numReleases), opts.PageSize, opts.Page, 5) pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
pager.SetDefaultParams(ctx) pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
@ -249,15 +276,24 @@ func SingleRelease(ctx *context.Context) {
writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Params("*")) releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{Page: 1, PageSize: 1},
RepoID: ctx.Repo.Repository.ID,
TagNames: []string{ctx.Params("*")},
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: writeAccess,
})
if err != nil { if err != nil {
if repo_model.IsErrReleaseNotExist(err) { ctx.ServerError("getReleaseInfos", err)
ctx.NotFound("GetRelease", err)
return
}
ctx.ServerError("GetReleasesByRepoID", err)
return return
} }
if len(releases) != 1 {
ctx.NotFound("SingleRelease", err)
return
}
release := releases[0].Release
ctx.Data["PageIsSingleTag"] = release.IsTag ctx.Data["PageIsSingleTag"] = release.IsTag
if release.IsTag { if release.IsTag {
ctx.Data["Title"] = release.TagName ctx.Data["Title"] = release.TagName
@ -265,43 +301,7 @@ func SingleRelease(ctx *context.Context) {
ctx.Data["Title"] = release.Title ctx.Data["Title"] = release.Title
} }
release.Repo = ctx.Repo.Repository ctx.Data["Releases"] = releases
err = repo_model.GetReleaseAttachments(ctx, release)
if err != nil {
ctx.ServerError("GetReleaseAttachments", err)
return
}
release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
release.Publisher = user_model.NewGhostUser()
} else {
ctx.ServerError("GetUserByID", err)
return
}
}
if !release.IsDraft {
if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil {
ctx.ServerError("calReleaseNumCommitsBehind", err)
return
}
}
release.Note, err = markdown.RenderString(&markup.RenderContext{
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, release.Note)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
ctx.Data["Releases"] = []*repo_model.Release{release}
ctx.HTML(http.StatusOK, tplReleasesList) ctx.HTML(http.StatusOK, tplReleasesList)
} }

View file

@ -4,6 +4,8 @@
package user package user
import ( import (
"net/url"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
@ -37,7 +39,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID) ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID) ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
ctx.Data["UserLocationMapURL"] = setting.Service.UserLocationMapURL ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
// Show OpenID URIs // Show OpenID URIs
openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID) openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)

View file

@ -4,22 +4,10 @@
package web package web
import ( import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
) )
// tplSwaggerV1Json swagger v1 json template
const tplSwaggerV1Json base.TplName = "swagger/v1_json"
// SwaggerV1Json render swagger v1 json // SwaggerV1Json render swagger v1 json
func SwaggerV1Json(ctx *context.Context) { func SwaggerV1Json(ctx *context.Context) {
t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json), nil) ctx.JSONTemplate("swagger/v1_json")
if err != nil {
ctx.ServerError("unable to find template", err)
return
}
ctx.Resp.Header().Set("Content-Type", "application/json")
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
ctx.ServerError("unable to execute template", err)
}
} }

View file

@ -64,6 +64,9 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
return fmt.Errorf("head of pull request is missing in event payload") return fmt.Errorf("head of pull request is missing in event payload")
} }
sha = payload.PullRequest.Head.Sha sha = payload.PullRequest.Head.Sha
case webhook_module.HookEventRelease:
event = string(run.Event)
sha = run.CommitSHA
default: default:
return nil return nil
} }

View file

@ -55,6 +55,47 @@ func (n *actionsNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu
}).Notify(withMethod(ctx, "NewIssue")) }).Notify(withMethod(ctx, "NewIssue"))
} }
// IssueChangeContent notifies change content of issue
func (n *actionsNotifier) IssueChangeContent(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldContent string) {
ctx = withMethod(ctx, "IssueChangeContent")
var err error
if err = issue.LoadRepo(ctx); err != nil {
log.Error("LoadRepo: %v", err)
return
}
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
if issue.IsPull {
if err = issue.LoadPullRequest(ctx); err != nil {
log.Error("loadPullRequest: %v", err)
return
}
newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequest).
WithDoer(doer).
WithPayload(&api.PullRequestPayload{
Action: api.HookIssueEdited,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}),
Sender: convert.ToUser(ctx, doer, nil),
}).
WithPullRequest(issue.PullRequest).
Notify(ctx)
return
}
newNotifyInputFromIssue(issue, webhook_module.HookEventIssues).
WithDoer(doer).
WithPayload(&api.IssuePayload{
Action: api.HookIssueEdited,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
}).
Notify(ctx)
}
// IssueChangeStatus notifies close or reopen issue to notifiers // IssueChangeStatus notifies close or reopen issue to notifiers
func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, _ *issues_model.Comment, isClosed bool) { func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, _ *issues_model.Comment, isClosed bool) {
ctx = withMethod(ctx, "IssueChangeStatus") ctx = withMethod(ctx, "IssueChangeStatus")
@ -101,11 +142,40 @@ func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_mode
Notify(ctx) Notify(ctx)
} }
// IssueChangeAssignee notifies assigned or unassigned to notifiers
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) {
ctx = withMethod(ctx, "IssueChangeAssignee")
var action api.HookIssueAction
if removed {
action = api.HookIssueUnassigned
} else {
action = api.HookIssueAssigned
}
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestAssign, action)
}
// IssueChangeMilestone notifies assignee to notifiers
func (n *actionsNotifier) IssueChangeMilestone(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) {
ctx = withMethod(ctx, "IssueChangeMilestone")
var action api.HookIssueAction
if issue.MilestoneID > 0 {
action = api.HookIssueMilestoned
} else {
action = api.HookIssueDemilestoned
}
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestMilestone, action)
}
func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue,
_, _ []*issues_model.Label, _, _ []*issues_model.Label,
) { ) {
ctx = withMethod(ctx, "IssueChangeLabels") ctx = withMethod(ctx, "IssueChangeLabels")
notifyIssueChange(ctx, doer, issue, webhook_module.HookEventPullRequestLabel, api.HookIssueLabelUpdated)
}
func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, event webhook_module.HookEventType, action api.HookIssueAction) {
var err error var err error
if err = issue.LoadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
log.Error("LoadRepo: %v", err) log.Error("LoadRepo: %v", err)
@ -117,20 +187,15 @@ func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
return return
} }
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
if issue.IsPull { if issue.IsPull {
if err = issue.LoadPullRequest(ctx); err != nil { if err = issue.LoadPullRequest(ctx); err != nil {
log.Error("loadPullRequest: %v", err) log.Error("loadPullRequest: %v", err)
return return
} }
if err = issue.PullRequest.LoadIssue(ctx); err != nil { newNotifyInputFromIssue(issue, event).
log.Error("LoadIssue: %v", err)
return
}
newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestLabel).
WithDoer(doer). WithDoer(doer).
WithPayload(&api.PullRequestPayload{ WithPayload(&api.PullRequestPayload{
Action: api.HookIssueLabelUpdated, Action: action,
Index: issue.Index, Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}), Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}),
@ -140,10 +205,11 @@ func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
Notify(ctx) Notify(ctx)
return return
} }
newNotifyInputFromIssue(issue, webhook_module.HookEventIssueLabel). permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
newNotifyInputFromIssue(issue, event).
WithDoer(doer). WithDoer(doer).
WithPayload(&api.IssuePayload{ WithPayload(&api.IssuePayload{
Action: api.HookIssueLabelUpdated, Action: action,
Index: issue.Index, Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
@ -305,6 +371,39 @@ func (n *actionsNotifier) PullRequestReview(ctx context.Context, pr *issues_mode
}).Notify(ctx) }).Notify(ctx)
} }
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) {
if !issue.IsPull {
log.Warn("PullRequestReviewRequest: issue is not a pull request: %v", issue.ID)
return
}
ctx = withMethod(ctx, "PullRequestReviewRequest")
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
if err := issue.LoadPullRequest(ctx); err != nil {
log.Error("LoadPullRequest failed: %v", err)
return
}
var action api.HookIssueAction
if isRequest {
action = api.HookIssueReviewRequested
} else {
action = api.HookIssueReviewRequestRemoved
}
newNotifyInputFromIssue(issue, webhook_module.HookEventPullRequestReviewRequest).
WithDoer(doer).
WithPayload(&api.PullRequestPayload{
Action: action,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
RequestedReviewer: convert.ToUser(ctx, reviewer, nil),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
}).
WithPullRequest(issue.PullRequest).
Notify(ctx)
}
func (*actionsNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { func (*actionsNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
ctx = withMethod(ctx, "MergePullRequest") ctx = withMethod(ctx, "MergePullRequest")

View file

@ -18,7 +18,7 @@ func (source *Source) FromDB(bs []byte) error {
return nil return nil
} }
// ToDB exports an SMTPConfig to a serialized format. // ToDB exports the config to a byte slice to be saved into database (this method is just dummy and does nothing for DB source)
func (source *Source) ToDB() ([]byte, error) { func (source *Source) ToDB() ([]byte, error) {
return nil, nil return nil, nil
} }

View file

@ -52,6 +52,10 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
} }
} }
if matchedCount != len(requiredContexts) {
return structs.CommitStatusPending
}
if matchedCount == 0 { if matchedCount == 0 {
status := git_model.CalcCommitStatus(commitStatuses) status := git_model.CalcCommitStatus(commitStatuses)
if status != nil { if status != nil {

View file

@ -321,14 +321,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
return nil return nil
} }
lowerTags := make([]string, 0, len(tags))
for _, tag := range tags {
lowerTags = append(lowerTags, strings.ToLower(tag))
}
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID, RepoID: repo.ID,
TagNames: lowerTags, TagNames: tags,
}) })
if err != nil { if err != nil {
return fmt.Errorf("db.Find[repo_model.Release]: %w", err) return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
@ -338,6 +333,11 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
relMap[rel.LowerTagName] = rel relMap[rel.LowerTagName] = rel
} }
lowerTags := make([]string, 0, len(tags))
for _, tag := range tags {
lowerTags = append(lowerTags, strings.ToLower(tag))
}
newReleases := make([]*repo_model.Release, 0, len(lowerTags)-len(relMap)) newReleases := make([]*repo_model.Release, 0, len(lowerTags)-len(relMap))
emailToUser := make(map[string]*user_model.User) emailToUser := make(map[string]*user_model.User)

View file

@ -88,7 +88,7 @@
{{ctx.Locale.Tr "packages.settings.delete"}} {{ctx.Locale.Tr "packages.settings.delete"}}
</div> </div>
<div class="content"> <div class="content">
{{ctx.Locale.Tr "packages.settings.delete.notice" `<span class="name"></span>` `<span class="dataVersion"></span>` | Safe}} {{ctx.Locale.Tr "packages.settings.delete.notice" (`<span class="name"></span>`|Safe) (`<span class="dataVersion"></span>`|Safe)}}
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</div> </div>

View file

@ -101,7 +101,7 @@
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "repo.settings.delete_desc"}}</p> <p>{{ctx.Locale.Tr "repo.settings.delete_desc"}}</p>
{{ctx.Locale.Tr "repo.settings.delete_notices_2" `<span class="name"></span>` | Safe}}<br> {{ctx.Locale.Tr "repo.settings.delete_notices_2" (`<span class="name"></span>`|Safe)}}<br>
{{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}<br> {{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}<br>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}

View file

@ -39,7 +39,7 @@
{{ctx.Locale.Tr "admin.monitor.process.cancel"}} {{ctx.Locale.Tr "admin.monitor.process.cancel"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" `<span class="name"></span>` | Safe}}</p> <p>{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" (`<span class="name"></span>`|Safe)}}</p>
<p>{{ctx.Locale.Tr "admin.monitor.process.cancel_desc"}}</p> <p>{{ctx.Locale.Tr "admin.monitor.process.cancel_desc"}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}

View file

@ -26,7 +26,7 @@
<div class="inline field {{if .Err_Visibility}}error{{end}}"> <div class="inline field {{if .Err_Visibility}}error{{end}}">
<span class="inline required field"><label for="visibility">{{ctx.Locale.Tr "settings.visibility"}}</label></span> <span class="inline required field"><label for="visibility">{{ctx.Locale.Tr "settings.visibility"}}</label></span>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">
<input type="hidden" id="visibility" name="visibility" value="{{if .visibility}}{{.visibility}}{{else}}{{printf "%d" .DefaultUserVisibilityMode}}{{end}}"> <input type="hidden" id="visibility" name="visibility" value="{{if .visibility}}{{printf "%d" .visibility}}{{else}}{{printf "%d" .DefaultUserVisibilityMode}}{{end}}">
<div class="text"> <div class="text">
{{if .DefaultUserVisibilityMode.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}} {{if .DefaultUserVisibilityMode.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}}
{{if .DefaultUserVisibilityMode.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}} {{if .DefaultUserVisibilityMode.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}

View file

@ -16,6 +16,5 @@
<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> <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>
{{template "custom/footer" .}} {{template "custom/footer" .}}
{{ctx.DataRaceCheck $.Context}}
</body> </body>
</html> </html>

View file

@ -31,7 +31,6 @@
{{template "custom/header" .}} {{template "custom/header" .}}
</head> </head>
<body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-ext="morph" hx-push-url="false"> <body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-ext="morph" hx-push-url="false">
{{ctx.DataRaceCheck $.Context}}
{{template "custom/body_outer_pre" .}} {{template "custom/body_outer_pre" .}}
<div class="full height"> <div class="full height">

View file

@ -73,7 +73,7 @@
{{ctx.Locale.Tr "org.members.leave"}} {{ctx.Locale.Tr "org.members.leave"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p> <p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|Safe)}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</div> </div>
@ -82,7 +82,7 @@
{{ctx.Locale.Tr "org.members.remove"}} {{ctx.Locale.Tr "org.members.remove"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataOrganizationName"></span>` | Safe}}</p> <p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|Safe) (`<span class="dataOrganizationName"></span>`|Safe)}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</div> </div>

View file

@ -81,7 +81,7 @@
{{ctx.Locale.Tr "org.members.remove"}} {{ctx.Locale.Tr "org.members.remove"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "org.members.remove.detail" `<span class="name"></span>` `<span class="dataTeamName"></span>` | Safe}}</p> <p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|Safe) (`<span class="dataTeamName"></span>`|Safe)}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</div> </div>

View file

@ -88,7 +88,7 @@
{{ctx.Locale.Tr "org.teams.leave"}} {{ctx.Locale.Tr "org.teams.leave"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p> <p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|Safe)}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</div> </div>

View file

@ -49,7 +49,7 @@
{{ctx.Locale.Tr "org.teams.leave"}} {{ctx.Locale.Tr "org.teams.leave"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "org.teams.leave.detail" `<span class="name"></span>` | Safe}}</p> <p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|Safe)}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</div> </div>

View file

@ -88,7 +88,7 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.branch.new_branch_from" `<span class="text" id="modal-create-branch-from-span"></span>` | Safe}} {{ctx.Locale.Tr "repo.branch.new_branch_from" (`<span class="text" id="modal-create-branch-from-span"></span>`|Safe)}}
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
@ -113,7 +113,7 @@
<input type="hidden" name="create_tag" value="true"> <input type="hidden" name="create_tag" value="true">
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.tag.create_tag_from" `<span class="text" id="modal-create-tag-from-span"></span>` | Safe}} {{ctx.Locale.Tr "repo.tag.create_tag_from" (`<span class="text" id="modal-create-tag-from-span"></span>`|Safe)}}
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
@ -184,7 +184,7 @@
</div> </div>
</div> </div>
{{if .Commit.Signature}} {{if .Commit.Signature}}
<div class="ui bottom attached message gt-text-left gt-df gt-ac gt-sb commit-header-row gt-fw {{$class}}"> <div class="ui bottom attached message gt-text-left gt-df gt-ac gt-sb commit-header-row gt-fw gt-mb-0 {{$class}}">
<div class="gt-df gt-ac"> <div class="gt-df gt-ac">
{{if .Verification.Verified}} {{if .Verification.Verified}}
{{if ne .Verification.SigningUser.ID 0}} {{if ne .Verification.SigningUser.ID 0}}

View file

@ -1,10 +1,10 @@
{{if .Statuses}} {{if .Statuses}}
{{if and (eq (len .Statuses) 1) .Status.TargetURL}} {{if and (eq (len .Statuses) 1) .Status.TargetURL}}
<a class="gt-vm gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}"> <a class="gt-vm {{.AdditionalClasses}} gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}">
{{template "repo/commit_status" .Status}} {{template "repo/commit_status" .Status}}
</a> </a>
{{else}} {{else}}
<span class="gt-vm" data-tippy="commit-statuses" tabindex="0"> <span class="gt-vm {{.AdditionalClasses}}" data-tippy="commit-statuses" tabindex="0">
{{template "repo/commit_status" .Status}} {{template "repo/commit_status" .Status}}
</span> </span>
{{end}} {{end}}

View file

@ -4,10 +4,10 @@
data-locale-contribution-type-commits="{{ctx.Locale.Tr "repo.contributors.contribution_type.commits"}}" data-locale-contribution-type-commits="{{ctx.Locale.Tr "repo.contributors.contribution_type.commits"}}"
data-locale-contribution-type-additions="{{ctx.Locale.Tr "repo.contributors.contribution_type.additions"}}" data-locale-contribution-type-additions="{{ctx.Locale.Tr "repo.contributors.contribution_type.additions"}}"
data-locale-contribution-type-deletions="{{ctx.Locale.Tr "repo.contributors.contribution_type.deletions"}}" data-locale-contribution-type-deletions="{{ctx.Locale.Tr "repo.contributors.contribution_type.deletions"}}"
data-locale-loading-title="{{ctx.Locale.Tr "repo.contributors.loading_title"}}" data-locale-loading-title="{{ctx.Locale.Tr "graphs.component_loading" (ctx.Locale.Tr "graphs.contributors.what")}}"
data-locale-loading-title-failed="{{ctx.Locale.Tr "repo.contributors.loading_title_failed"}}" data-locale-loading-title-failed="{{ctx.Locale.Tr "graphs.component_loading_failed" (ctx.Locale.Tr "graphs.contributors.what")}}"
data-locale-loading-info="{{ctx.Locale.Tr "repo.contributors.loading_info"}}" data-locale-loading-info="{{ctx.Locale.Tr "graphs.component_loading_info"}}"
data-locale-component-failed-to-load="{{ctx.Locale.Tr "repo.contributors.component_failed_to_load"}}" data-locale-component-failed-to-load="{{ctx.Locale.Tr "graphs.component_failed_to_load"}}"
> >
</div> </div>
{{end}} {{end}}

View file

@ -1,7 +1,7 @@
{{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}} {{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}}
<div> <div>
<div class="diff-detail-box diff-box"> <div class="diff-detail-box diff-box">
<div class="gt-df gt-ac gt-fw"> <div class="gt-df gt-ac gt-fw gt-gap-3 gt-ml-1">
{{if $showFileTree}} {{if $showFileTree}}
<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"}}"> <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"}}">
{{/* the icon meaning is reversed here, "octicon-sidebar-collapse" means show the file tree */}} {{/* the icon meaning is reversed here, "octicon-sidebar-collapse" means show the file tree */}}

View file

@ -112,9 +112,9 @@
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{$link := printf "%s/commit/%s" $.Repository.Link ($.Issue.PullRequest.MergedCommitID|PathEscape)}} {{$link := printf "%s/commit/%s" $.Repository.Link ($.Issue.PullRequest.MergedCommitID|PathEscape)}}
{{if eq $.Issue.PullRequest.Status 3}} {{if eq $.Issue.PullRequest.Status 3}}
{{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}} {{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}}
{{else}} {{else}}
{{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}} {{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}}
{{end}} {{end}}
</span> </span>
</div> </div>

View file

@ -24,6 +24,7 @@
{{template "repo/pulls/status" (dict {{template "repo/pulls/status" (dict
"CommitStatus" .LatestCommitStatus "CommitStatus" .LatestCommitStatus
"CommitStatuses" .LatestCommitStatuses "CommitStatuses" .LatestCommitStatuses
"MissingRequiredChecks" .MissingRequiredChecks
"ShowHideChecks" true "ShowHideChecks" true
"is_context_required" .is_context_required "is_context_required" .is_context_required
)}} )}}
@ -38,7 +39,7 @@
{{ctx.Locale.Tr "repo.pulls.merged_success"}} {{ctx.Locale.Tr "repo.pulls.merged_success"}}
</h3> </h3>
<div class="merge-section-info"> <div class="merge-section-info">
{{ctx.Locale.Tr "repo.pulls.merged_info_text" (printf "<code>%s</code>" (.HeadTarget | Escape)) | Str2html}} {{ctx.Locale.Tr "repo.pulls.merged_info_text" (printf "<code>%s</code>" (.HeadTarget | Escape) | Safe)}}
</div> </div>
</div> </div>
<div class="item-section-right"> <div class="item-section-right">

View file

@ -2,6 +2,7 @@
Template Attributes: Template Attributes:
* CommitStatus: summary of all commit status state * CommitStatus: summary of all commit status state
* CommitStatuses: all commit status elements * CommitStatuses: all commit status elements
* MissingRequiredChecks: commit check contexts that are required by branch protection but not present
* ShowHideChecks: whether use a button to show/hide the checks * ShowHideChecks: whether use a button to show/hide the checks
* is_context_required: Used in pull request commit status check table * is_context_required: Used in pull request commit status check table
*/}} */}}
@ -9,7 +10,7 @@ Template Attributes:
{{if .CommitStatus}} {{if .CommitStatus}}
<div class="commit-status-panel"> <div class="commit-status-panel">
<div class="ui top attached header commit-status-header"> <div class="ui top attached header commit-status-header">
{{if eq .CommitStatus.State "pending"}} {{if or (eq .CommitStatus.State "pending") (.MissingRequiredChecks)}}
{{ctx.Locale.Tr "repo.pulls.status_checking"}} {{ctx.Locale.Tr "repo.pulls.status_checking"}}
{{else if eq .CommitStatus.State "success"}} {{else if eq .CommitStatus.State "success"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_success"}} {{ctx.Locale.Tr "repo.pulls.status_checks_success"}}
@ -46,6 +47,13 @@ Template Attributes:
</div> </div>
</div> </div>
{{end}} {{end}}
{{range .MissingRequiredChecks}}
<div class="commit-status-item">
{{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}}
<div class="status-context gt-ellipsis">{{.}}</div>
<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>
</div>
{{end}}
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -5,90 +5,90 @@
{{template "base/alert" .}} {{template "base/alert" .}}
{{template "repo/release_tag_header" .}} {{template "repo/release_tag_header" .}}
<ul id="release-list"> <ul id="release-list">
{{range $idx, $release := .Releases}} {{range $idx, $info := .Releases}}
{{$release := $info.Release}}
<li class="ui grid"> <li class="ui grid">
<div class="ui four wide column meta"> <div class="ui four wide column meta">
<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> <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>
{{if and .Sha1 ($.Permission.CanRead $.UnitTypeCode)}} {{if and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode)}}
<a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a> <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>
{{template "repo/branch_dropdown" dict "root" $ "release" .}} {{template "repo/branch_dropdown" dict "root" $ "release" $release}}
{{end}} {{end}}
</div> </div>
<div class="ui twelve wide column detail"> <div class="ui twelve wide column detail">
<div class="gt-df gt-ac gt-sb gt-fw gt-mb-3"> <div class="gt-df gt-ac gt-sb gt-fw gt-mb-3">
<h4 class="release-list-title gt-word-break"> <h4 class="release-list-title gt-word-break">
<a href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{.Title}}</a> <a href="{{$.RepoLink}}/releases/tag/{{$release.TagName | PathEscapeSegments}}">{{$release.Title}}</a>
{{if .IsDraft}} {{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "gt-df"}}
<span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span> {{if $release.IsDraft}}
{{else if .IsPrerelease}} <span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span>
<span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span> {{else if $release.IsPrerelease}}
{{else}} <span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span>
<span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span>
{{end}}
</h4>
<div>
{{if $.CanCreateRelease}}
<a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{.TagName | PathEscapeSegments}}" rel="nofollow">
{{svg "octicon-pencil"}}
</a>
{{end}}
</div>
</div>
<p class="text grey">
<span class="author">
{{if .OriginalAuthor}}
{{svg (MigrationIcon .Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{.OriginalAuthor}}
{{else if .Publisher}}
{{ctx.AvatarUtils.Avatar .Publisher 20 "gt-mr-2"}}
<a href="{{.Publisher.HomeLink}}">{{.Publisher.GetDisplayName}}</a>
{{else}} {{else}}
Ghost <span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span>
{{end}} {{end}}
</span> </h4>
<span class="released"> <div>
{{ctx.Locale.Tr "repo.released_this"}} {{if $.CanCreateRelease}}
</span> <a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{$release.TagName | PathEscapeSegments}}" rel="nofollow">
{{if .CreatedUnix}} {{svg "octicon-pencil"}}
<span class="time">{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span> </a>
{{end}} {{end}}
{{if and (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
| <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>
{{end}}
</p>
<div class="markup desc">
{{Str2html .Note}}
</div> </div>
<div class="divider"></div> </div>
<details class="download" {{if eq $idx 0}}open{{end}}> <p class="text grey">
<summary class="gt-my-4"> <span class="author">
{{ctx.Locale.Tr "repo.release.downloads"}} {{if $release.OriginalAuthor}}
</summary> {{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{$release.OriginalAuthor}}
<ul class="list"> {{else if $release.Publisher}}
{{if and (not $.DisableDownloadSourceArchives) (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} {{ctx.AvatarUtils.Avatar $release.Publisher 20 "gt-mr-2"}}
<li> <a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a>
<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> {{else}}
</li> Ghost
<li> {{end}}
<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> </span>
</li> <span class="released">
{{end}} {{ctx.Locale.Tr "repo.released_this"}}
{{if .Attachments}} </span>
{{range .Attachments}} {{if $release.CreatedUnix}}
<li> <span class="time">{{TimeSinceUnix $release.CreatedUnix ctx.Locale}}</span>
<a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download> {{end}}
<strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong> {{if and (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
</a> | <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>
<div> {{end}}
<span class="text grey">{{.Size | FileSize}}</span> </p>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}"> <div class="markup desc">
{{svg "octicon-info"}} {{Str2html $release.Note}}
</span> </div>
</div> <div class="divider"></div>
</li> <details class="download" {{if eq $idx 0}}open{{end}}>
{{end}} <summary class="gt-my-4">
{{end}} {{ctx.Locale.Tr "repo.release.downloads"}}
</ul> </summary>
</details> <ul class="list">
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
<li>
<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>
</li>
<li>
<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>
</li>
{{end}}
{{range $release.Attachments}}
<li>
<a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download>
<strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong>
</a>
<div>
<span class="text grey">{{.Size | FileSize}}</span>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}">
{{svg "octicon-info"}}
</span>
</div>
</li>
{{end}}
</ul>
</details>
<div class="dot"></div> <div class="dot"></div>
</div> </div>
</li> </li>

View file

@ -263,7 +263,7 @@
<label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label> <label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label>
<input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}> <input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}>
{{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}} {{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}}
<span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" "<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | Str2html}}</span> <span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | Safe)}}</span>
{{end}} {{end}}
</div> </div>

View file

@ -31,9 +31,8 @@
<li> <li>
{{svg "octicon-location"}} {{svg "octicon-location"}}
<span class="gt-f1">{{.ContextUser.Location}}</span> <span class="gt-f1">{{.ContextUser.Location}}</span>
{{if .UserLocationMapURL}} {{if .ContextUserLocationMapURL}}
{{/* We presume that the UserLocationMapURL is safe, as it is provided by the site administrator. */}} <a href="{{.ContextUserLocationMapURL}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}">
<a href="{{.UserLocationMapURL | Safe}}{{.ContextUser.Location | QueryEscape}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}">
{{svg "octicon-link-external"}} {{svg "octicon-link-external"}}
</a> </a>
{{end}} {{end}}

View file

@ -19,9 +19,9 @@
"name": "MIT", "name": "MIT",
"url": "http://opensource.org/licenses/MIT" "url": "http://opensource.org/licenses/MIT"
}, },
"version": "{{AppVer | JSEscape | Safe}}" "version": "{{AppVer | JSEscape}}"
}, },
"basePath": "{{AppSubUrl | JSEscape | Safe}}/api/v1", "basePath": "{{AppSubUrl | JSEscape}}/api/v1",
"paths": { "paths": {
"/activitypub/user-id/{user-id}": { "/activitypub/user-id/{user-id}": {
"get": { "get": {

View file

@ -1,16 +1,16 @@
{ {
"issuer": "{{AppUrl | JSEscape | Safe}}", "issuer": "{{AppUrl | JSEscape}}",
"authorization_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/authorize", "authorization_endpoint": "{{AppUrl | JSEscape}}login/oauth/authorize",
"token_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/access_token", "token_endpoint": "{{AppUrl | JSEscape}}login/oauth/access_token",
"jwks_uri": "{{AppUrl | JSEscape | Safe}}login/oauth/keys", "jwks_uri": "{{AppUrl | JSEscape}}login/oauth/keys",
"userinfo_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/userinfo", "userinfo_endpoint": "{{AppUrl | JSEscape}}login/oauth/userinfo",
"introspection_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/introspect", "introspection_endpoint": "{{AppUrl | JSEscape}}login/oauth/introspect",
"response_types_supported": [ "response_types_supported": [
"code", "code",
"id_token" "id_token"
], ],
"id_token_signing_alg_values_supported": [ "id_token_signing_alg_values_supported": [
"{{.SigningKey.SigningMethod.Alg | JSEscape | Safe}}" "{{.SigningKey.SigningMethod.Alg | JSEscape}}"
], ],
"subject_types_supported": [ "subject_types_supported": [
"public" "public"

View file

@ -47,7 +47,7 @@
{{ctx.Locale.Tr "org.members.leave"}} {{ctx.Locale.Tr "org.members.leave"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p> <p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|Safe)}}</p>
</div> </div>
{{template "base/modal_actions_confirm" .}} {{template "base/modal_actions_confirm" .}}
</div> </div>

View file

@ -21,6 +21,7 @@
--border-radius-circle: 50%; --border-radius-circle: 50%;
--opacity-disabled: 0.55; --opacity-disabled: 0.55;
--height-loading: 16rem; --height-loading: 16rem;
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
--tab-size: 4; --tab-size: 4;
} }
@ -492,6 +493,11 @@ ol.ui.list li,
background: var(--color-active) !important; background: var(--color-active) !important;
} }
.ui.form textarea:not([rows]) {
height: var(--min-height-textarea); /* override fomantic default 12em */
min-height: var(--min-height-textarea); /* override fomantic default 8em */
}
/* styles from removed fomantic transition module */ /* styles from removed fomantic transition module */
.hidden.transition { .hidden.transition {
visibility: hidden; visibility: hidden;

View file

@ -37,13 +37,12 @@
.combo-markdown-editor textarea.markdown-text-editor { .combo-markdown-editor textarea.markdown-text-editor {
display: block; display: block;
width: 100%; width: 100%;
min-height: 200px; max-height: calc(100vh - var(--min-height-textarea));
max-height: calc(100vh - 200px);
resize: vertical; resize: vertical;
} }
.combo-markdown-editor .CodeMirror-scroll { .combo-markdown-editor .CodeMirror-scroll {
max-height: calc(100vh - 200px); max-height: calc(100vh - var(--min-height-textarea));
} }
/* use the same styles as markup/content.css */ /* use the same styles as markup/content.css */

View file

@ -1498,12 +1498,6 @@
background: var(--color-body); background: var(--color-body);
} }
@media (max-width: 991.98px) {
.repository .diff-detail-box {
flex-direction: row;
}
}
@media (max-width: 480px) { @media (max-width: 480px) {
.repository .diff-detail-box { .repository .diff-detail-box {
flex-wrap: wrap; flex-wrap: wrap;
@ -1528,7 +1522,7 @@
color: var(--color-red); color: var(--color-red);
} }
@media (max-width: 991.98px) { @media (max-width: 800px) {
.repository .diff-detail-box .diff-detail-stats { .repository .diff-detail-box .diff-detail-stats {
display: none !important; display: none !important;
} }
@ -1538,7 +1532,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.25em; gap: 0.25em;
flex-wrap: wrap;
justify-content: end; justify-content: end;
} }
@ -1548,15 +1541,6 @@
margin-right: 0 !important; margin-right: 0 !important;
} }
@media (max-width: 480px) {
.repository .diff-detail-box .diff-detail-actions {
padding-top: 0.25rem;
}
.repository .diff-detail-box .diff-detail-actions .ui.button:not(.btn-submit) {
padding: 0 0.5rem;
}
}
.repository .diff-detail-box span.status { .repository .diff-detail-box span.status {
display: inline-block; display: inline-block;
width: 12px; width: 12px;

View file

@ -1,5 +1,7 @@
import $ from 'jquery'; import $ from 'jquery';
import {htmlEscape} from 'escape-goat';
import {POST} from '../../modules/fetch.js'; import {POST} from '../../modules/fetch.js';
import {imageInfo} from '../../utils/image.js';
async function uploadFile(file, uploadUrl) { async function uploadFile(file, uploadUrl) {
const formData = new FormData(); const formData = new FormData();
@ -109,10 +111,22 @@ const uploadClipboardImage = async (editor, dropzone, e) => {
const placeholder = `![${name}](uploading ...)`; const placeholder = `![${name}](uploading ...)`;
editor.insertPlaceholder(placeholder); editor.insertPlaceholder(placeholder);
const data = await uploadFile(img, uploadUrl);
editor.replacePlaceholder(placeholder, `![${name}](/attachments/${data.uuid})`);
const $input = $(`<input name="files" type="hidden">`).attr('id', data.uuid).val(data.uuid); const {uuid} = await uploadFile(img, uploadUrl);
const {width, dppx} = await imageInfo(img);
const url = `/attachments/${uuid}`;
let text;
if (width > 0 && dppx > 1) {
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
// method to change image size in Markdown that is supported by all implementations.
text = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(name)}" src="${htmlEscape(url)}">`;
} else {
text = `![${name}](${url})`;
}
editor.replacePlaceholder(placeholder, text);
const $input = $(`<input name="files" type="hidden">`).attr('id', uuid).val(uuid);
$files.append($input); $files.append($input);
} }
}; };

View file

@ -1,18 +1,17 @@
import $ from 'jquery';
import {hideElem, showElem} from '../utils/dom.js'; import {hideElem, showElem} from '../utils/dom.js';
import {GET, POST} from '../modules/fetch.js'; import {GET, POST} from '../modules/fetch.js';
const {appSubUrl} = window.config; const {appSubUrl} = window.config;
export function initRepoMigrationStatusChecker() { export function initRepoMigrationStatusChecker() {
const $repoMigrating = $('#repo_migrating'); const repoMigrating = document.getElementById('repo_migrating');
if (!$repoMigrating.length) return; if (!repoMigrating) return;
$('#repo_migrating_retry').on('click', doMigrationRetry); document.getElementById('repo_migrating_retry').addEventListener('click', doMigrationRetry);
const task = $repoMigrating.attr('data-migrating-task-id'); const task = repoMigrating.getAttribute('data-migrating-task-id');
// returns true if the refresh still need to be called after a while // returns true if the refresh still needs to be called after a while
const refresh = async () => { const refresh = async () => {
const res = await GET(`${appSubUrl}/user/task/${task}`); const res = await GET(`${appSubUrl}/user/task/${task}`);
if (res.status !== 200) return true; // continue to refresh if network error occurs if (res.status !== 200) return true; // continue to refresh if network error occurs
@ -21,7 +20,7 @@ export function initRepoMigrationStatusChecker() {
// for all status // for all status
if (data.message) { if (data.message) {
$('#repo_migrating_progress_message').text(data.message); document.getElementById('repo_migrating_progress_message').textContent = data.message;
} }
// TaskStatusFinished // TaskStatusFinished
@ -37,7 +36,7 @@ export function initRepoMigrationStatusChecker() {
showElem('#repo_migrating_retry'); showElem('#repo_migrating_retry');
showElem('#repo_migrating_failed'); showElem('#repo_migrating_failed');
showElem('#repo_migrating_failed_image'); showElem('#repo_migrating_failed_image');
$('#repo_migrating_failed_error').text(data.message); document.getElementById('repo_migrating_failed_error').textContent = data.message;
return false; return false;
} }
@ -59,6 +58,6 @@ export function initRepoMigrationStatusChecker() {
} }
async function doMigrationRetry(e) { async function doMigrationRetry(e) {
await POST($(e.target).attr('data-migrating-task-retry-url')); await POST(e.target.getAttribute('data-migrating-task-retry-url'));
window.location.reload(); window.location.reload();
} }

View file

@ -1,4 +1,3 @@
import $ from 'jquery';
import {checkAppUrl} from './common-global.js'; import {checkAppUrl} from './common-global.js';
export function initUserAuthOauth2() { export function initUserAuthOauth2() {
@ -21,30 +20,3 @@ export function initUserAuthOauth2() {
}); });
} }
} }
export function initUserAuthLinkAccountView() {
const $lnkUserPage = $('.page-content.user.link-account');
if ($lnkUserPage.length === 0) {
return false;
}
const $signinTab = $lnkUserPage.find('.item[data-tab="auth-link-signin-tab"]');
const $signUpTab = $lnkUserPage.find('.item[data-tab="auth-link-signup-tab"]');
const $signInView = $lnkUserPage.find('.tab[data-tab="auth-link-signin-tab"]');
const $signUpView = $lnkUserPage.find('.tab[data-tab="auth-link-signup-tab"]');
$signUpTab.on('click', () => {
$signinTab.removeClass('active');
$signInView.removeClass('active');
$signUpTab.addClass('active');
$signUpView.addClass('active');
return false;
});
$signinTab.on('click', () => {
$signUpTab.removeClass('active');
$signUpView.removeClass('active');
$signinTab.addClass('active');
$signInView.addClass('active');
});
}

View file

@ -23,7 +23,7 @@ import {initFindFileInRepo} from './features/repo-findfile.js';
import {initCommentContent, initMarkupContent} from './markup/content.js'; import {initCommentContent, initMarkupContent} from './markup/content.js';
import {initPdfViewer} from './render/pdf.js'; import {initPdfViewer} from './render/pdf.js';
import {initUserAuthLinkAccountView, initUserAuthOauth2} from './features/user-auth.js'; import {initUserAuthOauth2} from './features/user-auth.js';
import { import {
initRepoIssueDue, initRepoIssueDue,
initRepoIssueReferenceRepositorySearch, initRepoIssueReferenceRepositorySearch,
@ -178,7 +178,6 @@ onDomReady(() => {
initCommitStatuses(); initCommitStatuses();
initCaptcha(); initCaptcha();
initUserAuthLinkAccountView();
initUserAuthOauth2(); initUserAuthOauth2();
initUserAuthWebAuthn(); initUserAuthWebAuthn();
initUserAuthWebAuthnRegister(); initUserAuthWebAuthnRegister();

47
web_src/js/utils/image.js Normal file
View file

@ -0,0 +1,47 @@
export async function pngChunks(blob) {
const uint8arr = new Uint8Array(await blob.arrayBuffer());
const chunks = [];
if (uint8arr.length < 12) return chunks;
const view = new DataView(uint8arr.buffer);
if (view.getBigUint64(0) !== 9894494448401390090n) return chunks;
const decoder = new TextDecoder();
let index = 8;
while (index < uint8arr.length) {
const len = view.getUint32(index);
chunks.push({
name: decoder.decode(uint8arr.slice(index + 4, index + 8)),
data: uint8arr.slice(index + 8, index + 8 + len),
});
index += len + 12;
}
return chunks;
}
// decode a image and try to obtain width and dppx. If will never throw but instead
// return default values.
export async function imageInfo(blob) {
let width = 0; // 0 means no width could be determined
let dppx = 1; // 1 dot per pixel for non-HiDPI screens
if (blob.type === 'image/png') { // only png is supported currently
try {
for (const {name, data} of await pngChunks(blob)) {
const view = new DataView(data.buffer);
if (name === 'IHDR' && data?.length) {
// extract width from mandatory IHDR chunk
width = view.getUint32(0);
} else if (name === 'pHYs' && data?.length) {
// extract dppx from optional pHYs chunk, assuming pixels are square
const unit = view.getUint8(8);
if (unit === 1) {
dppx = Math.round(view.getUint32(0) / 39.3701) / 72; // meter to inch to dppx
}
}
}
} catch {}
}
return {width, dppx};
}

View file

@ -0,0 +1,29 @@
import {pngChunks, imageInfo} from './image.js';
const pngNoPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAADUlEQVQIHQECAP3/AAAAAgABzePRKwAAAABJRU5ErkJggg==';
const pngPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAEElEQVQI12OQNZcAIgYIBQAL8gGxdzzM0A==';
const pngEmpty = 'data:image/png;base64,';
async function dataUriToBlob(datauri) {
return await (await globalThis.fetch(datauri)).blob();
}
test('pngChunks', async () => {
expect(await pngChunks(await dataUriToBlob(pngNoPhys))).toEqual([
{name: 'IHDR', data: new Uint8Array([0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 0, 0, 0])},
{name: 'IDAT', data: new Uint8Array([8, 29, 1, 2, 0, 253, 255, 0, 0, 0, 2, 0, 1])},
{name: 'IEND', data: new Uint8Array([])},
]);
expect(await pngChunks(await dataUriToBlob(pngPhys))).toEqual([
{name: 'IHDR', data: new Uint8Array([0, 0, 0, 2, 0, 0, 0, 2, 8, 2, 0, 0, 0])},
{name: 'pHYs', data: new Uint8Array([0, 0, 22, 37, 0, 0, 22, 37, 1])},
{name: 'IDAT', data: new Uint8Array([8, 215, 99, 144, 53, 151, 0, 34, 6, 8, 5, 0, 11, 242, 1, 177])},
]);
expect(await pngChunks(await dataUriToBlob(pngEmpty))).toEqual([]);
});
test('imageInfo', async () => {
expect(await imageInfo(await dataUriToBlob(pngNoPhys))).toEqual({width: 1, dppx: 1});
expect(await imageInfo(await dataUriToBlob(pngPhys))).toEqual({width: 2, dppx: 2});
expect(await imageInfo(await dataUriToBlob(pngEmpty))).toEqual({width: 0, dppx: 1});
});