0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-07-17 21:52:53 +02:00

Merge branch 'main' into add-owner-parent-docs

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
wxiaoguang 2025-07-11 11:08:03 +08:00 committed by GitHub
commit 714fcc9fb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
111 changed files with 1114 additions and 993 deletions

View File

@ -326,6 +326,7 @@ module.exports = {
'@typescript-eslint/no-unnecessary-type-arguments': [0],
'@typescript-eslint/no-unnecessary-type-assertion': [2],
'@typescript-eslint/no-unnecessary-type-constraint': [2],
'@typescript-eslint/no-unnecessary-type-conversion': [2],
'@typescript-eslint/no-unsafe-argument': [0],
'@typescript-eslint/no-unsafe-assignment': [0],
'@typescript-eslint/no-unsafe-call': [0],
@ -645,7 +646,7 @@ module.exports = {
'no-multi-str': [2],
'no-negated-condition': [0],
'no-nested-ternary': [0],
'no-new-func': [2],
'no-new-func': [0], // handled by @typescript-eslint/no-implied-eval
'no-new-native-nonconstructor': [2],
'no-new-object': [2],
'no-new-symbol': [2],

View File

@ -591,7 +591,7 @@ be reviewed by two maintainers and must pass the automatic tests.
## Releasing Gitea
- Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours.
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody is against it in about several hours.
- If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
- Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
- When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`

View File

@ -39,7 +39,6 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io"
@ -83,4 +82,3 @@ CMD ["/usr/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh

View File

@ -37,7 +37,6 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/tmp/local/usr/local/bin/gitea \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io"
@ -72,7 +71,6 @@ RUN chown git:git /var/lib/gitea /etc/gitea
COPY --from=build-env /tmp/local /
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
# git:git
USER 1000:1000

View File

@ -87,6 +87,14 @@ func oauthCLIFlags() []cli.Flag {
Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source",
},
&cli.StringFlag{
Name: "ssh-public-key-claim-name",
Usage: "Claim name that provides SSH public keys",
},
&cli.StringFlag{
Name: "full-name-claim-name",
Usage: "Claim name that provides user's full name",
},
&cli.StringFlag{
Name: "required-claim-name",
Value: "",
@ -177,6 +185,8 @@ func parseOAuth2Config(c *cli.Command) *oauth2.Source {
RestrictedGroup: c.String("restricted-group"),
GroupTeamMap: c.String("group-team-map"),
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
SSHPublicKeyClaimName: c.String("ssh-public-key-claim-name"),
FullNameClaimName: c.String("full-name-claim-name"),
}
}
@ -268,6 +278,12 @@ func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error
if c.IsSet("group-team-map-removal") {
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
}
if c.IsSet("ssh-public-key-claim-name") {
oAuth2Config.SSHPublicKeyClaimName = c.String("ssh-public-key-claim-name")
}
if c.IsSet("full-name-claim-name") {
oAuth2Config.FullNameClaimName = c.String("full-name-claim-name")
}
// update custom URL mapping
customURLMapping := &oauth2.CustomURLMapping{}

View File

@ -88,6 +88,8 @@ func TestAddOauth(t *testing.T) {
"--restricted-group", "restricted",
"--group-team-map", `{"group1": [1,2]}`,
"--group-team-map-removal=true",
"--ssh-public-key-claim-name", "attr_ssh_pub_key",
"--full-name-claim-name", "attr_full_name",
},
source: &auth_model.Source{
Type: auth_model.OAuth2,
@ -104,15 +106,17 @@ func TestAddOauth(t *testing.T) {
EmailURL: "https://example.com/email",
Tenant: "some_tenant",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: true,
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: true,
SSHPublicKeyClaimName: "attr_ssh_pub_key",
FullNameClaimName: "attr_full_name",
},
TwoFactorPolicy: "skip",
},
@ -223,15 +227,17 @@ func TestUpdateOauth(t *testing.T) {
EmailURL: "https://old.example.com/email",
Tenant: "old_tenant",
},
IconURL: "https://old.example.com/icon",
Scopes: []string{"old_scope1", "old_scope2"},
RequiredClaimName: "old_claim_name",
RequiredClaimValue: "old_claim_value",
GroupClaimName: "old_group_name",
AdminGroup: "old_admin",
RestrictedGroup: "old_restricted",
GroupTeamMap: `{"old_group1": [1,2]}`,
GroupTeamMapRemoval: true,
IconURL: "https://old.example.com/icon",
Scopes: []string{"old_scope1", "old_scope2"},
RequiredClaimName: "old_claim_name",
RequiredClaimValue: "old_claim_value",
GroupClaimName: "old_group_name",
AdminGroup: "old_admin",
RestrictedGroup: "old_restricted",
GroupTeamMap: `{"old_group1": [1,2]}`,
GroupTeamMapRemoval: true,
SSHPublicKeyClaimName: "old_ssh_pub_key",
FullNameClaimName: "old_full_name",
},
TwoFactorPolicy: "",
},
@ -257,6 +263,8 @@ func TestUpdateOauth(t *testing.T) {
"--restricted-group", "restricted",
"--group-team-map", `{"group1": [1,2]}`,
"--group-team-map-removal=false",
"--ssh-public-key-claim-name", "new_ssh_pub_key",
"--full-name-claim-name", "new_full_name",
},
authSource: &auth_model.Source{
ID: 1,
@ -274,15 +282,17 @@ func TestUpdateOauth(t *testing.T) {
EmailURL: "https://example.com/email",
Tenant: "new_tenant",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: false,
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: false,
SSHPublicKeyClaimName: "new_ssh_pub_key",
FullNameClaimName: "new_full_name",
},
TwoFactorPolicy: "skip",
},

View File

@ -32,6 +32,7 @@ var (
CmdHook = &cli.Command{
Name: "hook",
Usage: "(internal) Should only be called by Git",
Hidden: true, // internal commands shouldn't be visible
Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Commands: []*cli.Command{

View File

@ -19,6 +19,7 @@ import (
var CmdKeys = &cli.Command{
Name: "keys",
Usage: "(internal) Should only be called by SSH server",
Hidden: true, // internal commands shouldn't not be visible
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runKeys,

View File

@ -6,6 +6,7 @@ package cmd
import (
"context"
"fmt"
"io"
"os"
"strings"
@ -15,26 +16,28 @@ import (
"github.com/urfave/cli/v3"
)
// cmdHelp is our own help subcommand with more information
// Keep in mind that the "./gitea help"(subcommand) is different from "./gitea --help"(flag), the flag doesn't parse the config or output "DEFAULT CONFIGURATION:" information
func cmdHelp() *cli.Command {
c := &cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(ctx context.Context, c *cli.Command) (err error) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea
targetCmdIdx := 0
if c.Name == "help" {
targetCmdIdx = 1
}
if lineage[targetCmdIdx] != lineage[targetCmdIdx].Root() {
err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1] /* parent cmd */, lineage[targetCmdIdx].Name /* sub cmd */)
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.Root().Writer, `
var cliHelpPrinterOld = cli.HelpPrinter
func init() {
cli.HelpPrinter = cliHelpPrinterNew
}
// cliHelpPrinterNew helps to print "DEFAULT CONFIGURATION" for the following cases ( "-c" can apper in any position):
// * ./gitea -c /dev/null -h
// * ./gitea -c help /dev/null help
// * ./gitea help -c /dev/null
// * ./gitea help -c /dev/null web
// * ./gitea help web -c /dev/null
// * ./gitea web help -c /dev/null
// * ./gitea web -h -c /dev/null
func cliHelpPrinterNew(out io.Writer, templ string, data any) {
cmd, _ := data.(*cli.Command)
if cmd != nil {
prepareWorkPathAndCustomConf(cmd)
}
cliHelpPrinterOld(out, templ, data)
if setting.CustomConf != "" {
_, _ = fmt.Fprintf(out, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
@ -42,77 +45,36 @@ DEFAULT CONFIGURATION:
ConfigFile: %s
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
return err
},
}
return c
}
func appGlobalFlags() []cli.Flag {
return []cli.Flag{
// make the builtin flags at the top
cli.HelpFlag,
// shared configuration flags, they are for global and for each sub-command at the same time
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
&cli.StringFlag{
Name: "custom-path",
Aliases: []string{"C"},
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
&cli.StringFlag{
Name: "work-path",
Aliases: []string{"w"},
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
}
}
func prepareSubcommandWithGlobalFlags(command *cli.Command) {
command.Flags = append(append([]cli.Flag{}, appGlobalFlags()...), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
command.Commands = append(command.Commands, cmdHelp())
}
for i := range command.Commands {
prepareSubcommandWithGlobalFlags(command.Commands[i])
}
}
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(context.Context, *cli.Command) error {
return func(ctx context.Context, cmd *cli.Command) error {
var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags
for _, curCtx := range cmd.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
args.CustomPath = curCtx.String("custom-path")
}
if curCtx.IsSet("config") && args.CustomConf == "" {
args.CustomConf = curCtx.String("config")
}
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
originBefore := originCmd.Before
originCmd.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
prepareWorkPathAndCustomConf(cmd)
if originBefore != nil {
return originBefore(ctx, cmd)
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if cmd.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx, cmd)
}
return action(ctx, cmd)
return ctx, nil
}
}
// prepareWorkPathAndCustomConf tries to prepare the work path, custom path and custom config from various inputs:
// command line flags, environment variables, config file
func prepareWorkPathAndCustomConf(cmd *cli.Command) {
var args setting.ArgWorkPathAndCustomConf
if cmd.IsSet("work-path") {
args.WorkPath = cmd.String("work-path")
}
if cmd.IsSet("custom-path") {
args.CustomPath = cmd.String("custom-path")
}
if cmd.IsSet("config") {
args.CustomConf = cmd.String("config")
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
}
type AppVersion struct {
Version string
Extra string
@ -125,10 +87,29 @@ func NewMainApp(appVer AppVersion) *cli.Command {
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
app.Version = appVer.Version + appVer.Extra
app.EnableShellCompletion = true
// these sub-commands need to use config file
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "work-path",
Aliases: []string{"w"},
TakesFile: true,
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
TakesFile: true,
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
&cli.StringFlag{
Name: "custom-path",
Aliases: []string{"C"},
TakesFile: true,
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
}
// these sub-commands need to use a config file
subCmdWithConfig := []*cli.Command{
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
CmdWeb,
CmdServ,
CmdHook,
@ -156,9 +137,6 @@ func NewMainApp(appVer AppVersion) *cli.Command {
// but not sure whether it would break Windows users who used to double-click the EXE to run.
app.DefaultCommand = CmdWeb.Name
app.Flags = append(app.Flags, cli.VersionFlag)
app.Flags = append(app.Flags, appGlobalFlags()...)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig {
prepareSubcommandWithGlobalFlags(subCmdWithConfig[i])

View File

@ -74,12 +74,56 @@ func TestCliCmd(t *testing.T) {
cmd string
exp string
}{
// main command help
// help commands
{
cmd: "./gitea -h",
exp: "DEFAULT CONFIGURATION:",
},
{
cmd: "./gitea help",
exp: "DEFAULT CONFIGURATION:",
},
{
cmd: "./gitea -c /dev/null -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea help -c /dev/null",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null test-cmd -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -c /dev/null -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -h -c /dev/null",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null test-cmd help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -c /dev/null help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd help -c /dev/null",
exp: "ConfigFile: /dev/null",
},
// parse paths
{
cmd: "./gitea test-cmd",

View File

@ -41,6 +41,7 @@ var CmdServ = &cli.Command{
Name: "serv",
Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
Hidden: true, // Internal commands shouldn't be visible in help
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ,
Flags: []cli.Flag{

View File

@ -1,17 +0,0 @@
Bash and Zsh completion
=======================
From within the gitea root run:
```bash
source contrib/autocompletion/bash_autocomplete
```
or for zsh run:
```bash
source contrib/autocompletion/zsh_autocomplete
```
These scripts will check if gitea is on the path and if so add autocompletion for `gitea`. Or if not autocompletion will work for `./gitea`.
If gitea has been installed as a different program pass in the `PROG` environment variable to set the correct program name.

View File

@ -1,30 +0,0 @@
#! /bin/bash
# Heavily inspired by https://github.com/urfave/cli
_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
if [ -z "$PROG" ] && [ ! "$(command -v gitea &> /dev/null)" ] ; then
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete gitea
elif [ -z "$PROG" ]; then
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete ./gitea
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PWD/gitea"
else
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PROG"
unset PROG
fi

View File

@ -1,30 +0,0 @@
#compdef ${PROG:=gitea}
# Heavily inspired by https://github.com/urfave/cli
_cli_zsh_autocomplete() {
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
else
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
return
}
if [ -z $PROG ] ; then
compdef _cli_zsh_autocomplete gitea
else
compdef _cli_zsh_autocomplete $(basename $PROG)
fi

2
go.mod
View File

@ -1,6 +1,6 @@
module code.gitea.io/gitea
go 1.24.4
go 1.24.5
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related:

View File

@ -15,25 +15,6 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
// __________________ ________ ____ __.
// / _____/\______ \/ _____/ | |/ _|____ ___.__.
// / \ ___ | ___/ \ ___ | <_/ __ < | |
// \ \_\ \| | \ \_\ \ | | \ ___/\___ |
// \______ /|____| \______ / |____|__ \___ > ____|
// \/ \/ \/ \/\/
// _________ .__ __
// \_ ___ \ ____ _____ _____ |__|/ |_
// / \ \/ / _ \ / \ / \| \ __\
// \ \___( <_> ) Y Y \ Y Y \ || |
// \______ /\____/|__|_| /__|_| /__||__|
// \/ \/ \/
// ____ ____ .__ _____.__ __ .__
// \ \ / /___________|__|/ ____\__| ____ _____ _/ |_|__| ____ ____
// \ Y // __ \_ __ \ \ __\| |/ ___\\__ \\ __\ |/ _ \ / \
// \ /\ ___/| | \/ || | | \ \___ / __ \| | | ( <_> ) | \
// \___/ \___ >__| |__||__| |__|\___ >____ /__| |__|\____/|___| /
// \/ \/ \/ \/
// This file provides functions relating commit verification
// CommitVerification represents a commit validation of signature
@ -41,8 +22,8 @@ type CommitVerification struct {
Verified bool
Warning bool
Reason string
SigningUser *user_model.User
CommittingUser *user_model.User
SigningUser *user_model.User // if Verified, then SigningUser is non-nil
CommittingUser *user_model.User // if Verified, then CommittingUser is non-nil
SigningEmail string
SigningKey *GPGKey
SigningSSHKey *PublicKey

View File

@ -355,13 +355,13 @@ func AddPublicKeysBySource(ctx context.Context, usr *user_model.User, s *auth.So
return sshKeysNeedUpdate
}
// SynchronizePublicKeys updates a users public keys. Returns true if there are changes.
// SynchronizePublicKeys updates a user's public keys. Returns true if there are changes.
func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
var sshKeysNeedUpdate bool
log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name)
// Get Public Keys from DB with current LDAP source
// Get Public Keys from DB with the current auth source
var giteaKeys []string
keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{
OwnerID: usr.ID,

View File

@ -612,8 +612,8 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error {
return util.ErrNotExist
}
// GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name
func GetActiveOAuth2SourceByName(ctx context.Context, name string) (*Source, error) {
// GetActiveOAuth2SourceByAuthName returns a OAuth2 AuthSource based on the given name
func GetActiveOAuth2SourceByAuthName(ctx context.Context, name string) (*Source, error) {
authSource := new(Source)
has, err := db.GetEngine(ctx).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource)
if err != nil {

View File

@ -334,7 +334,7 @@ func UpdateSource(ctx context.Context, source *Source) error {
err = registerableSource.RegisterSource()
if err != nil {
// restore original values since we cannot update the provider it self
// restore original values since we cannot update the provider itself
if _, err := db.GetEngine(ctx).ID(source.ID).AllCols().Update(originalSource); err != nil {
log.Error("UpdateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
}

View File

@ -472,7 +472,7 @@ type RecentlyPushedNewBranch struct {
// if opts.CommitAfterUnix is 0, we will find the branches that were committed to in the last 2 hours
// if opts.ListOptions is not set, we will only display top 2 latest branches.
// Protected branches will be skipped since they are unlikely to be used to create new PRs.
func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, opts *FindRecentlyPushedNewBranchesOptions) ([]*RecentlyPushedNewBranch, error) {
func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, opts FindRecentlyPushedNewBranchesOptions) ([]*RecentlyPushedNewBranch, error) {
if doer == nil {
return []*RecentlyPushedNewBranch{}, nil
}

View File

@ -652,7 +652,13 @@ func (repo *Repository) AllowsPulls(ctx context.Context) bool {
}
// CanEnableEditor returns true if repository meets the requirements of web editor.
// FIXME: most CanEnableEditor calls should be replaced with CanContentChange
// And all other like CanCreateBranch / CanEnablePulls should also be updated
func (repo *Repository) CanEnableEditor() bool {
return repo.CanContentChange()
}
func (repo *Repository) CanContentChange() bool {
return !repo.IsMirror && !repo.IsArchived
}

View File

@ -1166,12 +1166,6 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([
for _, c := range oldCommits {
user := emailUserMap.GetByEmail(c.Author.Email) // FIXME: why ValidateCommitsWithEmails uses "Author", but ParseCommitsWithSignature uses "Committer"?
if user == nil {
user = &User{
Name: c.Author.Name,
Email: c.Author.Email,
}
}
newCommits = append(newCommits, &UserCommit{
User: user,
Commit: c,
@ -1195,12 +1189,14 @@ func GetUsersByEmails(ctx context.Context, emails []string) (*EmailUserMap, erro
needCheckEmails := make(container.Set[string])
needCheckUserNames := make(container.Set[string])
noReplyAddressSuffix := "@" + strings.ToLower(setting.Service.NoReplyAddress)
for _, email := range emails {
if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) {
username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress)
needCheckUserNames.Add(strings.ToLower(username))
emailLower := strings.ToLower(email)
if noReplyUserNameLower, ok := strings.CutSuffix(emailLower, noReplyAddressSuffix); ok {
needCheckUserNames.Add(noReplyUserNameLower)
needCheckEmails.Add(emailLower)
} else {
needCheckEmails.Add(strings.ToLower(email))
needCheckEmails.Add(emailLower)
}
}

View File

@ -85,6 +85,11 @@ func TestUserEmails(t *testing.T) {
testGetUserByEmail(t, c.Email, c.UID)
})
}
t.Run("NoReplyConflict", func(t *testing.T) {
setting.Service.NoReplyAddress = "example.com"
testGetUserByEmail(t, "user1-2@example.COM", 1)
})
})
}

View File

@ -22,9 +22,9 @@ import (
type Commit struct {
Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache"
ID ObjectID // The ID of this commit object
Author *Signature
Committer *Signature
ID ObjectID
Author *Signature // never nil
Committer *Signature // never nil
CommitMessage string
Signature *CommitSignature

View File

@ -6,17 +6,18 @@ package git
import (
"context"
"strings"
giturl "code.gitea.io/gitea/modules/git/url"
)
// CommitSubmoduleFile represents a file with submodule type.
type CommitSubmoduleFile struct {
refURL string
parsedURL *giturl.RepositoryURL
parsed bool
refID string
repoLink string
refURL string
refID string
parsed bool
targetRepoLink string
}
// NewCommitSubmoduleFile create a new submodule file
@ -35,20 +36,27 @@ func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID
}
if !sf.parsed {
sf.parsed = true
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
if err != nil {
return nil
if strings.HasPrefix(sf.refURL, "../") {
// FIXME: when handling relative path, this logic is not right. It needs to:
// 1. Remember the submodule's full path and its commit's repo home link
// 2. Resolve the relative path: targetRepoLink = path.Join(repoHomeLink, path.Dir(submoduleFullPath), refURL)
// Not an easy task and need to refactor related code a lot.
sf.targetRepoLink = sf.refURL
} else {
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
if err != nil {
return nil
}
sf.targetRepoLink = giturl.MakeRepositoryWebLink(parsedURL)
}
sf.parsedURL = parsedURL
sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL)
}
var commitLink string
if len(optCommitID) == 2 {
commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
commitLink = sf.targetRepoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
} else if len(optCommitID) == 1 {
commitLink = sf.repoLink + "/tree/" + optCommitID[0]
commitLink = sf.targetRepoLink + "/tree/" + optCommitID[0]
} else {
commitLink = sf.repoLink + "/tree/" + sf.refID
commitLink = sf.targetRepoLink + "/tree/" + sf.refID
}
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
return &SubmoduleWebLink{RepoWebLink: sf.targetRepoLink, CommitWebLink: commitLink}
}

View File

@ -10,20 +10,29 @@ import (
)
func TestCommitSubmoduleLink(t *testing.T) {
sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa")
wl := sf.SubmoduleWebLink(t.Context())
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
wl = sf.SubmoduleWebLink(t.Context(), "1111")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/tree/1111", wl.CommitWebLink)
wl = sf.SubmoduleWebLink(t.Context(), "1111", "2222")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(t.Context())
wl := (*CommitSubmoduleFile)(nil).SubmoduleWebLink(t.Context())
assert.Nil(t, wl)
t.Run("GitHubRepo", func(t *testing.T) {
sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa")
wl := sf.SubmoduleWebLink(t.Context())
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
wl = sf.SubmoduleWebLink(t.Context(), "1111")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/tree/1111", wl.CommitWebLink)
wl = sf.SubmoduleWebLink(t.Context(), "1111", "2222")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
})
t.Run("RelativePath", func(t *testing.T) {
sf := NewCommitSubmoduleFile("../../user/repo", "aaaa")
wl := sf.SubmoduleWebLink(t.Context())
assert.Equal(t, "../../user/repo", wl.RepoWebLink)
assert.Equal(t, "../../user/repo/tree/aaaa", wl.CommitWebLink)
})
}

View File

@ -28,6 +28,13 @@ func FromPtr[T any](v *T) Option[T] {
return Some(*v)
}
func FromMapLookup[K comparable, V any](m map[K]V, k K) Option[V] {
if v, ok := m[k]; ok {
return Some(v)
}
return None[V]()
}
func FromNonDefault[T comparable](v T) Option[T] {
var zero T
if v == zero {

View File

@ -56,6 +56,12 @@ func TestOption(t *testing.T) {
opt3 := optional.FromNonDefault(1)
assert.True(t, opt3.Has())
assert.Equal(t, int(1), opt3.Value())
opt4 := optional.FromMapLookup(map[string]int{"a": 1}, "a")
assert.True(t, opt4.Has())
assert.Equal(t, 1, opt4.Value())
opt4 = optional.FromMapLookup(map[string]int{"a": 1}, "b")
assert.False(t, opt4.Has())
}
func Test_ParseBool(t *testing.T) {

View File

@ -12,7 +12,7 @@ import (
"code.gitea.io/gitea/modules/log"
)
// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data
// OAuth2UsernameType is enum describing the way gitea generates its 'username' from oauth2 data
type OAuth2UsernameType string
const (

View File

@ -59,7 +59,7 @@ type Repository struct {
Fork bool `json:"fork"`
Template bool `json:"template"`
// the original repository if the repository in the parameter is a fork; else empty
Parent *Repository `json:"parent"`
Parent *Repository `json:"parent,omitempty"`
Mirror bool `json:"mirror"`
Size int `json:"size"`
Language string `json:"language"`
@ -116,7 +116,7 @@ type Repository struct {
ObjectFormatName string `json:"object_format_name"`
// swagger:strfmt date-time
MirrorUpdated time.Time `json:"mirror_updated"`
RepoTransfer *RepoTransfer `json:"repo_transfer"`
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
Topics []string `json:"topics"`
Licenses []string `json:"licenses"`
}

View File

@ -128,7 +128,6 @@ pin=Připnout
unpin=Odepnout
artifacts=Artefakty
confirm_delete_artifact=Jste si jisti, že chcete odstranit artefakt „%s“?
archived=Archivováno
@ -168,7 +167,6 @@ internal_error_skipped=Došlo k vnitřní chybě, ale je přeskočena: %s
search=Hledat...
type_tooltip=Druh vyhledávání
fuzzy=Fuzzy
fuzzy_tooltip=Zahrnout výsledky, které také úzce odpovídají hledanému výrazu
exact=Přesně
exact_tooltip=Zahrnout pouze výsledky, které přesně odpovídají hledanému výrazu
repo_kind=Hledat repozitáře...
@ -490,7 +488,6 @@ activate_email.text=Pro aktivaci vašeho účtu do <b>%s</b> klikněte na násle
register_notify=Vítejte v %s
register_notify.title=%[1]s vítejte v %[2]s
register_notify.text_1=toto je váš potvrzovací e-mail pro %s!
register_notify.text_2=Nyní se můžete přihlásit přes uživatelské jméno: %s.
register_notify.text_3=Pokud pro vás byl vytvořen tento účet, nejprve <a href="%s">nastavte své heslo</a>.
@ -977,7 +974,6 @@ webauthn_alternative_tip=Možná budete chtít nakonfigurovat další metodu ov
manage_account_links=Správa propojených účtů
manage_account_links_desc=Tyto externí účty jsou propojeny s vaším Gitea účtem.
account_links_not_available=K vašemu Gitea účtu nejsou aktuálně připojené žádné externí účty.
link_account=Propojit účet
remove_account_link=Odstranit propojený účet
remove_account_link_desc=Odstraněním propojeného účtu zrušíte jeho přístup k vašemu Gitea účtu. Pokračovat?
@ -1014,8 +1010,6 @@ new_repo_helper=Repozitář obsahuje všechny projektové soubory, včetně hist
owner=Vlastník
owner_helper=Některé organizace se nemusejí v seznamu zobrazit kvůli maximálnímu dosaženému počtu repozitářů.
repo_name=Název repozitáře
repo_name_profile_public_hint=.profile je speciální repozitář, který můžete použít k přidání souboru README.md do svého veřejného profilu organizace, který je viditelný pro každého. Ujistěte se, že je veřejný, a pro začátek jej inicializujte pomocí README v adresáři profilu.
repo_name_profile_private_hint=.profile-private je speciální repozitář, který můžete použít k přidání souboru README.md do profilu člena organizace, který je viditelný pouze pro členy organizace. Ujistěte se, že je soukromý, a pro začátek jej inicializujte pomocí README v adresáři profilu.
repo_name_helper=Dobrá jména repozitářů používají krátká, zapamatovatelná a unikátní klíčová slova. Repozitář s názvem „.profile“ nebo „.profile-private“ lze použít k přidání README.md pro uživatelský/organizační profil.
repo_size=Velikost repozitáře
template=Šablona
@ -1037,7 +1031,6 @@ fork_branch=Větev, která má být klonována pro fork
all_branches=Všechny větve
view_all_branches=Zobrazit všechny větve
view_all_tags=Zobrazit všechny značky
fork_no_valid_owners=Tento repozitář nemůže být rozštěpen, protože neexistují žádní platní vlastníci.
fork.blocked_user=Nelze rozštěpit repozitář, protože jste blokováni majitelem repozitáře.
use_template=Použít tuto šablonu
open_with_editor=Otevřít pomocí %s
@ -1705,7 +1698,6 @@ issues.due_date_form=rrrr-mm-dd
issues.due_date_form_add=Přidat termín dokončení
issues.due_date_form_edit=Upravit
issues.due_date_form_remove=Odstranit
issues.due_date_not_writer=Potřebujete přístup k zápisu do tohoto repozitáře, abyste mohli aktualizovat datum dokončení úkolu.
issues.due_date_not_set=Žádný termín dokončení.
issues.due_date_added=přidal/a termín dokončení %s %s
issues.due_date_modified=upravil/a termín termínu z %[2]s na %[1]s %[3]s
@ -2070,7 +2062,6 @@ activity.title.releases_1=%d Vydání
activity.title.releases_n=%d Vydání
activity.title.releases_published_by=%s publikoval %s
activity.published_release_label=Publikováno
activity.no_git_activity=V tomto období nebyla žádná aktivita při odevzdání.
activity.git_stats_exclude_merges=Při vyloučení slučování,
activity.git_stats_author_1=%d autor
activity.git_stats_author_n=%d autoři

View File

@ -117,7 +117,6 @@ files=Dateien
error=Fehler
error404=Die Seite, die Du versuchst aufzurufen, <strong>existiert nicht</strong> oder <strong>Du bist nicht berechtigt</strong>, diese anzusehen.
error503=Der Server konnte deine Anfrage nicht abschließen. Bitte versuche es später erneut.
go_back=Zurück
invalid_data=Ungültige Daten: %v
@ -131,7 +130,6 @@ unpin=Loslösen
artifacts=Artefakte
expired=Abgelaufen
confirm_delete_artifact=Bist du sicher, dass du das Artefakt '%s' löschen möchtest?
archived=Archiviert
@ -171,7 +169,6 @@ internal_error_skipped=Ein interner Fehler ist aufgetreten, wurde aber überspru
search=Suche ...
type_tooltip=Suchmodus
fuzzy=Ähnlich
fuzzy_tooltip=Ergebnisse einbeziehen, die dem Suchbegriff ähnlich sind
words=Wörter
words_tooltip=Nur Suchbegriffe einbeziehen, die den Suchbegriffen exakt entsprechen
regexp=Regexp
@ -505,7 +502,6 @@ activate_email.text=Bitte klicke innerhalb von <b>%s</b> auf folgenden Link, um
register_notify=Willkommen bei %s
register_notify.title=%[1]s, willkommen bei %[2]s
register_notify.text_1=dies ist deine Bestätigungs-E-Mail für %s!
register_notify.text_2=Du kannst dich jetzt mit dem Benutzernamen "%s" anmelden.
register_notify.text_3=Wenn dieser Account von dir erstellt wurde, musst du zuerst <a href="%s">dein Passwort setzen</a>.
@ -997,7 +993,6 @@ webauthn_alternative_tip=Möglicherweise möchtest du eine zusätzliche Authenti
manage_account_links=Verknüpfte Accounts verwalten
manage_account_links_desc=Diese externen Accounts sind mit deinem Gitea-Account verknüpft.
account_links_not_available=Es sind keine externen Accounts mit diesem Gitea-Account verknüpft.
link_account=Account verbinden
remove_account_link=Verknüpften Account entfernen
remove_account_link_desc=Wenn du den verknüpften Account entfernst, wirst du darüber nicht mehr auf deinen Gitea-Account zugreifen können. Fortfahren?
@ -1034,8 +1029,6 @@ new_repo_helper=Ein Repository enthält alle Projektdateien, einschließlich des
owner=Besitzer
owner_helper=Einige Organisationen könnten in der Dropdown-Liste nicht angezeigt werden, da die Anzahl an Repositories begrenzt ist.
repo_name=Repository-Name
repo_name_profile_public_hint=.profile ist ein spezielles Repository, mit dem du die README.md zu deinem öffentlichen Organisationsprofil hinzufügen kannst, das für jeden sichtbar ist. Stelle sicher, dass es öffentlich ist und initialisiere es mit einer README im Profilverzeichnis, um loszulegen.
repo_name_profile_private_hint=.profile ist ein spezielles Repository, mit dem du die README.md zu deinem privaten Organisationsprofil hinzufügen kannst, das nur für Organisationsmitglieder sichtbar ist. Stelle sicher, dass es privat ist und initialisiere es mit einer README im Profilverzeichnis, um loszulegen.
repo_name_helper=Ein guter Repository-Name besteht normalerweise aus kurzen, unvergesslichen und einzigartigen Schlagwörtern. Ein Repository namens ".profile" or ".profile-private" kann verwendet werden, um die README.md auf dem Benutzer- oder Organisationsprofil anzuzeigen.
repo_size=Repository-Größe
template=Template
@ -1057,7 +1050,6 @@ fork_branch=Branch, der zum Fork geklont werden soll
all_branches=Alle Branches
view_all_branches=Alle Branches anzeigen
view_all_tags=Alle Tags anzeigen
fork_no_valid_owners=Dieses Repository kann nicht geforkt werden, da keine gültigen Besitzer vorhanden sind.
fork.blocked_user=Das Repository kann nicht geforkt werden, da du vom Repository-Eigentümer blockiert wurdest.
use_template=Dieses Template verwenden
open_with_editor=Mit %s öffnen
@ -1739,7 +1731,6 @@ issues.due_date_form=JJJJ-MM-TT
issues.due_date_form_add=Fälligkeitsdatum hinzufügen
issues.due_date_form_edit=Bearbeiten
issues.due_date_form_remove=Entfernen
issues.due_date_not_writer=Du musst Schreibrechte für dieses Repository haben, um das Fälligkeitsdatum zu ändern.
issues.due_date_not_set=Kein Fälligkeitsdatum gesetzt.
issues.due_date_added=hat %[2]s das Fälligkeitsdatum %[1]s hinzugefügt
issues.due_date_modified=ändert das Abgabedatum von %[2]s auf %[1]s %[3]s s
@ -2106,7 +2097,6 @@ activity.title.releases_1=%d Release
activity.title.releases_n=%d Releases
activity.title.releases_published_by=%s von %s veröffentlicht
activity.published_release_label=Veröffentlicht
activity.no_git_activity=In diesem Zeitraum sind keine Commit-Aktivität vorhanden.
activity.git_stats_exclude_merges=Merges ausgenommen,
activity.git_stats_author_1=%d Autor
activity.git_stats_author_n=%d Autoren

View File

@ -427,7 +427,6 @@ activate_email.title=%s, παρακαλώ επαληθεύστε τη διεύθ
activate_email.text=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να επαληθεύσετε τη διεύθυνση email σας στο <b>%s</b>:
register_notify.title=%[1]s, καλώς ήρθατε στο %[2]s
register_notify.text_1=αυτό είναι το email επιβεβαίωσης εγγραφής για το %s!
register_notify.text_2=Τώρα μπορείτε να συνδεθείτε μέσω του ονόματος χρήστη: %s.
register_notify.text_3=Εάν αυτός ο λογαριασμός έχει δημιουργηθεί για εσάς, παρακαλώ <a href="%s">ορίστε πρώτα τον κωδικό πρόσβασής σας</a>.
@ -871,7 +870,6 @@ webauthn_alternative_tip=Μπορεί να θέλετε να ρυθμίσετε
manage_account_links=Διαχείριση Συνδεδεμένων Λογαριασμών
manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Gitea λογαριασμό σας.
account_links_not_available=Προς το παρόν δεν υπάρχουν εξωτερικοί λογαριασμοί συνδεδεμένοι με τον λογαριασμό σας στο Gitea.
link_account=Σύνδεση Λογαριασμού
remove_account_link=Αφαίρεση Συνδεδεμένου Λογαριασμού
remove_account_link_desc=Η κατάργηση ενός συνδεδεμένου λογαριασμού θα ανακαλέσει την πρόσβασή του στο λογαριασμό σας στο Gitea. Συνέχεια;
@ -926,7 +924,6 @@ fork_to_different_account=Fork σε διαφορετικό λογαριασμό
fork_visibility_helper=Η ορατότητα ενός fork αποθετηρίου δεν μπορεί να αλλάξει.
fork_branch=Κλάδος που θα κλωνοποιηθεί στο fork
all_branches=Όλοι οι κλάδοι
fork_no_valid_owners=Αυτό το αποθετήριο δεν μπορεί να γίνει fork επειδή δεν υπάρχουν έγκυροι ιδιοκτήτες.
use_template=Χρήση αυτού του πρότυπου
download_zip=Λήψη ZIP
download_tar=Λήψη TAR.GZ
@ -1537,7 +1534,6 @@ issues.due_date_form=εεεε-μμ-ηη
issues.due_date_form_add=Προσθήκη ημερομηνίας παράδοσης
issues.due_date_form_edit=Επεξεργασία
issues.due_date_form_remove=Διαγραφή
issues.due_date_not_writer=Χρειάζεστε πρόσβαση εγγραφής στο αποθετήριο για να ενημερώσετε την ημερομηνία λήξης ενός προβλήματος.
issues.due_date_not_set=Δεν ορίστηκε ημερομηνία παράδοσης.
issues.due_date_added=πρόσθεσε την ημερομηνία παράδοσης %s %s
issues.due_date_modified=τροποποίησε την ημερομηνία παράδοσης από %[2]s σε %[1]s %[3]s
@ -1875,7 +1871,6 @@ activity.title.releases_1=%d Κυκλοφορία
activity.title.releases_n=%d Εκδόσεις
activity.title.releases_published_by=%s δημοσιεύτηκε από %s
activity.published_release_label=Δημοσιεύθηκε
activity.no_git_activity=Δεν έχει υπάρξει καμία δραστηριότητα υποβολών σε αυτήν την περίοδο.
activity.git_stats_exclude_merges=Εκτός τις συγχωνεύσεις,
activity.git_stats_author_1=%d συγγραφέας
activity.git_stats_author_n=%d συγγραφείς

View File

@ -117,7 +117,7 @@ files = Files
error = Error
error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.
error503 = The server was unable to complete your request. Please try again later.
error503 = The server could not complete your request. Please try again later.
go_back = Go Back
invalid_data = Invalid data: %v
@ -131,7 +131,7 @@ unpin = Unpin
artifacts = Artifacts
expired = Expired
confirm_delete_artifact = Are you sure you want to delete the artifact '%s' ?
confirm_delete_artifact = Are you sure you want to delete the artifact '%s'?
archived = Archived
@ -171,7 +171,7 @@ internal_error_skipped = Internal error occurred but is skipped: %s
search = Search...
type_tooltip = Search type
fuzzy = Fuzzy
fuzzy_tooltip = Include results that also match the search term closely
fuzzy_tooltip = Include results that closely match the search term
words = Words
words_tooltip = Include only results that match the search term words
regexp = Regexp
@ -506,7 +506,7 @@ activate_email.text = Please click the following link to verify your email addre
register_notify = Welcome to %s
register_notify.title = %[1]s, welcome to %[2]s
register_notify.text_1 = this is your registration confirmation email for %s!
register_notify.text_1 = This is your registration confirmation email for %s!
register_notify.text_2 = You can now login via username: %s.
register_notify.text_3 = If this account has been created for you, please <a href="%s">set your password</a> first.
@ -998,7 +998,7 @@ webauthn_alternative_tip = You may want to configure an additional authenticatio
manage_account_links = Manage Linked Accounts
manage_account_links_desc = These external accounts are linked to your Gitea account.
account_links_not_available = There are currently no external accounts linked to your Gitea account.
account_links_not_available = No external accounts are currently linked to your Gitea account.
link_account = Link Account
remove_account_link = Remove Linked Account
remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue?
@ -1035,8 +1035,8 @@ new_repo_helper = A repository contains all project files, including revision hi
owner = Owner
owner_helper = Some organizations may not show up in the dropdown due to a maximum repository count limit.
repo_name = Repository Name
repo_name_profile_public_hint= .profile is a special repository that you can use to add README.md to your public organization profile, visible to anyone. Make sure its public and initialize it with a README in the profile directory to get started.
repo_name_profile_private_hint = .profile-private is a special repository that you can use to add a README.md to your organization member profile, visible only to organization members. Make sure its private and initialize it with a README in the profile directory to get started.
repo_name_profile_public_hint= .profile is a special repository that you can use to add README.md to your public organization profile, visible to anyone. Make sure it's public and initialize it with a README in the profile directory to get started.
repo_name_profile_private_hint = .profile-private is a special repository that you can use to add a README.md to your organization member profile, visible only to organization members. Make sure it's private and initialize it with a README in the profile directory to get started.
repo_name_helper = Good repository names use short, memorable and unique keywords. A repository named ".profile" or ".profile-private" could be used to add a README.md for the user/organization profile.
repo_size = Repository Size
template = Template
@ -1058,7 +1058,7 @@ fork_branch = Branch to be cloned to the fork
all_branches = All branches
view_all_branches = View all branches
view_all_tags = View all tags
fork_no_valid_owners = This repository can not be forked because there are no valid owners.
fork_no_valid_owners = This repository cannot be forked because there are no valid owners.
fork.blocked_user = Cannot fork the repository because you are blocked by the repository owner.
use_template = Use this template
open_with_editor = Open with %s
@ -1755,7 +1755,7 @@ issues.due_date_form = "yyyy-mm-dd"
issues.due_date_form_add = "Add due date"
issues.due_date_form_edit = "Edit"
issues.due_date_form_remove = "Remove"
issues.due_date_not_writer = "You need write access to this repository in order to update the due date of an issue."
issues.due_date_not_writer = "You need write access to this repository to update the due date of an issue."
issues.due_date_not_set = "No due date set."
issues.due_date_added = "added the due date %s %s"
issues.due_date_modified = "modified the due date from %[2]s to %[1]s %[3]s"
@ -2123,7 +2123,7 @@ activity.title.releases_1 = %d Release
activity.title.releases_n = %d Releases
activity.title.releases_published_by = %s published by %s
activity.published_release_label = Published
activity.no_git_activity = There has not been any commit activity in this period.
activity.no_git_activity = There has been no commit activity in this period.
activity.git_stats_exclude_merges = Excluding merges,
activity.git_stats_author_1 = %d author
activity.git_stats_author_n = %d authors
@ -3251,6 +3251,8 @@ auths.oauth2_required_claim_name_helper = Set this name to restrict login from t
auths.oauth2_required_claim_value = Required Claim Value
auths.oauth2_required_claim_value_helper = Set this value to restrict login from this source to users with a claim with this name and value
auths.oauth2_group_claim_name = Claim name providing group names for this source. (Optional)
auths.oauth2_full_name_claim_name = Full Name Claim Name. (Optional, if set, the user's full name will always be synchronized with this claim)
auths.oauth2_ssh_public_key_claim_name = SSH Public Key Claim Name
auths.oauth2_admin_group = Group Claim value for administrator users. (Optional - requires claim name above)
auths.oauth2_restricted_group = Group Claim value for restricted users. (Optional - requires claim name above)
auths.oauth2_map_group_to_team = Map claimed groups to Organization teams. (Optional - requires claim name above)

View File

@ -424,7 +424,6 @@ activate_email.title=%s, por favor verifique su dirección de correo electrónic
activate_email.text=Por favor, haga clic en el siguiente enlace para verificar su dirección de correo electrónico dentro de <b>%s</b>:
register_notify.title=%[1]s, bienvenido a %[2]s
register_notify.text_1=este es tu correo de confirmación de registro para %s!
register_notify.text_2=Ahora puede iniciar sesión vía nombre de usuario: %s.
register_notify.text_3=Si esta cuenta ha sido creada para usted, por favor <a href="%s">establezca su contraseña</a> primero.
@ -862,7 +861,6 @@ webauthn_delete_key_desc=Si elimina una llave de seguridad ya no podrá utilizar
manage_account_links=Administrar cuentas vinculadas
manage_account_links_desc=Estas cuentas externas están vinculadas a su cuenta de Gitea.
account_links_not_available=Actualmente no hay cuentas externas vinculadas a su cuenta de Gitea.
link_account=Enlazar cuenta
remove_account_link=Eliminar cuenta vinculada
remove_account_link_desc=Eliminar una cuenta vinculada revocará su acceso a su cuenta de Gitea. ¿Continuar?
@ -916,7 +914,6 @@ fork_to_different_account=Forkear a una cuenta diferente
fork_visibility_helper=La visibilidad de un repositorio del cual se ha hecho fork no puede ser cambiada.
fork_branch=Rama a clonar en la bifurcación
all_branches=Todas las ramas
fork_no_valid_owners=Este repositorio no puede ser bifurcado porque no hay propietarios válidos.
use_template=Utilizar esta plantilla
download_zip=Descargar ZIP
download_tar=Descargar TAR.GZ
@ -1527,7 +1524,6 @@ issues.due_date_form=aaaa-mm-dd
issues.due_date_form_add=Añadir fecha de vencimiento
issues.due_date_form_edit=Editar
issues.due_date_form_remove=Eliminar
issues.due_date_not_writer=Necesita acceso de escritura a este repositorio para actualizar la fecha límite de de una incidencia.
issues.due_date_not_set=Sin fecha de vencimiento.
issues.due_date_added=añadió la fecha de vencimiento %s %s
issues.due_date_modified=modificó la fecha de vencimiento de %[2]s a %[1]s %[3]s
@ -1859,7 +1855,6 @@ activity.title.releases_1=%d Lanzamiento
activity.title.releases_n=%d Lanzamientos
activity.title.releases_published_by=%s publicado por %s
activity.published_release_label=Publicado
activity.no_git_activity=No ha habido ningún commit en este período.
activity.git_stats_exclude_merges=Excluyendo fusiones,
activity.git_stats_author_1=%d autor
activity.git_stats_author_n=%d autores

View File

@ -332,7 +332,6 @@ activate_email=نشانی ایمیل خود را تایید کنید
activate_email.text=لطفاً روی پیوند زیر کلیک کنید تا رایانامه‌ی خود را در <b>%s</b> تأیید کنید:
register_notify.title=%[1]s، به %[2]s خوش‌آمدید
register_notify.text_1=این رایانامه‌ی تأیید عضویت شما در %s است!
register_notify.text_2=حالا شما می‌توانید با نام کاربری وارد شوید: %s.
register_notify.text_3=اگر این حساب برای شما ایجاد شده، لطفاً ابتدا <a href="%s">گذرواژه‌ی خود را تنظیم کنید</a>.
@ -675,7 +674,6 @@ twofa_failed_get_secret=خطا در دریافت رمز.
manage_account_links=مدیریت حساب های مرتبط شده
manage_account_links_desc=این حساب های خارجی به حساب Gitea ارتباط دارد.
account_links_not_available=اکنون دیگر هیچ پیوند حساب‌های کاربری خارجی به حساب کاربری شما وجود ندارد.
link_account=پیوند به حساب
remove_account_link=حذف حساب پیوند خرده
remove_account_link_desc=با حذف پیوند خارجی حساب کاربری دسترسی شما به حساب کابریتان توسط آن از بین میرود. آیا ادامه می‌دهید؟
@ -1437,7 +1435,6 @@ activity.title.releases_1=%d انتشار
activity.title.releases_n=%d انتشار
activity.title.releases_published_by=%s منشتر شده توسط %s
activity.published_release_label=منتشر شده
activity.no_git_activity=در این دوره فعالیت کامیتی ارسال نشده است.
activity.git_stats_exclude_merges=به استثنای ادغام‎ها ،
activity.git_stats_author_1=%d بانی
activity.git_stats_author_n=%d بانی

View File

@ -117,7 +117,6 @@ files=Fichiers
error=Erreur
error404=La page que vous essayez d'atteindre <strong>n'existe pas</strong> ou <strong>vous n'êtes pas autorisé</strong> à la voir.
error503=Le serveur na pas pu répondre à votre requête. Veuillez réessayer plus tard.
go_back=Retour
invalid_data=Données invalides : %v
@ -131,7 +130,6 @@ unpin=Désépingler
artifacts=Artefacts
expired=Expiré
confirm_delete_artifact=Êtes-vous sûr de vouloir supprimer lartefact « %s » ?
archived=Archivé
@ -171,7 +169,6 @@ internal_error_skipped=Une erreur interne est survenue, mais ignorée : %s
search=Rechercher…
type_tooltip=Type de recherche
fuzzy=Approximative
fuzzy_tooltip=Inclure également les résultats proches de la recherche
words=Mots
words_tooltip=Inclure uniquement les résultats qui correspondent exactement aux mots recherchés
regexp=Regexp
@ -506,7 +503,6 @@ activate_email.text=Veuillez cliquer sur le lien suivant pour vérifier votre ad
register_notify=Bienvenue sur %s
register_notify.title=%[1]s, bienvenue à %[2]s
register_notify.text_1=ceci est votre courriel de confirmation d'inscription pour %s!
register_notify.text_2=Vous pouvez maintenant vous connecter avec le nom d'utilisateur : %s.
register_notify.text_3=Si ce compte a été créé pour vous, veuillez <a href="%s">définir votre mot de passe</a> d'abord.
@ -998,7 +994,6 @@ webauthn_alternative_tip=Vous devriez configurer une méthode dauthentificati
manage_account_links=Gérer les comptes liés
manage_account_links_desc=Ces comptes externes sont liés à votre compte Gitea.
account_links_not_available=Il n'y a pour l'instant pas de compte externe connecté à votre compte Gitea.
link_account=Lier un Compte
remove_account_link=Supprimer un compte lié
remove_account_link_desc=La suppression d'un compte lié révoquera son accès à votre compte Gitea. Continuer ?
@ -1035,8 +1030,6 @@ new_repo_helper=Un dépôt contient tous les fichiers dun projet, ainsi que l
owner=Propriétaire
owner_helper=Certaines organisations peuvent ne pas apparaître dans la liste déroulante en raison d'une limite maximale du nombre de dépôts.
repo_name=Nom du dépôt
repo_name_profile_public_hint=.profile est un dépôt spécial que vous pouvez utiliser pour ajouter un README.md à votre profil public dorganisation, visible à tout le monde. Assurez-vous quil soit public et initialisez-le avec un README dans le répertoire de profil pour commencer.
repo_name_profile_private_hint=.profile-private est un dépôt spécial que vous pouvez utiliser pour ajouter un README.md à votre profil dorganisation, visible uniquement aux membres de lorganisation. Assurez-vous quil soit privé et initialisez-le avec un README dans le répertoire de profil pour commencer.
repo_name_helper=Idéalement, le nom dun dépôt devrait être court, mémorisable et unique. Vous pouvez personnaliser votre profil ou celui de votre organisation en créant un dépôt nommé « .profile » ou « .profile-private » et contenant un README.md.
repo_size=Taille du dépôt
template=Modèle
@ -1058,7 +1051,6 @@ fork_branch=Branche à cloner sur la bifurcation
all_branches=Toutes les branches
view_all_branches=Voir toutes les branches
view_all_tags=Voir toutes les étiquettes
fork_no_valid_owners=Ce dépôt ne peut pas être bifurqué car il na pas de propriétaire valide.
fork.blocked_user=Impossible de bifurquer le dépôt car vous êtes bloqué par son propriétaire.
use_template=Utiliser ce modèle
open_with_editor=Ouvrir avec %s
@ -1755,7 +1747,6 @@ issues.due_date_form=aaaa-mm-jj
issues.due_date_form_add=Ajouter une échéance
issues.due_date_form_edit=Éditer
issues.due_date_form_remove=Supprimer
issues.due_date_not_writer=Vous avez besoin dun accès en écriture à ce dépôt pour modifier léchéance de ses tickets.
issues.due_date_not_set=Aucune échéance n'a été définie.
issues.due_date_added=a ajouté l'échéance %s %s
issues.due_date_modified=a modifié l'échéance de %[2]s à %[1]s %[3]s
@ -2123,7 +2114,6 @@ activity.title.releases_1=%d publication
activity.title.releases_n=%d publications
activity.title.releases_published_by=%s publiée par %s
activity.published_release_label=Publiée
activity.no_git_activity=Il n'y a pas eu de nouvelle révision dans cette période.
activity.git_stats_exclude_merges=En excluant les fusions,
activity.git_stats_author_1=%d auteur
activity.git_stats_author_n=%d auteurs

View File

@ -117,7 +117,6 @@ files=Comhaid
error=Earráid
error404=Níl an leathanach atá tú ag iarraidh a bhaint amach <strong>ann</strong> nó <strong>níl tú údaraithe</strong> chun é a fheiceáil.
error503=Níorbh fhéidir leis an bhfreastalaí d'iarratas a chur i gcrích. Bain triail eile as ar ball.
go_back=Ar ais
invalid_data=Sonraí neamhbhailí: %v
@ -131,7 +130,6 @@ unpin=Díphoráil
artifacts=Déantáin
expired=Imithe in éag
confirm_delete_artifact=An bhfuil tú cinnte gur mhaith leat an déantán '%s' a scriosadh?
archived=Cartlann
@ -171,7 +169,6 @@ internal_error_skipped=Tharla earráid inmheánach ach éirithe as: %s
search=Cuardaigh...
type_tooltip=Cineál cuardaigh
fuzzy=Doiléir
fuzzy_tooltip=Cuir san áireamh torthaí a mheaitseálann an téarma cuardaigh go dlúth freisin
words=Focail
words_tooltip=Ná cuir san áireamh ach torthaí a mheaitseálann na focail téarma cuardaigh
regexp=Nathanna Rialta
@ -506,7 +503,6 @@ activate_email.text=Cliceáil ar an nasc seo a leanas le do sheoladh ríomhphois
register_notify=Fáilte go dtí %s
register_notify.title=%[1]s, fáilte go %[2]s
register_notify.text_1=is é seo do ríomhphost deimhnithe clárúcháin do %s!
register_notify.text_2=Is féidir leat logáil isteach anois trí ainm úsáideora: %s.
register_notify.text_3=Má cruthaíodh an cuntas seo duit, <a href="%s">socraigh do phasfhocal</a> ar dtús.
@ -998,7 +994,6 @@ webauthn_alternative_tip=B'fhéidir gur mhaith leat modh fíordheimhnithe breise
manage_account_links=Bainistigh Cuntais Nasctha
manage_account_links_desc=Tá na cuntais sheachtracha seo nasctha le do chuntas Gitea.
account_links_not_available=Níl aon chuntais sheachtracha nasctha le do chuntas Gitea faoi láthair.
link_account=Cuntas Nasc
remove_account_link=Bain Cuntas Nasctha
remove_account_link_desc=Ag baint cuntas nasctha, cuirfear a rochtain ar do chuntas Gitea a chúlghairm. Lean ar aghaidh?
@ -1035,8 +1030,6 @@ new_repo_helper=Tá gach comhad tionscadail i stór, lena n-áirítear stair ath
owner=Úinéir
owner_helper=B'fhéidir nach dtaispeánfar roinnt eagraíochtaí sa anuas mar gheall ar theorainn uasta comhaireamh stórais.
repo_name=Ainm Stórais
repo_name_profile_public_hint=Is stóras speisialta é .profile is féidir leat a úsáid chun README.md a chur le do phróifíl eagraíochta poiblí, le feiceáil ag aon duine. Cinntigh go bhfuil sé poiblí agus tosaigh é le README san eolaire próifíle le tosú.
repo_name_profile_private_hint=Is stóras speisialta é .profile-private is féidir leat a úsáid chun README.md a chur le do phróifíl bhall eagraíochta, nach féidir a fheiceáil ach ag baill eagraíochta. Cinntigh go bhfuil sé príobháideach agus tosaigh le README sa eolaire próifíle chun tús a chur leis.
repo_name_helper=Úsáideann ainmneacha maith stóras focail eochair gairide, áithnid agus uathúla. D'fhéadfaí stóras darbh ainm '.profile' nó '.profile-private' a úsáid chun README.md a chur leis an bpróifíl úsáideora/eagraíochta.
repo_size=Méid an Stóras
template=Teimpléad
@ -1058,7 +1051,6 @@ fork_branch=Brainse le clónú chuig an bhforc
all_branches=Gach brainse
view_all_branches=Féach ar gach brainse
view_all_tags=Féach ar gach clib
fork_no_valid_owners=Ní féidir an stór seo a fhorcáil toisc nach bhfuil úinéirí bailí ann.
fork.blocked_user=Ní féidir an stór a fhorcáil toisc go bhfuil úinéir an stórais bac ort.
use_template=Úsáid an teimpléad seo
open_with_editor=Oscail le %s
@ -1755,7 +1747,6 @@ issues.due_date_form=bbbb-mm-ll
issues.due_date_form_add=Cuir dáta dlite leis
issues.due_date_form_edit=Cuir in eagar
issues.due_date_form_remove=Bain
issues.due_date_not_writer=Ní mór duit rochtain scríofa ar an stór seo d'fhonn dáta dlite eisiúna a nuashonrú.
issues.due_date_not_set=Níl aon dáta dlite socraithe.
issues.due_date_added=cuireadh an dáta dlite %s %s
issues.due_date_modified=d'athraigh an dáta dlite ó %[2]s go %[1]s %[3]s
@ -2123,7 +2114,6 @@ activity.title.releases_1=Scaoileadh %d
activity.title.releases_n=Eisiúintí %d
activity.title.releases_published_by=%s foilsithe ag %s
activity.published_release_label=Foilsithe
activity.no_git_activity=Níor rinneadh aon ghníomhaíocht tiomanta sa tréimhse seo.
activity.git_stats_exclude_merges=Gan cumaisc a áireamh,
activity.git_stats_author_1=%d údar
activity.git_stats_author_n=%d údair

View File

@ -1017,7 +1017,6 @@ activity.title.releases_1=%d Kiadás
activity.title.releases_n=%d Kiadások
activity.title.releases_published_by=%s publikálta %s
activity.published_release_label=Publikálva
activity.no_git_activity=Nem voltak commit-ok ebben az időszakban.
activity.git_stats_commit_1=%d commit
activity.git_stats_commit_n=%d commit
activity.git_stats_file_n=%d fájl

View File

@ -117,7 +117,6 @@ pin=Sematkan
unpin=Lepas sematan
artifacts=Artefak
confirm_delete_artifact=Apakah Anda yakin ingin menghapus artefak '%s' ?
archived=Diarsipkan
@ -147,7 +146,6 @@ no_results_found=Hasil tidak ditemukan.
[search]
search=Cari...
type_tooltip=Tipe pencarian
fuzzy_tooltip=Termasuk juga hasil yang mendekati kata pencarian
exact_tooltip=Hanya menampilkan hasil yang cocok dengan istilah pencarian
repo_kind=Cari repo...
user_kind=Telusuri pengguna...
@ -561,7 +559,6 @@ passcode_invalid=Kode sandi salah. Coba lagi.
manage_account_links=Kelola akun tertaut
manage_account_links_desc=Semua akun eksternal ini sementara tertaut dengan akun Gitea Anda.
account_links_not_available=Saat ini tidak ada akun eksternal yang tertaut ke akun Gitea ini.
link_account=Tautan Akun
remove_account_link=Hapus Akun Tertaut
remove_account_link_desc=Menghapus akun tertaut akan membuat akun itu tidak bisa mengakses akun Gitea Anda. Lanjutkan?

View File

@ -298,7 +298,6 @@ activate_email=Staðfestu netfangið þitt
activate_email.text=Vinsamlegast smelltu á eftirfarandi tengil til að staðfesta netfangið þitt innan <b>%s</b>:
register_notify.title=%[1]s, velkomin(n) í %[2]s
register_notify.text_1=þetta er staðfestingarpóstur þinn fyrir skráningu á %s!
register_notify.text_2=Þú getur nú skráð þig inn með notandanafni: %s.
register_notify.text_3=Ef þessi reikningur hefur verið búinn til fyrir þig, vinsamlegast <a href="%s">stilltu lykilorðið þitt</a> fyrst.

View File

@ -356,7 +356,6 @@ activate_email=Verifica il tuo indirizzo e-mail
activate_email.text=Clicca sul seguente link per verificare il tuo indirizzo email entro <b>%s</b>:
register_notify.title=%[1]s, benvenuto in %[2]s
register_notify.text_1=questa è la tua email di conferma di registrazione per %s!
register_notify.text_2=Ora è possibile accedere tramite nome utente: %s.
register_notify.text_3=Se questo account è stato creato per te, per favore <a href="%s">imposta prima la tua password</a>.
@ -725,7 +724,6 @@ webauthn_delete_key_desc=Se si rimuove una chiave di sicurezza non è più possi
manage_account_links=Gestisci gli account collegati
manage_account_links_desc=Questi account esterni sono collegati al tuo account Gitea.
account_links_not_available=Attualmente non è collegato alcun account esterno al tuo account Gitea.
link_account=Collega Account
remove_account_link=Rimuovi account collegato
remove_account_link_desc=Rimuovere un account collegato ne revoca l'accesso al tuo account Gitea. Continuare?
@ -1555,7 +1553,6 @@ activity.title.releases_1=%d Release
activity.title.releases_n=%d Release
activity.title.releases_published_by=%s pubblicata da %s
activity.published_release_label=Pubblicata
activity.no_git_activity=In questo periodo non c'è stata alcuna attività di commit.
activity.git_stats_exclude_merges=Escludendo i merge,
activity.git_stats_author_1=%d autore
activity.git_stats_author_n=%d autori

View File

@ -117,7 +117,6 @@ files=ファイル
error=エラー
error404=アクセスしようとしたページは<strong>存在しない</strong>か、閲覧が<strong>許可されていません</strong>。
error503=サーバーはリクエストを完了できませんでした。 後でもう一度お試しください。
go_back=戻る
invalid_data=無効なデータ: %v
@ -131,7 +130,6 @@ unpin=ピン留め解除
artifacts=成果物
expired=期限切れ
confirm_delete_artifact=アーティファクト %s を削除してよろしいですか?
archived=アーカイブ
@ -171,7 +169,6 @@ internal_error_skipped=内部エラーが発生しましたがスキップされ
search=検索…
type_tooltip=検索タイプ
fuzzy=あいまい
fuzzy_tooltip=検索語におおよそ一致する結果も含めます
words=単語
words_tooltip=検索語と一致する結果だけを含めます
regexp=正規表現
@ -506,7 +503,6 @@ activate_email.text=あなたのメールアドレスを確認するため、<b>
register_notify=%s へようこそ
register_notify.title=%[1]s さん、%[2]s にようこそ
register_notify.text_1=これは %s への登録確認メールです!
register_notify.text_2=あなたはユーザー名 %s でログインできるようになりました。
register_notify.text_3=このアカウントがあなたに作成されたものであれば、最初に<a href="%s">パスワードを設定</a>してください。
@ -998,7 +994,6 @@ webauthn_alternative_tip=もうひとつ別の認証方法も設定しておく
manage_account_links=連携アカウントの管理
manage_account_links_desc=これらの外部アカウントがGiteaアカウントと連携されています。
account_links_not_available=現在このGiteaアカウントが連携している外部アカウントはありません。
link_account=アカウントをリンク
remove_account_link=連携アカウントの削除
remove_account_link_desc=連携アカウントを削除し、Giteaアカウントへのアクセス権を取り消します。 続行しますか?
@ -1035,8 +1030,6 @@ new_repo_helper=リポジトリには、プロジェクトのすべてのファ
owner=オーナー
owner_helper=リポジトリ数の上限により、一部の組織はドロップダウンに表示されない場合があります。
repo_name=リポジトリ名
repo_name_profile_public_hint=.profile は特別なリポジトリで、これを使用して、あなたの組織の公開プロフィール(誰でも閲覧可能)に README.md を追加することができます。 利用を開始するには、必ず公開リポジトリとし、プロフィールディレクトリにREADMEを追加して初期化してください。
repo_name_profile_private_hint=.profile-private は特別なリポジトリで、これを使用して、あなたの組織のメンバー向けプロフィール(組織メンバーのみ閲覧可能)に README.md を追加することができます。 利用を開始するには、必ずプライベートリポジトリとし、プロフィールディレクトリにREADMEを追加して初期化してください。
repo_name_helper=リポジトリ名は、短く、覚えやすく、他と重複しないキーワードを使用しましょう。 リポジトリ名を ".profile" または ".profile-private" にして README.md を追加すると、ユーザーや組織のプロフィールとなります。
repo_size=リポジトリサイズ
template=テンプレート
@ -1058,7 +1051,6 @@ fork_branch=フォークにクローンされるブランチ
all_branches=すべてのブランチ
view_all_branches=すべてのブランチを表示
view_all_tags=すべてのタグを表示
fork_no_valid_owners=このリポジトリには有効なオーナーがいないため、フォークできません。
fork.blocked_user=リポジトリのオーナーがあなたをブロックしているため、リポジトリをフォークできません。
use_template=このテンプレートを使用
open_with_editor=%s で開く
@ -1746,7 +1738,6 @@ issues.due_date_form=yyyy-mm-dd
issues.due_date_form_add=期日の追加
issues.due_date_form_edit=変更
issues.due_date_form_remove=削除
issues.due_date_not_writer=イシューの期日を変更するには、リポジトリへの書き込み権限が必要です。
issues.due_date_not_set=期日は設定されていません。
issues.due_date_added=が期日 %s を追加 %s
issues.due_date_modified=が期日を %[2]s から %[1]s に変更 %[3]s
@ -2113,7 +2104,6 @@ activity.title.releases_1=%d件のリリース
activity.title.releases_n=%d件のリリース
activity.title.releases_published_by=%sが%sによって発行されました
activity.published_release_label=発行
activity.no_git_activity=この期間にはコミットのアクティビティがありません。
activity.git_stats_exclude_merges=マージを除くと、
activity.git_stats_author_1=%d人の作成者
activity.git_stats_author_n=%d人の作成者

View File

@ -510,7 +510,6 @@ twofa_enrolled=당신의 계정에 2단계 인증이 설정되었습니다. 스
manage_account_links=연결된 계정 관리
manage_account_links_desc=Gitea 계정에 연결된 외부 계정입니다.
account_links_not_available=현재 Gitea 계정에 연결된 외부 계정이 없습니다.
link_account=계정 연결
remove_account_link=연결된 계정 제거
remove_account_link_desc=해당 계정을 연결해제 하는 경우 Gitea 계정에 대한 접근 권한이 사라지게 됩니다. 계속하시겠습니까?

View File

@ -430,7 +430,6 @@ activate_email.title=%s, apstipriniet savu e-pasta adresi
activate_email.text=Nospiediet uz saites, lai apstiprinātu savu e-pasta adresi lapā <b>%s</b>:
register_notify.title=%[1]s, esat reģistrējies %[2]s
register_notify.text_1=šis ir reģistrācijas apstiprinājuma e-pasts lapai %s!
register_notify.text_2=Tagad varat autorizēties ar lietotāja vārdu: %s.
register_notify.text_3=Ja šis konts Jums tika izveidots, tad obligāti <a href="%s">nomainiet citu paroli</a>.
@ -876,7 +875,6 @@ webauthn_alternative_tip=Ir vēlams uzstādīt papildu autentifikācijas veidu.
manage_account_links=Pārvaldīt saistītos kontus
manage_account_links_desc=Šādi ārējie konti ir piesaistīti Jūsu Gitea kontam.
account_links_not_available=Pašlaik nav neviena ārējā konta piesaistīta šim kontam.
link_account=Sasaistīt kontu
remove_account_link=Noņemt saistīto kontu
remove_account_link_desc=Noņemot saistīto kontu, tam tiks liegta piekļuve Jūsu Gitea kontam. Vai turpināt?
@ -931,7 +929,6 @@ fork_to_different_account=Atdalīt uz citu kontu
fork_visibility_helper=Atdalītam repozitorijam nav iespējams mainīt tā redzamību.
fork_branch=Atzars, ko klonēt atdalītajā repozitorijā
all_branches=Visi atzari
fork_no_valid_owners=Šim repozitorijam nevar izveidot atdalītu repozitoriju, jo tam nav spēkā esošu īpašnieku.
use_template=Izmantot šo sagatavi
download_zip=Lejupielādēt ZIP
download_tar=Lejupielādēt TAR.GZ
@ -1543,7 +1540,6 @@ issues.due_date_form=dd.mm.yyyy
issues.due_date_form_add=Pievienot izpildes termiņu
issues.due_date_form_edit=Labot
issues.due_date_form_remove=Noņemt
issues.due_date_not_writer=Ir nepieciešama rakstīšanas piekļuve šim repozitorijam, lai varētu mainīt problēmas plānoto izpildes datumu.
issues.due_date_not_set=Izpildes termiņš nav uzstādīts.
issues.due_date_added=pievienoja izpildes termiņu %s %s
issues.due_date_modified=mainīja termiņa datumu no %[2]s uz %[1]s %[3]s
@ -1881,7 +1877,6 @@ activity.title.releases_1=%d versiju
activity.title.releases_n=%d versijas
activity.title.releases_published_by=%s publicēja %s
activity.published_release_label=Publicēts
activity.no_git_activity=Šajā laika periodā nav notikušas nekādas izmaiņas.
activity.git_stats_exclude_merges=Neskaitot sapludināšanas revīzijas,
activity.git_stats_author_1=%d autors
activity.git_stats_author_n=%d autori

View File

@ -355,7 +355,6 @@ activate_email=Verifieer uw e-mailadres
activate_email.text=Klik op de volgende link om je e-mailadres te bevestigen in <b>%s</b>:
register_notify.title=%[1]s, welkom bij %[2]s
register_notify.text_1=dit is uw registratie bevestigingsemail voor %s!
register_notify.text_2=U kunt nu inloggen via de gebruikersnaam: %s.
register_notify.text_3=Als dit account voor u is aangemaakt, kunt u eerst <a href="%s">uw wachtwoord instellen</a>.
@ -723,7 +722,6 @@ webauthn_delete_key_desc=Als u een beveiligingssleutel verwijdert, kunt u er nie
manage_account_links=Gekoppelde accounts beheren
manage_account_links_desc=Deze externe accounts zijn gekoppeld aan je Gitea-account.
account_links_not_available=Er zijn momenteel geen externe accounts aan je Gitea-account gelinkt.
link_account=Account koppelen
remove_account_link=Gekoppeld account verwijderen
remove_account_link_desc=Als je een gekoppeld account verwijdert, verliest dit account toegang tot je Gitea-account. Doorgaan?
@ -1550,7 +1548,6 @@ activity.title.releases_1=%d Release
activity.title.releases_n=%d Releases
activity.title.releases_published_by=%s gepubliceerd door %s
activity.published_release_label=Gepubliceerd
activity.no_git_activity=Er is in deze periode geen sprake geweest van een commit activiteit.
activity.git_stats_exclude_merges=Exclusief merges,
activity.git_stats_author_1=%d auteur
activity.git_stats_author_n=%d auteurs

View File

@ -348,7 +348,6 @@ activate_email=Potwierdź swój adres e-mail
activate_email.text=Aby zweryfikować swój adres e-mail, w ciągu następnych <b>%s</b> kliknij poniższy link:
register_notify.title=%[1]s, witaj w %[2]s
register_notify.text_1=to jest Twój e-mail z potwierdzeniem rejestracji dla %s!
register_notify.text_2=Możesz teraz zalogować się za pomocą nazwy użytkownika: %s.
register_notify.text_3=Jeśli to konto zostało utworzone dla Ciebie, <a href="%s">ustaw swoje hasło</a>.
@ -682,7 +681,6 @@ webauthn_delete_key_desc=Jeżeli usuniesz klucz bezpieczeństwa, utracisz możli
manage_account_links=Zarządzaj powiązanymi kontami
manage_account_links_desc=Te konta zewnętrzne są powiązane z Twoim kontem Gitea.
account_links_not_available=Obecnie nie ma żadnych zewnętrznych kont powiązanych z tym kontem Gitea.
link_account=Powiąż konto
remove_account_link=Usuń powiązane konto
remove_account_link_desc=Usunięcie powiązanego konta unieważni jego dostęp do Twojego konta Gitea. Kontynuować?
@ -1405,7 +1403,6 @@ activity.title.releases_1=%d Wydanie
activity.title.releases_n=%d Wydań
activity.title.releases_published_by=%s opublikowane przez %s
activity.published_release_label=Opublikowane
activity.no_git_activity=Nie było żadnej aktywności w tym okresie czasu.
activity.git_stats_exclude_merges=Wykluczając scalenia,
activity.git_stats_author_1=%d autor
activity.git_stats_author_n=%d autorzy

View File

@ -123,7 +123,6 @@ pin=Fixar
unpin=Desfixar
artifacts=Artefatos
confirm_delete_artifact=Tem certeza que deseja excluir o artefato '%s' ?
archived=Arquivado
@ -428,7 +427,6 @@ activate_email.title=%s, por favor verifique o seu endereço de e-mail
activate_email.text=Por favor clique no link a seguir para verificar o seu endereço de e-mail em <b>%s</b>:
register_notify.title=%[1]s, bem-vindo(a) a %[2]s
register_notify.text_1=este é o seu e-mail de confirmação de registro para %s!
register_notify.text_2=Agora você pode entrar com o nome de usuário: %s.
register_notify.text_3=Se esta conta foi criada para você, <a href="%s">defina sua senha</a> primeiro.
@ -871,7 +869,6 @@ webauthn_alternative_tip=Você pode querer configurar um método de autenticaç
manage_account_links=Gerenciar contas vinculadas
manage_account_links_desc=Estas contas externas estão vinculadas a sua conta de Gitea.
account_links_not_available=Não existem contas externas atualmente vinculadas a esta conta.
link_account=Vincular Conta
remove_account_link=Remover conta vinculada
remove_account_link_desc=A exclusão da chave SSH revogará o acesso à sua conta. Continuar?
@ -926,7 +923,6 @@ fork_to_different_account=Faça um fork para uma conta diferente
fork_visibility_helper=A visibilidade do fork de um repositório não pode ser alterada.
fork_branch=Branch a ser clonado para o fork
all_branches=Todos os branches
fork_no_valid_owners=Não é possível fazer um fork desse repositório porque não há proprietários validos.
use_template=Usar este modelo
download_zip=Baixar ZIP
download_tar=Baixar TAR.GZ
@ -1536,7 +1532,6 @@ issues.due_date_form=dd/mm/aaaa
issues.due_date_form_add=Adicionar data limite
issues.due_date_form_edit=Editar
issues.due_date_form_remove=Remover
issues.due_date_not_writer=Você precisa de acesso de gravação a esse repositório para atualizar a data limite de uma issue.
issues.due_date_not_set=Data limite não informada.
issues.due_date_added=adicionou a data limite %s %s
issues.due_date_modified=modificou a data limite de %[2]s para %[1]s %[3]s
@ -1874,7 +1869,6 @@ activity.title.releases_1=%d Versão
activity.title.releases_n=%d Versões
activity.title.releases_published_by=%s publicada(s) por %s
activity.published_release_label=Publicado
activity.no_git_activity=Não houve nenhuma atividade de commit neste período.
activity.git_stats_exclude_merges=Excluindo merges,
activity.git_stats_author_1=%d autor
activity.git_stats_author_n=%d autores

View File

@ -117,7 +117,6 @@ files=Ficheiros
error=Erro
error404=A página que pretende aceder <strong>não existe</strong> ou <strong>não tem autorização</strong> para a ver.
error503=O servidor não conseguiu concluir o seu pedido. Tente novamente mais tarde.
go_back=Voltar
invalid_data=Dados inválidos: %v
@ -131,7 +130,6 @@ unpin=Desafixar
artifacts=Artefactos
expired=Expirado
confirm_delete_artifact=Tem a certeza que quer eliminar este artefacto "%s"?
archived=Arquivado
@ -171,7 +169,6 @@ internal_error_skipped=Ocorreu um erro interno mas foi ignorado: %s
search=Pesquisar...
type_tooltip=Tipo de pesquisa
fuzzy=Aproximada
fuzzy_tooltip=Incluir também os resultados que estejam próximos do termo de pesquisa
words=Palavras
words_tooltip=Incluir apenas os resultados que correspondam às palavras do termo de pesquisa
regexp=Regexp
@ -506,7 +503,6 @@ activate_email.text=Por favor clique na seguinte ligação para validar o seu en
register_notify=Bem-vindo(a) a %s
register_notify.title=%[1]s, bem-vindo(a) a %[2]s
register_notify.text_1=este é o seu email de confirmação de registo para %s!
register_notify.text_2=Agora pode iniciar a sessão com o nome de utilizador: %s.
register_notify.text_3=Se esta conta foi criada para si, <a href="%s">defina a sua senha</a> primeiro.
@ -998,7 +994,6 @@ webauthn_alternative_tip=Poderá querer configurar um método de autenticação
manage_account_links=Gerir contas vinculadas
manage_account_links_desc=Estas contas externas estão vinculadas à sua conta do Gitea.
account_links_not_available=Neste momento não existem contas externas vinculadas à sua conta do Gitea.
link_account=Vincular conta
remove_account_link=Remover conta vinculada
remove_account_link_desc=A remoção de uma conta vinculada revogará o acesso dessa conta à sua conta do Gitea. Quer continuar?
@ -1035,8 +1030,6 @@ new_repo_helper=Um repositório contém todos os ficheiros do trabalho, incluind
owner=Proprietário(a)
owner_helper=Algumas organizações podem não aparecer na lista suspensa devido a um limite máximo de contagem de repositórios.
repo_name=Nome do repositório
repo_name_profile_public_hint=.profile é um repositório especial que pode usar para adicionar README.md ao seu perfil público da organização, visível para qualquer pessoa. Certifique-se que é público e inicialize-o com um README na pasta do perfil para começar.
repo_name_profile_private_hint=.profile-private é um repositório especial que pode usar para adicionar um README.md ao seu perfil de membro da organização, visível apenas para membros da organização. Certifique-se que é privado e inicialize-o com um README na pasta de perfil para começar.
repo_name_helper=Bons nomes de repositórios usam palavras-chave curtas, memorizáveis e únicas. Um repositório chamado ".profile" ou ".profile-private" pode ser usado para adicionar um README.md ao perfil do utilizador ou da organização.
repo_size=Tamanho do repositório
template=Modelo
@ -1058,7 +1051,6 @@ fork_branch=Ramo a ser clonado para a derivação
all_branches=Todos os ramos
view_all_branches=Ver todos os ramos
view_all_tags=Ver todas as etiquetas
fork_no_valid_owners=Não pode fazer uma derivação deste repositório porque não existem proprietários válidos.
fork.blocked_user=Não pode derivar o repositório porque foi bloqueado/a pelo/a proprietário/a do repositório.
use_template=Usar este modelo
open_with_editor=Abrir com %s
@ -1755,7 +1747,6 @@ issues.due_date_form=yyyy-mm-dd
issues.due_date_form_add=Adicionar data de vencimento
issues.due_date_form_edit=Editar
issues.due_date_form_remove=Remover
issues.due_date_not_writer=Tem que ter acesso de escrita neste repositório para poder modificar a data de vencimento de uma questão.
issues.due_date_not_set=Sem data de vencimento definida.
issues.due_date_added=adicionou a data de vencimento %s %s
issues.due_date_modified=modificou a data de vencimento de %[2]s para %[1]s %[3]s
@ -2123,7 +2114,6 @@ activity.title.releases_1=%d lançamento
activity.title.releases_n=%d Lançamentos
activity.title.releases_published_by=%s publicado por %s
activity.published_release_label=Publicado
activity.no_git_activity=Não houve quaisquer cometimentos feitos durante este período.
activity.git_stats_exclude_merges=Excluindo integrações,
activity.git_stats_author_1=%d autor
activity.git_stats_author_n=%d autores

View File

@ -425,7 +425,6 @@ activate_email.title=%s, пожалуйста, подтвердите ваш а
activate_email.text=Пожалуйста, перейдите по ссылке, чтобы подтвердить ваш адрес электронной почты в течение <b>%s</b>:
register_notify.title=%[1]s, добро пожаловать в %[2]s
register_notify.text_1=это письмо с вашим подтверждением регистрации в %s!
register_notify.text_2=Теперь вы можете войти через логин: %s.
register_notify.text_3=Если эта учётная запись была создана для вас, пожалуйста, сначала <a href="%s">установите пароль</a>.
@ -864,7 +863,6 @@ webauthn_key_loss_warning=В случае утраты ключей безопа
manage_account_links=Управление привязанными аккаунтами
manage_account_links_desc=Эти внешние аккаунты привязаны к вашему аккаунту Gitea.
account_links_not_available=В настоящее время нет внешних аккаунтов, привязанных к вашему аккаунту Gitea.
link_account=Привязать аккаунт
remove_account_link=Удалить привязанный аккаунт
remove_account_link_desc=Удаление привязанной учётной записи отменит её доступ к вашей учётной записи Gitea. Продолжить?
@ -1836,7 +1834,6 @@ activity.title.releases_1=%d релиз
activity.title.releases_n=%d релизов
activity.title.releases_published_by=%s опубликованы %s
activity.published_release_label=Опубликовано
activity.no_git_activity=В этот период не было новых коммитов.
activity.git_stats_exclude_merges=За исключением слияний,
activity.git_stats_author_1=%d автор
activity.git_stats_author_n=%d автора(ов)

View File

@ -322,7 +322,6 @@ activate_email=ඔබගේ විද්යුත් තැපැල් ලි
activate_email.text=තුළ ඔබගේ විද්යුත් තැපැල් ලිපිනය සත්යාපනය කිරීමට පහත සබැඳිය ක්ලික් කරන්න <b>%s</b>:
register_notify.title=%[1]s, %[2]s වෙත සාදරයෙන් පිළිගනිමු
register_notify.text_1=මෙය %sසඳහා ඔබගේ ලියාපදිංචි තහවුරු කිරීමේ විද්යුත් තැපෑලයි!
register_notify.text_2=ඔබට දැන් පරිශීලක නාමය හරහා පිවිසිය හැකිය: %s.
register_notify.text_3=මෙම ගිණුම ඔබ වෙනුවෙන් නිර්මාණය කර තිබේ නම්, කරුණාකර <a href="%s">ඔබගේ මුරපදය</a> පළමු සකසන්න.
@ -664,7 +663,6 @@ twofa_failed_get_secret=රහස්ය වීමට අසමත් විය.
manage_account_links=සම්බන්ධිත ගිණුම් කළමනාකරණය කරන්න
manage_account_links_desc=මෙම බාහිර ගිණුම් ඔබගේ Gitea ගිණුමට සම්බන්ධ කර ඇත.
account_links_not_available=දැනට ඔබගේ Gitea ගිණුමට සම්බන්ධ බාහිර ගිණුම් නොමැත.
link_account=ගිණුම සබැඳින්න
remove_account_link=සම්බන්ධිත ගිණුම ඉවත් කරන්න
remove_account_link_desc=සම්බන්ධිත ගිණුමක් ඉවත් කිරීම ඔබගේ Gitea ගිණුමට එහි ප්රවේශය අවලංගු කරනු ඇත. දිගටම?
@ -1401,7 +1399,6 @@ activity.title.releases_1=නිකුතු %d
activity.title.releases_n=නිකුතු %d
activity.title.releases_published_by=%s විසින් ප්රකාශයට පත් කර %s
activity.published_release_label=ප්රකාශයට පත්
activity.no_git_activity=මෙම කාලය තුළ කිසිදු කැපවීමක් සිදු වී නොමැත.
activity.git_stats_exclude_merges=ඒකාබද්ධ කිරීම හැර,
activity.git_stats_author_1=%d කර්තෘ
activity.git_stats_author_n=%d කතුවරුන්

View File

@ -411,7 +411,6 @@ activate_email=Overte svoju e-mailovú adresu
activate_email.text=Pre overenie vašej e-mailovej adresy kliknite, prosím, na nasledovný odkaz do <b>%s</b>:
register_notify.title=%[1]s, vitajte v %[2]s
register_notify.text_1=toto je e-mail potvrdzujúci vašu registráciu pre %s!
register_notify.text_2=Teraz sa môžete prihlásiť s používateľským menom: %s.
register_notify.text_3=Ak bol tento účet vytvorený pre vás, nastavte prosím najskôr <a href="%s">svoje heslo</a>.
@ -788,7 +787,6 @@ webauthn_delete_key_desc=Ak odstránite bezpečnostný kľúč, už sa s ním ne
manage_account_links=Spravovať prepojené kontá
manage_account_links_desc=Tieto externé účty sú prepojené s vaším účtom Gitea.
account_links_not_available=V súčasnosti nie sú s vaším účtom Gitea prepojené žiadne externé účty.
link_account=Pripojiť účet
remove_account_link=Odstrániť prepojený účet
remove_account_link_desc=Odstránenie prepojeného účtu zruší jeho prístup k vášmu účtu Gitea. Pokračovať?

View File

@ -573,7 +573,6 @@ passcode_invalid=Koden är ogiltig. Försök igen.
manage_account_links=Hantera Länkade Konton
manage_account_links_desc=Dessa externa konton är länkade till ditt Gitea-konto.
account_links_not_available=Det finns för närvarande inga externa konton länkade till ditt Gitea-konto.
link_account=Länka konto
remove_account_link=Ta Bort Länkat Konto
remove_account_link_desc=Borttagning av länkade konton kommer häva dess åtkomst till ditt Gitea-konto. Vill du fortsätta?
@ -1182,7 +1181,6 @@ activity.title.releases_1=%d release
activity.title.releases_n=%d releaser
activity.title.releases_published_by=%s publicerad av %s
activity.published_release_label=Publicerad
activity.no_git_activity=Det har inte gjorts några commit under den här perioden.
activity.git_stats_exclude_merges=Exkludera merger,
activity.git_stats_author_1=%d författare
activity.git_stats_author_n=%d författare

View File

@ -117,7 +117,6 @@ files=Dosyalar
error=Hata
error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>.
error503=Sunucu isteğinizi gerçekleştiremedi. Lütfen daha sonra tekrar deneyin.
go_back=Geri Git
invalid_data=Geçersiz veri: %v
@ -131,7 +130,6 @@ unpin=Sabitlemeyi kaldır
artifacts=Yapılar
expired=Süresi doldu
confirm_delete_artifact=%s yapısını silmek istediğinizden emin misiniz?
archived=Arşivlenmiş
@ -171,7 +169,6 @@ internal_error_skipped=Dahili bir hata oluştu ama atlandı: %s
search=Ara...
type_tooltip=Arama türü
fuzzy=Bulanık
fuzzy_tooltip=Arama terimine benzeyen sonuçları da içer
words=Kelimeler
words_tooltip=Sadece arama terimi kelimeleriyle eşleşen sonuçları içer
regexp=Regexp
@ -506,7 +503,6 @@ activate_email.text=E posta adresinizi doğrulamak için lütfen <b>%s</b> için
register_notify=%s'ya Hoş Geldiniz
register_notify.title=%[1]s, %[2]s e hoşgeldiniz
register_notify.text_1=bu %s için kayıt onay e postanızdır!
register_notify.text_2=Artık %s kullanıcı adı ile oturum açabilirsiniz.
register_notify.text_3=Eğer bu hesap sizin için oluşturulduysa, lütfen önce <a href="%s">şifrenizi</a> ayarlayın.
@ -998,7 +994,6 @@ webauthn_alternative_tip=Ek bir kimlik doğrulama yöntemi ayarlamak isteyebilir
manage_account_links=Bağlı Hesapları Yönet
manage_account_links_desc=Bu harici hesaplar Gitea hesabınızla bağlantılı.
account_links_not_available=Şu anda Gitea hesabınıza bağlı harici bir hesap yok.
link_account=Hesap Bağla
remove_account_link=Bağlantılı Hesabı Kaldır
remove_account_link_desc=Bağlantılı bir hesabı kaldırmak, onunla Gitea hesabınıza erişimi iptal edecektir. Devam edilsin mi?
@ -1035,8 +1030,6 @@ new_repo_helper=Bir depo, sürüm geçmişi dahil tüm proje dosyalarını içer
owner=Sahibi
owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir.
repo_name=Depo İsmi
repo_name_profile_public_hint=.profile herkese açık organizasyonunuzun profiline herkesin görüntüleyebileceği bir README.md dosyası eklemek için kullanabileceğiniz özel bir depodur. Başlamak için herkese açık olduğundan ve profile dizininde README ile başladığınızdan emin olun.
repo_name_profile_private_hint=.profile-private organizasyonunuzun üye profiline sadece organizasyon üyelerinin görüntüleyebileceği bir README.md eklemek için kullanabileceğiniz özel bir depodur. Başlamak için özel olduğundan ve profil dizininde README ile başladığınızdan emin olun.
repo_name_helper=İyi depo isimleri kısa, akılda kalıcı ve benzersiz anahtar kelimeler kullanır. “.profile” veya .profile-private adlı bir depo, kullanıcı/kuruluş profili için bir README.md eklemek için kullanılabilir.
repo_size=Depo Boyutu
template=Şablon
@ -1058,7 +1051,6 @@ fork_branch=Çatala klonlanacak dal
all_branches=Tüm dallar
view_all_branches=Tüm dalları görüntüle
view_all_tags=Tüm etiketleri görüntüle
fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz.
fork.blocked_user=Depo çatallanamıyor, depo sahibi tarafından engellenmişsiniz.
use_template=Bu şablonu kullan
open_with_editor=%s ile aç
@ -1748,7 +1740,6 @@ issues.due_date_form=yyyy-aa-gg
issues.due_date_form_add=Bitiş tarihi ekle
issues.due_date_form_edit=Düzenle
issues.due_date_form_remove=Kaldır
issues.due_date_not_writer=Bir konunun bitiş tarihini güncellemek için bu depoda yazma iznine ihtiyacınız var.
issues.due_date_not_set=Bitiş tarihi atanmadı.
issues.due_date_added=bitiş tarihini %s olarak %s ekledi
issues.due_date_modified=bitiş tarihini %[2]s iken %[1]s olarak %[3]s değiştirdi
@ -2107,7 +2098,6 @@ activity.title.releases_1=%d Sürüm
activity.title.releases_n=%d Sürüm
activity.title.releases_published_by=%s %s tarafından yayınlandı
activity.published_release_label=Yayınlandı
activity.no_git_activity=Bu dönemde herhangi bir işleme yapılmamıştır.
activity.git_stats_exclude_merges=Birleştirmeler hariç,
activity.git_stats_author_1=%d yazar
activity.git_stats_author_n=%d yazar

View File

@ -116,7 +116,6 @@ files=Файли
error=Помилка
error404=Сторінка, яку ви намагаєтеся відкрити, <strong>не існує</strong> або <strong>ви не маєте права</strong> на її перегляд.
error503=Сервер не зміг виконати ваш запит. Будь ласка, спробуйте пізніше.
go_back=Назад
invalid_data=Недійсні дані: %v
@ -130,7 +129,6 @@ unpin=Відкріпити
artifacts=Артефакти
expired=Прострочено
confirm_delete_artifact=Справді видалити артефакт '%s' ?
archived=Архівовано
@ -168,7 +166,6 @@ internal_error_skipped=Трапилась внутрішня помилка, а
search=Пошук...
type_tooltip=Тип пошуку
fuzzy=Неточний
fuzzy_tooltip=Включити результати, які також близько відповідають пошуковому запиту
words=Слова
words_tooltip=Включати тільки результати, які відповідають пошуковим словам
regexp=Регулярний вираз
@ -497,7 +494,6 @@ activate_email.text=Будь ласка, перейдіть за наступн
register_notify=Ласкаво просимо до %s
register_notify.title=%[1]s, ласкаво просимо до %[2]s
register_notify.text_1=це лист з підтвердженням реєстрації на %s!
register_notify.text_2=Тепер ви можете увійти як: %s.
register_notify.text_3=Якщо цей обліковий запис створено для вас, будь ласка, спершу <a href="%s">встановіть пароль</a>.
@ -980,7 +976,6 @@ webauthn_alternative_tip=Ви можете налаштувати додатко
manage_account_links=Керування прив'язаними обліковими записами
manage_account_links_desc=Ці зовнішні облікові записи прив'язані до вашого облікового запису Gitea.
account_links_not_available=Наразі немає зовнішніх облікових записів, пов'язаних із вашим обліковим записом Gitea.
link_account=Прив'язати обліковий запис
remove_account_link=Видалити обліковий запис
remove_account_link_desc=Видалення пов'язаного облікового запису відкликає його доступ до вашого облікового запису Gitea. Продовжити?
@ -1016,7 +1011,6 @@ new_repo_helper=Сховище містить усі файли проєкту,
owner=Власник
owner_helper=Деякі організації можуть не відображатися у списку через обмеження на максимальну кількість сховищ.
repo_name=Назва сховища
repo_name_profile_public_hint=.profile - це спеціальне сховище, за допомогою якого ви можете додати README.md до профілю вашої публічної організації, який буде видимим для всіх. Переконайтеся, що він є публічним, та ініціалізуйте його за допомогою README у каталозі профілю.
repo_name_helper=У хороших назвах сховищ використовуються короткі ключові слова, які легко запам'ятовуються та є унікальними. Сховище з назвою «.profile» або «.profile-private» можна використовувати для додавання README.md для профілю користувача/організації.
repo_size=Розмір сховища
template=Шаблон
@ -1690,7 +1684,6 @@ issues.due_date_form=рррр-мм-дд
issues.due_date_form_add=Додати дату завершення
issues.due_date_form_edit=Редагувати
issues.due_date_form_remove=Видалити
issues.due_date_not_writer=Вам потрібен доступ на запис до цього сховища, щоб оновити дату виконання задачі.
issues.due_date_not_set=Термін виконання не встановлений.
issues.due_date_added=додав(ла) дату завершення %s %s
issues.due_date_modified=змінив(-ла) термін виконання з %[2]s на %[1]s %[3]s
@ -2024,7 +2017,6 @@ activity.title.releases_1=%d Реліз
activity.title.releases_n=%d Релізів
activity.title.releases_published_by=%s опубліковано %s
activity.published_release_label=Опубліковано
activity.no_git_activity=За цей період не було жодної активності комітів.
activity.git_stats_exclude_merges=Не враховуючи об'єднання,
activity.git_stats_author_1=%d автор
activity.git_stats_author_n=%d автори

View File

@ -117,7 +117,6 @@ files=文件
error=错误
error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您尚未被授权</strong> 查看该页面。
error503=服务器无法完成您的请求,请稍后重试。
go_back=返回
invalid_data=无效数据: %v
@ -131,7 +130,6 @@ unpin=取消置顶
artifacts=产物
expired=已过期
confirm_delete_artifact=您确定要删除产物「%s」吗
archived=已归档
@ -171,7 +169,6 @@ internal_error_skipped=发生内部错误,但已跳过: %s
search=搜索...
type_tooltip=搜索类型
fuzzy=模糊
fuzzy_tooltip=包含近似匹配搜索词的结果
words=
words_tooltip=仅包含匹配搜索词的结果
regexp=正则表达式
@ -506,7 +503,6 @@ activate_email.text=请在 <b>%s</b> 时间内,点击以下链接,以验证
register_notify=欢迎来到 %s
register_notify.title=%[1]s欢迎来到 %[2]s
register_notify.text_1=这是您的 %s 注册确认邮件
register_notify.text_2=您现在可以以用户名 %s 登录。
register_notify.text_3=如果此账户已为您创建,请先 <a href="%s">设置您的密码</a>。
@ -998,7 +994,6 @@ webauthn_alternative_tip=您可能想要配置额外的身份验证方法。
manage_account_links=管理绑定过的账号
manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。
account_links_not_available=当前没有与您的 Gitea 帐户绑定的外部帐户。
link_account=链接账户
remove_account_link=删除已绑定的账号
remove_account_link_desc=删除已绑定帐户将吊销其对您的 Gitea 帐户的访问权限。继续?
@ -1035,8 +1030,6 @@ new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史
owner=拥有者
owner_helper=由于最大仓库数量限制,一些组织可能不会显示在下拉列表中。
repo_name=仓库名称
repo_name_profile_public_hint=.profile 是一个特殊的仓库,您可以使用它将 README.md 添加到您的公共组织资料中,任何人都可以看到。请确保它是公开的,并使用个人资料目录中的 README 对其进行初始化以开始使用。
repo_name_profile_private_hint=.profile-private 是一个特殊的仓库,您可以使用它向您的组织成员个人资料添加 README.md仅对组织成员可见。请确保它是私有的并使用个人资料目录中的 README 对其进行初始化以开始使用。
repo_name_helper=理想的仓库名称应由简短、有意义和独特的关键词组成。「.profile」和「.profile-private」可用于为用户/组织添加 README.md。
repo_size=仓库大小
template=模板
@ -1058,7 +1051,6 @@ fork_branch=要克隆为派生的分支
all_branches=所有分支
view_all_branches=查看所有分支
view_all_tags=查看所有标签
fork_no_valid_owners=这个代码仓库无法被派生,因为没有有效的所有者。
fork.blocked_user=无法克隆仓库,因为您被仓库所有者屏蔽。
use_template=使用此模板
open_with_editor=用 %s 打开
@ -1756,7 +1748,6 @@ issues.due_date_form=yyyy年mm月dd日
issues.due_date_form_add=设置到期时间
issues.due_date_form_edit=编辑
issues.due_date_form_remove=删除
issues.due_date_not_writer=您需要该仓库的写权限才能更新工单的到期日期。
issues.due_date_not_set=未设置到期时间。
issues.due_date_added=于 %[2]s 设置到期时间为 %[1]s
issues.due_date_modified=将到期日从 %[2]s 修改为 %[1]s %[3]s
@ -2124,7 +2115,6 @@ activity.title.releases_1=%d 个发布
activity.title.releases_n=%d 个发布
activity.title.releases_published_by=%[2]s 发布了 %[1]s
activity.published_release_label=已发布
activity.no_git_activity=在此期间没有任何提交活动。
activity.git_stats_exclude_merges=排除合并后,
activity.git_stats_author_1=%d 位作者
activity.git_stats_author_n=%d 位作者

View File

@ -128,7 +128,6 @@ pin=固定
unpin=取消固定
artifacts=檔案或工件
confirm_delete_artifact=你確定要刪除這個檔案 '%s' 嗎?
archived=已封存
@ -167,7 +166,6 @@ internal_error_skipped=已略過內部錯誤:%s
search=搜尋…
type_tooltip=搜尋類型
fuzzy=模糊
fuzzy_tooltip=包含與搜尋詞接近的結果
exact=精確
exact_tooltip=只包含完全符合關鍵字的結果
repo_kind=搜尋儲存庫…
@ -487,7 +485,6 @@ activate_email.text=請在 <b>%s</b>內點擊下列連結以驗證您的電子
register_notify=歡迎來到 Gitea
register_notify.title=%[1]s歡迎來到 %[2]s
register_notify.text_1=這是您在 %s 的註冊確認信!
register_notify.text_2=您現在可以用帳號 %s 登入。
register_notify.text_3=如果這是由管理員為您建立的帳戶,請先<a href="%s">設定您的密碼</a>。
@ -973,7 +970,6 @@ webauthn_alternative_tip=您可能需要設定其他的驗證方法。
manage_account_links=管理已連結的帳戶
manage_account_links_desc=這些外部帳戶已連結到您的 Gitea 帳戶。
account_links_not_available=目前沒有連結到您的 Gitea 帳戶的外部帳戶
link_account=連結帳戶
remove_account_link=刪除已連結的帳戶
remove_account_link_desc=刪除連結帳戶將撤銷其對 Gitea 帳戶的存取權限。是否繼續?
@ -1030,7 +1026,6 @@ fork_branch=要克隆到 fork 的分支
all_branches=所有分支
view_all_branches=查看所有分支
view_all_tags=查看所有標籤
fork_no_valid_owners=此儲存庫無法 fork因為沒有有效的擁有者。
fork.blocked_user=無法 fork 儲存庫,因為您被儲存庫擁有者封鎖。
use_template=使用此範本
open_with_editor=以 %s 開啟
@ -1690,7 +1685,6 @@ issues.due_date_form=yyyy年mm月dd日
issues.due_date_form_add=新增截止日期
issues.due_date_form_edit=編輯
issues.due_date_form_remove=移除
issues.due_date_not_writer=您需要對此儲存庫的寫入權限才能更新問題的截止日期。
issues.due_date_not_set=未設定截止日期。
issues.due_date_added=新增了截止日期 %s %s
issues.due_date_modified=將截止日期從 %[2]s 修改為 %[1]s %[3]s
@ -2051,7 +2045,6 @@ activity.title.releases_1=%d 個版本
activity.title.releases_n=%d 個版本
activity.title.releases_published_by=%[2]s發布了 %[1]s
activity.published_release_label=已發布
activity.no_git_activity=期間內沒有任何提交動態
activity.git_stats_exclude_merges=不計合併,
activity.git_stats_author_1=%d 位作者
activity.git_stats_author_n=%d 位作者

View File

@ -240,7 +240,7 @@ func EditUser(ctx *context.APIContext) {
Description: optional.FromPtr(form.Description),
IsActive: optional.FromPtr(form.Active),
IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
AllowGitHook: optional.FromPtr(form.AllowGitHook),
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),

View File

@ -391,7 +391,7 @@ func Edit(ctx *context.APIContext) {
Description: optional.Some(form.Description),
Website: optional.Some(form.Website),
Location: optional.Some(form.Location),
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
}
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {

View File

@ -747,7 +747,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
// type: integer
// responses:
// "200":
// "$ref": "#/responses/ArtifactsList"
// "$ref": "#/responses/WorkflowRunsList"
// "400":
// "$ref": "#/responses/error"
// "404":

View File

@ -199,6 +199,9 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
AdminGroup: form.Oauth2AdminGroup,
GroupTeamMap: form.Oauth2GroupTeamMap,
GroupTeamMapRemoval: form.Oauth2GroupTeamMapRemoval,
SSHPublicKeyClaimName: form.Oauth2SSHPublicKeyClaimName,
FullNameClaimName: form.Oauth2FullNameClaimName,
}
}

View File

@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
)
@ -75,7 +74,7 @@ func TwoFactorPost(ctx *context.Context) {
}
if ctx.Session.Get("linkAccount") != nil {
err = externalaccount.LinkAccountFromStore(ctx, ctx.Session, u)
err = linkAccountFromContext(ctx, u)
if err != nil {
ctx.ServerError("UserSignIn", err)
return

View File

@ -329,6 +329,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
"twofaUid",
"twofaRemember",
"linkAccount",
"linkAccountData",
}, map[string]any{
session.KeyUID: u.ID,
session.KeyUname: u.Name,
@ -519,7 +520,7 @@ func SignUpPost(ctx *context.Context) {
Passwd: form.Password,
}
if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false) {
if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil) {
// error already handled
return
}
@ -530,22 +531,22 @@ func SignUpPost(ctx *context.Context) {
// createAndHandleCreatedUser calls createUserInContext and
// then handleUserCreated.
func createAndHandleCreatedUser(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool {
if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) {
func createAndHandleCreatedUser(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, possibleLinkAccountData *LinkAccountData) bool {
if !createUserInContext(ctx, tpl, form, u, overwrites, possibleLinkAccountData) {
return false
}
return handleUserCreated(ctx, u, gothUser)
return handleUserCreated(ctx, u, possibleLinkAccountData)
}
// createUserInContext creates a user and handles errors within a given context.
// Optionally a template can be specified.
func createUserInContext(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
// Optionally, a template can be specified.
func createUserInContext(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, possibleLinkAccountData *LinkAccountData) (ok bool) {
meta := &user_model.Meta{
InitialIP: ctx.RemoteAddr(),
InitialUserAgent: ctx.Req.UserAgent(),
}
if err := user_model.CreateUser(ctx, u, meta, overwrites); err != nil {
if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
if possibleLinkAccountData != nil && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
switch setting.OAuth2Client.AccountLinking {
case setting.OAuth2AccountLinkingAuto:
var user *user_model.User
@ -561,15 +562,15 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any,
}
// TODO: probably we should respect 'remember' user's choice...
linkAccount(ctx, user, *gothUser, true)
oauth2LinkAccount(ctx, user, possibleLinkAccountData, true)
return false // user is already created here, all redirects are handled
case setting.OAuth2AccountLinkingLogin:
showLinkingLogin(ctx, *gothUser)
showLinkingLogin(ctx, &possibleLinkAccountData.AuthSource, possibleLinkAccountData.GothUser)
return false // user will be created only after linking login
}
}
// handle error without template
// handle error without a template
if len(tpl) == 0 {
ctx.ServerError("CreateUser", err)
return false
@ -610,7 +611,7 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any,
// handleUserCreated does additional steps after a new user is created.
// It auto-sets admin for the only user, updates the optional external user and
// sends a confirmation email if required.
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
func handleUserCreated(ctx *context.Context, u *user_model.User, possibleLinkAccountData *LinkAccountData) (ok bool) {
// Auto-set admin for the only user.
hasUsers, err := user_model.HasUsers(ctx)
if err != nil {
@ -631,8 +632,8 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
}
// update external user information
if gothUser != nil {
if err := externalaccount.EnsureLinkExternalToUser(ctx, u, *gothUser); err != nil {
if possibleLinkAccountData != nil {
if err := externalaccount.EnsureLinkExternalToUser(ctx, possibleLinkAccountData.AuthSource.ID, u, possibleLinkAccountData.GothUser); err != nil {
log.Error("EnsureLinkExternalToUser failed: %v", err)
}
}

View File

@ -5,7 +5,6 @@ package auth
import (
"errors"
"fmt"
"net/http"
"strings"
@ -21,8 +20,6 @@ import (
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"github.com/markbates/goth"
)
var tplLinkAccount templates.TplName = "user/auth/link_account"
@ -52,28 +49,28 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
gothUser, ok := ctx.Session.Get("linkAccountGothUser").(goth.User)
linkAccountData := oauth2GetLinkAccountData(ctx)
// If you'd like to quickly debug the "link account" page layout, just uncomment the blow line
// Don't worry, when the below line exists, the lint won't pass: ineffectual assignment to gothUser (ineffassign)
// gothUser, ok = goth.User{Email: "invalid-email", Name: "."}, true // intentionally use invalid data to avoid pass the registration check
// linkAccountData = &LinkAccountData{authSource, gothUser} // intentionally use invalid data to avoid pass the registration check
if !ok {
if linkAccountData == nil {
// no account in session, so just redirect to the login page, then the user could restart the process
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}
if missingFields, ok := gothUser.RawData["__giteaAutoRegMissingFields"].([]string); ok {
ctx.Data["AutoRegistrationFailedPrompt"] = ctx.Tr("auth.oauth_callback_unable_auto_reg", gothUser.Provider, strings.Join(missingFields, ","))
if missingFields, ok := linkAccountData.GothUser.RawData["__giteaAutoRegMissingFields"].([]string); ok {
ctx.Data["AutoRegistrationFailedPrompt"] = ctx.Tr("auth.oauth_callback_unable_auto_reg", linkAccountData.GothUser.Provider, strings.Join(missingFields, ","))
}
uname, err := extractUserNameFromOAuth2(&gothUser)
uname, err := extractUserNameFromOAuth2(&linkAccountData.GothUser)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
email := gothUser.Email
email := linkAccountData.GothUser.Email
ctx.Data["user_name"] = uname
ctx.Data["email"] = email
@ -152,8 +149,8 @@ func LinkAccountPostSignIn(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
gothUser := ctx.Session.Get("linkAccountGothUser")
if gothUser == nil {
linkAccountData := oauth2GetLinkAccountData(ctx)
if linkAccountData == nil {
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
return
}
@ -169,11 +166,14 @@ func LinkAccountPostSignIn(ctx *context.Context) {
return
}
linkAccount(ctx, u, gothUser.(goth.User), signInForm.Remember)
oauth2LinkAccount(ctx, u, linkAccountData, signInForm.Remember)
}
func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, remember bool) {
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
func oauth2LinkAccount(ctx *context.Context, u *user_model.User, linkAccountData *LinkAccountData, remember bool) {
oauth2SignInSync(ctx, &linkAccountData.AuthSource, u, linkAccountData.GothUser)
if ctx.Written() {
return
}
// If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
@ -185,7 +185,7 @@ func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, r
return
}
err = externalaccount.LinkAccountToUser(ctx, u, gothUser)
err = externalaccount.LinkAccountToUser(ctx, linkAccountData.AuthSource.ID, u, linkAccountData.GothUser)
if err != nil {
ctx.ServerError("UserLinkAccount", err)
return
@ -243,17 +243,11 @@ func LinkAccountPostRegister(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
gothUserInterface := ctx.Session.Get("linkAccountGothUser")
if gothUserInterface == nil {
linkAccountData := oauth2GetLinkAccountData(ctx)
if linkAccountData == nil {
ctx.ServerError("UserSignUp", errors.New("not in LinkAccount session"))
return
}
gothUser, ok := gothUserInterface.(goth.User)
if !ok {
ctx.ServerError("UserSignUp", fmt.Errorf("session linkAccountGothUser type is %t but not goth.User", gothUserInterface))
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplLinkAccount)
return
@ -296,31 +290,33 @@ func LinkAccountPostRegister(ctx *context.Context) {
}
}
authSource, err := auth.GetActiveOAuth2SourceByName(ctx, gothUser.Provider)
if err != nil {
ctx.ServerError("CreateUser", err)
return
}
u := &user_model.User{
Name: form.UserName,
Email: form.Email,
Passwd: form.Password,
LoginType: auth.OAuth2,
LoginSource: authSource.ID,
LoginName: gothUser.UserID,
LoginSource: linkAccountData.AuthSource.ID,
LoginName: linkAccountData.GothUser.UserID,
}
if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, nil, &gothUser, false) {
if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, nil, linkAccountData) {
// error already handled
return
}
source := authSource.Cfg.(*oauth2.Source)
if err := syncGroupsToTeams(ctx, source, &gothUser, u); err != nil {
source := linkAccountData.AuthSource.Cfg.(*oauth2.Source)
if err := syncGroupsToTeams(ctx, source, &linkAccountData.GothUser, u); err != nil {
ctx.ServerError("SyncGroupsToTeams", err)
return
}
handleSignIn(ctx, u, false)
}
func linkAccountFromContext(ctx *context.Context, user *user_model.User) error {
linkAccountData := oauth2GetLinkAccountData(ctx)
if linkAccountData == nil {
return errors.New("not in LinkAccount session")
}
return externalaccount.LinkAccountToUser(ctx, linkAccountData.AuthSource.ID, user, linkAccountData.GothUser)
}

View File

@ -20,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
source_service "code.gitea.io/gitea/services/auth/source"
"code.gitea.io/gitea/services/auth/source/oauth2"
@ -35,9 +34,8 @@ import (
// SignInOAuth handles the OAuth2 login buttons
func SignInOAuth(ctx *context.Context) {
provider := ctx.PathParam("provider")
authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
authName := ctx.PathParam("provider")
authSource, err := auth.GetActiveOAuth2SourceByAuthName(ctx, authName)
if err != nil {
ctx.ServerError("SignIn", err)
return
@ -74,8 +72,6 @@ func SignInOAuth(ctx *context.Context) {
// SignInOAuthCallback handles the callback from the given provider
func SignInOAuthCallback(ctx *context.Context) {
provider := ctx.PathParam("provider")
if ctx.Req.FormValue("error") != "" {
var errorKeyValues []string
for k, vv := range ctx.Req.Form {
@ -88,7 +84,8 @@ func SignInOAuthCallback(ctx *context.Context) {
}
// first look if the provider is still active
authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
authName := ctx.PathParam("provider")
authSource, err := auth.GetActiveOAuth2SourceByAuthName(ctx, authName)
if err != nil {
ctx.ServerError("SignIn", err)
return
@ -133,7 +130,7 @@ func SignInOAuthCallback(ctx *context.Context) {
if u == nil {
if ctx.Doer != nil {
// attach user to the current signed-in user
err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser)
err = externalaccount.LinkAccountToUser(ctx, authSource.ID, ctx.Doer, gothUser)
if err != nil {
ctx.ServerError("UserLinkAccount", err)
return
@ -174,12 +171,11 @@ func SignInOAuthCallback(ctx *context.Context) {
gothUser.RawData = make(map[string]any)
}
gothUser.RawData["__giteaAutoRegMissingFields"] = missingFields
showLinkingLogin(ctx, gothUser)
showLinkingLogin(ctx, authSource, gothUser)
return
}
u = &user_model.User{
Name: uname,
FullName: gothUser.Name,
Email: gothUser.Email,
LoginType: auth.OAuth2,
LoginSource: authSource.ID,
@ -196,7 +192,11 @@ func SignInOAuthCallback(ctx *context.Context) {
u.IsAdmin = isAdmin.ValueOrDefault(user_service.UpdateOptionField[bool]{FieldValue: false}).FieldValue
u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted)
if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
linkAccountData := &LinkAccountData{*authSource, gothUser}
if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingDisabled {
linkAccountData = nil
}
if !createAndHandleCreatedUser(ctx, "", nil, u, overwriteDefault, linkAccountData) {
// error already handled
return
}
@ -207,7 +207,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
} else {
// no existing user is found, request attach or new account
showLinkingLogin(ctx, gothUser)
showLinkingLogin(ctx, authSource, gothUser)
return
}
}
@ -271,9 +271,22 @@ func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *g
return isAdmin, isRestricted
}
func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
type LinkAccountData struct {
AuthSource auth.Source
GothUser goth.User
}
func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
v, ok := ctx.Session.Get("linkAccountData").(LinkAccountData)
if !ok {
return nil
}
return &v
}
func showLinkingLogin(ctx *context.Context, authSource *auth.Source, gothUser goth.User) {
if err := updateSession(ctx, nil, map[string]any{
"linkAccountGothUser": gothUser,
"linkAccountData": LinkAccountData{*authSource, gothUser},
}); err != nil {
ctx.ServerError("updateSession", err)
return
@ -281,7 +294,7 @@ func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
ctx.Redirect(setting.AppSubURL + "/user/link_account")
}
func updateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {
func oauth2UpdateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {
if setting.OAuth2Client.UpdateAvatar && len(url) > 0 {
resp, err := http.Get(url)
if err == nil {
@ -299,11 +312,14 @@ func updateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {
}
}
func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) {
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_model.User, gothUser goth.User) {
oauth2SignInSync(ctx, authSource, u, gothUser)
if ctx.Written() {
return
}
needs2FA := false
if !source.TwoFactorShouldSkip() {
if !authSource.TwoFactorShouldSkip() {
_, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserSignIn", err)
@ -312,7 +328,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
needs2FA = err == nil
}
oauth2Source := source.Cfg.(*oauth2.Source)
oauth2Source := authSource.Cfg.(*oauth2.Source)
groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(oauth2Source.GroupTeamMap)
if err != nil {
ctx.ServerError("UnmarshalGroupTeamMapping", err)
@ -338,7 +354,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
}
}
if err := externalaccount.EnsureLinkExternalToUser(ctx, u, gothUser); err != nil {
if err := externalaccount.EnsureLinkExternalToUser(ctx, authSource.ID, u, gothUser); err != nil {
ctx.ServerError("EnsureLinkExternalToUser", err)
return
}

View File

@ -0,0 +1,88 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package auth
import (
"fmt"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/context"
"github.com/markbates/goth"
)
func oauth2SignInSync(ctx *context.Context, authSource *auth.Source, u *user_model.User, gothUser goth.User) {
oauth2UpdateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
oauth2Source, _ := authSource.Cfg.(*oauth2.Source)
if !authSource.IsOAuth2() || oauth2Source == nil {
ctx.ServerError("oauth2SignInSync", fmt.Errorf("source %s is not an OAuth2 source", gothUser.Provider))
return
}
// sync full name
fullNameKey := util.IfZero(oauth2Source.FullNameClaimName, "name")
fullName, _ := gothUser.RawData[fullNameKey].(string)
fullName = util.IfZero(fullName, gothUser.Name)
// need to update if the user has no full name set
shouldUpdateFullName := u.FullName == ""
// force to update if the attribute is set
shouldUpdateFullName = shouldUpdateFullName || oauth2Source.FullNameClaimName != ""
// only update if the full name is different
shouldUpdateFullName = shouldUpdateFullName && u.FullName != fullName
if shouldUpdateFullName {
u.FullName = fullName
if err := user_model.UpdateUserCols(ctx, u, "full_name"); err != nil {
log.Error("Unable to sync OAuth2 user full name %s: %v", gothUser.Provider, err)
}
}
err := oauth2UpdateSSHPubIfNeed(ctx, authSource, &gothUser, u)
if err != nil {
log.Error("Unable to sync OAuth2 SSH public key %s: %v", gothUser.Provider, err)
}
}
func oauth2SyncGetSSHKeys(source *oauth2.Source, gothUser *goth.User) ([]string, error) {
value, exists := gothUser.RawData[source.SSHPublicKeyClaimName]
if !exists {
return []string{}, nil
}
rawSlice, ok := value.([]any)
if !ok {
return nil, fmt.Errorf("invalid SSH public key value type: %T", value)
}
sshKeys := make([]string, 0, len(rawSlice))
for _, v := range rawSlice {
str, ok := v.(string)
if !ok {
return nil, fmt.Errorf("invalid SSH public key value item type: %T", v)
}
sshKeys = append(sshKeys, str)
}
return sshKeys, nil
}
func oauth2UpdateSSHPubIfNeed(ctx *context.Context, authSource *auth.Source, gothUser *goth.User, user *user_model.User) error {
oauth2Source, _ := authSource.Cfg.(*oauth2.Source)
if oauth2Source == nil || oauth2Source.SSHPublicKeyClaimName == "" {
return nil
}
sshKeys, err := oauth2SyncGetSSHKeys(oauth2Source, gothUser)
if err != nil {
return err
}
if !asymkey_model.SynchronizePublicKeys(ctx, user, authSource, sshKeys) {
return nil
}
return asymkey_service.RewriteAllPublicKeys(ctx)
}

View File

@ -361,7 +361,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
Email: form.Email,
Passwd: password,
}
if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false) {
if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil) {
// error already handled
return
}

View File

@ -15,7 +15,6 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
@ -150,7 +149,7 @@ func WebAuthnPasskeyLogin(ctx *context.Context) {
// Now handle account linking if that's requested
if ctx.Session.Get("linkAccount") != nil {
if err := externalaccount.LinkAccountFromStore(ctx, ctx.Session, user); err != nil {
if err := linkAccountFromContext(ctx, user); err != nil {
ctx.ServerError("LinkAccountFromStore", err)
return
}
@ -268,7 +267,7 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) {
// Now handle account linking if that's requested
if ctx.Session.Get("linkAccount") != nil {
if err := externalaccount.LinkAccountFromStore(ctx, ctx.Session, user); err != nil {
if err := linkAccountFromContext(ctx, user); err != nil {
ctx.ServerError("LinkAccountFromStore", err)
return
}

View File

@ -0,0 +1,73 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
git_model "code.gitea.io/gitea/models/git"
access_model "code.gitea.io/gitea/models/perm/access"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
type RecentBranchesPromptDataStruct struct {
RecentlyPushedNewBranches []*git_model.RecentlyPushedNewBranch
}
func prepareRecentlyPushedNewBranches(ctx *context.Context) {
if ctx.Doer == nil {
return
}
if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
log.Error("GetBaseRepo: %v", err)
return
}
opts := git_model.FindRecentlyPushedNewBranchesOptions{
Repo: ctx.Repo.Repository,
BaseRepo: ctx.Repo.Repository,
}
if ctx.Repo.Repository.IsFork {
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
}
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
if err != nil {
log.Error("GetUserRepoPermission: %v", err)
return
}
if !opts.Repo.CanContentChange() || !opts.BaseRepo.CanContentChange() {
return
}
if !opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) || !baseRepoPerm.CanRead(unit_model.TypePullRequests) {
return
}
var finalBranches []*git_model.RecentlyPushedNewBranch
branches, err := git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
if err != nil {
log.Error("FindRecentlyPushedNewBranches failed: %v", err)
return
}
for _, branch := range branches {
divergingInfo, err := repo_service.GetBranchDivergingInfo(ctx,
branch.BranchRepo, branch.BranchName, // "base" repo for diverging info
opts.BaseRepo, opts.BaseRepo.DefaultBranch, // "head" repo for diverging info
)
if err != nil {
log.Error("GetBranchDivergingInfo failed: %v", err)
continue
}
branchRepoHasNewCommits := divergingInfo.BaseHasNewCommits
baseRepoCommitsBehind := divergingInfo.HeadCommitsBehind
if branchRepoHasNewCommits || baseRepoCommitsBehind > 0 {
finalBranches = append(finalBranches, branch)
}
}
if len(finalBranches) > 0 {
ctx.Data["RecentBranchesPromptData"] = RecentBranchesPromptDataStruct{finalBranches}
}
}

View File

@ -767,6 +767,10 @@ func Issues(ctx *context.Context) {
}
ctx.Data["Title"] = ctx.Tr("repo.pulls")
ctx.Data["PageIsPullList"] = true
prepareRecentlyPushedNewBranches(ctx)
if ctx.Written() {
return
}
} else {
MustEnableIssues(ctx)
if ctx.Written() {

View File

@ -15,7 +15,6 @@ import (
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@ -196,56 +195,6 @@ func prepareUpstreamDivergingInfo(ctx *context.Context) {
ctx.Data["UpstreamDivergingInfo"] = upstreamDivergingInfo
}
func prepareRecentlyPushedNewBranches(ctx *context.Context) {
if ctx.Doer != nil {
if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
ctx.ServerError("GetBaseRepo", err)
return
}
opts := &git_model.FindRecentlyPushedNewBranchesOptions{
Repo: ctx.Repo.Repository,
BaseRepo: ctx.Repo.Repository,
}
if ctx.Repo.Repository.IsFork {
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
}
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
var finalBranches []*git_model.RecentlyPushedNewBranch
branches, err := git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
if err != nil {
log.Error("FindRecentlyPushedNewBranches failed: %v", err)
}
for _, branch := range branches {
divergingInfo, err := repo_service.GetBranchDivergingInfo(ctx,
branch.BranchRepo, branch.BranchName, // "base" repo for diverging info
opts.BaseRepo, opts.BaseRepo.DefaultBranch, // "head" repo for diverging info
)
if err != nil {
log.Error("GetBranchDivergingInfo failed: %v", err)
continue
}
branchRepoHasNewCommits := divergingInfo.BaseHasNewCommits
baseRepoCommitsBehind := divergingInfo.HeadCommitsBehind
if branchRepoHasNewCommits || baseRepoCommitsBehind > 0 {
finalBranches = append(finalBranches, branch)
}
}
ctx.Data["RecentlyPushedNewBranches"] = finalBranches
}
}
}
func updateContextRepoEmptyAndStatus(ctx *context.Context, empty bool, status repo_model.RepositoryStatus) {
if ctx.Repo.Repository.IsEmpty == empty && ctx.Repo.Repository.Status == status {
return

View File

@ -4,10 +4,8 @@
package user
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
activities_model "code.gitea.io/gitea/models/activities"
@ -34,58 +32,42 @@ const (
tplNotificationSubscriptions templates.TplName = "user/notification/notification_subscriptions"
)
// Notifications is the notifications page
// Notifications is the notification list page
func Notifications(ctx *context.Context) {
getNotifications(ctx)
prepareUserNotificationsData(ctx)
if ctx.Written() {
return
}
if ctx.FormBool("div-only") {
ctx.Data["SequenceNumber"] = ctx.FormString("sequence-number")
ctx.HTML(http.StatusOK, tplNotificationDiv)
return
}
ctx.HTML(http.StatusOK, tplNotification)
}
func getNotifications(ctx *context.Context) {
var (
keyword = ctx.FormTrim("q")
status activities_model.NotificationStatus
page = ctx.FormInt("page")
perPage = ctx.FormInt("perPage")
)
if page < 1 {
page = 1
}
if perPage < 1 {
perPage = 20
}
switch keyword {
case "read":
status = activities_model.NotificationStatusRead
default:
status = activities_model.NotificationStatusUnread
}
func prepareUserNotificationsData(ctx *context.Context) {
pageType := ctx.FormString("type", ctx.FormString("q")) // "q" is the legacy query parameter for "page type"
page := max(1, ctx.FormInt("page"))
perPage := util.IfZero(ctx.FormInt("perPage"), 20) // this value is never used or exposed ....
queryStatus := util.Iif(pageType == "read", activities_model.NotificationStatusRead, activities_model.NotificationStatusUnread)
total, err := db.Count[activities_model.Notification](ctx, activities_model.FindNotificationOptions{
UserID: ctx.Doer.ID,
Status: []activities_model.NotificationStatus{status},
Status: []activities_model.NotificationStatus{queryStatus},
})
if err != nil {
ctx.ServerError("ErrGetNotificationCount", err)
return
}
// redirect to last page if request page is more than total pages
pager := context.NewPagination(int(total), perPage, page, 5)
if pager.Paginater.Current() < page {
ctx.Redirect(fmt.Sprintf("%s/notifications?q=%s&page=%d", setting.AppSubURL, url.QueryEscape(ctx.FormString("q")), pager.Paginater.Current()))
return
// use the last page if the requested page is more than total pages
page = pager.Paginater.Current()
pager = context.NewPagination(int(total), perPage, page, 5)
}
statuses := []activities_model.NotificationStatus{status, activities_model.NotificationStatusPinned}
statuses := []activities_model.NotificationStatus{queryStatus, activities_model.NotificationStatusPinned}
nls, err := db.Find[activities_model.Notification](ctx, activities_model.FindNotificationOptions{
ListOptions: db.ListOptions{
PageSize: perPage,
@ -142,51 +124,37 @@ func getNotifications(ctx *context.Context) {
}
ctx.Data["Title"] = ctx.Tr("notifications")
ctx.Data["Keyword"] = keyword
ctx.Data["Status"] = status
ctx.Data["PageType"] = pageType
ctx.Data["Notifications"] = notifications
ctx.Data["Link"] = setting.AppSubURL + "/notifications"
ctx.Data["SequenceNumber"] = ctx.FormString("sequence-number")
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
}
// NotificationStatusPost is a route for changing the status of a notification
func NotificationStatusPost(ctx *context.Context) {
var (
notificationID = ctx.FormInt64("notification_id")
statusStr = ctx.FormString("status")
status activities_model.NotificationStatus
)
switch statusStr {
case "read":
status = activities_model.NotificationStatusRead
case "unread":
status = activities_model.NotificationStatusUnread
case "pinned":
status = activities_model.NotificationStatusPinned
notificationID := ctx.FormInt64("notification_id")
var newStatus activities_model.NotificationStatus
switch ctx.FormString("notification_action") {
case "mark_as_read":
newStatus = activities_model.NotificationStatusRead
case "mark_as_unread":
newStatus = activities_model.NotificationStatusUnread
case "pin":
newStatus = activities_model.NotificationStatusPinned
default:
ctx.ServerError("InvalidNotificationStatus", errors.New("Invalid notification status"))
return
return // ignore user's invalid input
}
if _, err := activities_model.SetNotificationStatus(ctx, notificationID, ctx.Doer, status); err != nil {
if _, err := activities_model.SetNotificationStatus(ctx, notificationID, ctx.Doer, newStatus); err != nil {
ctx.ServerError("SetNotificationStatus", err)
return
}
if !ctx.FormBool("noredirect") {
url := fmt.Sprintf("%s/notifications?page=%s", setting.AppSubURL, url.QueryEscape(ctx.FormString("page")))
ctx.Redirect(url, http.StatusSeeOther)
}
getNotifications(ctx)
prepareUserNotificationsData(ctx)
if ctx.Written() {
return
}
ctx.Data["Link"] = setting.AppSubURL + "/notifications"
ctx.Data["SequenceNumber"] = ctx.Req.PostFormValue("sequence-number")
ctx.HTML(http.StatusOK, tplNotificationDiv)
}

View File

@ -35,7 +35,7 @@ const (
// Account renders change user's password, user's email and user suicide page
func Account(ctx *context.Context) {
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials, setting.UserFeatureDeletion) && !setting.Service.EnableNotifyMail {
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials, setting.UserFeatureDeletion) {
ctx.NotFound(errors.New("account setting are not allowed to be changed"))
return
}
@ -43,7 +43,6 @@ func Account(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings.account")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.Doer.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
loadAccountData(ctx)
@ -61,7 +60,6 @@ func AccountPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.Doer.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
if ctx.HasError() {
loadAccountData(ctx)
@ -112,7 +110,6 @@ func EmailPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.Doer.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
// Make email address primary.
if ctx.FormString("_method") == "PRIMARY" {
@ -172,30 +169,6 @@ func EmailPost(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
}
// Set Email Notification Preference
if ctx.FormString("_method") == "NOTIFICATION" {
preference := ctx.FormString("preference")
if !(preference == user_model.EmailNotificationsEnabled ||
preference == user_model.EmailNotificationsOnMention ||
preference == user_model.EmailNotificationsDisabled ||
preference == user_model.EmailNotificationsAndYourOwn) {
log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
return
}
opts := &user.UpdateOptions{
EmailNotificationsPreference: optional.Some(preference),
}
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
log.Error("Set Email Notifications failed: %v", err)
ctx.ServerError("UpdateUser", err)
return
}
log.Trace("Email notifications preference made %s: %s", preference, ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
}
if ctx.HasError() {
loadAccountData(ctx)
@ -267,7 +240,6 @@ func DeleteAccount(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.Doer.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
switch {
@ -342,7 +314,6 @@ func loadAccountData(ctx *context.Context) {
emails[i] = &email
}
ctx.Data["Emails"] = emails
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
ctx.Data["ActivationsPending"] = pendingActivation
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)

View File

@ -0,0 +1,62 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"errors"
"net/http"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/user"
)
const tplSettingsNotifications templates.TplName = "user/settings/notifications"
// Notifications render user's notifications settings
func Notifications(ctx *context.Context) {
if !setting.Service.EnableNotifyMail {
ctx.NotFound(nil)
return
}
ctx.Data["Title"] = ctx.Tr("notifications")
ctx.Data["PageIsSettingsNotifications"] = true
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
ctx.HTML(http.StatusOK, tplSettingsNotifications)
}
// NotificationsEmailPost set user's email notification preference
func NotificationsEmailPost(ctx *context.Context) {
if !setting.Service.EnableNotifyMail {
ctx.NotFound(nil)
return
}
preference := ctx.FormString("preference")
if !(preference == user_model.EmailNotificationsEnabled ||
preference == user_model.EmailNotificationsOnMention ||
preference == user_model.EmailNotificationsDisabled ||
preference == user_model.EmailNotificationsAndYourOwn) {
log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
return
}
opts := &user.UpdateOptions{
EmailNotificationsPreference: optional.Some(preference),
}
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
log.Error("Set Email Notifications failed: %v", err)
ctx.ServerError("UpdateUser", err)
return
}
log.Trace("Email notifications preference made %s: %s", preference, ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success"))
ctx.Redirect(setting.AppSubURL + "/user/settings/notifications")
}

View File

@ -595,6 +595,10 @@ func registerWebRoutes(m *web.Router) {
m.Post("/hidden_comments", user_setting.UpdateUserHiddenComments)
m.Post("/theme", web.Bind(forms.UpdateThemeForm{}), user_setting.UpdateUIThemePost)
})
m.Group("/notifications", func() {
m.Get("", user_setting.Notifications)
m.Post("/email", user_setting.NotificationsEmailPost)
})
m.Group("/security", func() {
m.Get("", security.Security)
m.Group("/two_factor", func() {
@ -682,7 +686,7 @@ func registerWebRoutes(m *web.Router) {
m.Get("", user_setting.BlockedUsers)
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
})
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled))
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled, "EnableNotifyMail", setting.Service.EnableNotifyMail))
m.Group("/user", func() {
m.Get("/activate", auth.Activate)

View File

@ -5,6 +5,7 @@ package agit
import (
"context"
"encoding/base64"
"fmt"
"os"
"strings"
@ -18,17 +19,30 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
)
func parseAgitPushOptionValue(s string) string {
if base64Value, ok := strings.CutPrefix(s, "{base64}"); ok {
decoded, err := base64.StdEncoding.DecodeString(base64Value)
return util.Iif(err == nil, string(decoded), s)
}
return s
}
// ProcReceive handle proc receive work
func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) {
results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs))
forcePush := opts.GitPushOptions.Bool(private.GitPushOptionForcePush)
topicBranch := opts.GitPushOptions["topic"]
title := strings.TrimSpace(opts.GitPushOptions["title"])
description := strings.TrimSpace(opts.GitPushOptions["description"])
// some options are base64-encoded with "{base64}" prefix if they contain new lines
// other agit push options like "issue", "reviewer" and "cc" are not supported
title := parseAgitPushOptionValue(opts.GitPushOptions["title"])
description := parseAgitPushOptionValue(opts.GitPushOptions["description"])
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
userName := strings.ToLower(opts.UserName)

View File

@ -0,0 +1,16 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package agit
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseAgitPushOptionValue(t *testing.T) {
assert.Equal(t, "a", parseAgitPushOptionValue("a"))
assert.Equal(t, "a", parseAgitPushOptionValue("{base64}YQ=="))
assert.Equal(t, "{base64}invalid value", parseAgitPushOptionValue("{base64}invalid value"))
}

View File

@ -24,47 +24,43 @@ import (
// ParseCommitWithSignature check if signature is good against keystore.
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *asymkey_model.CommitVerification {
var committer *user_model.User
if c.Committer != nil {
var err error
// Find Committer account
committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
if err != nil { // Skipping not user for committer
committer = &user_model.User{
Name: c.Committer.Name,
Email: c.Committer.Email,
}
// We can expect this to often be an ErrUserNotExist. in the case
// it is not, however, it is important to log it.
if !user_model.IsErrUserNotExist(err) {
log.Error("GetUserByEmail: %v", err)
return &asymkey_model.CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.no_committer_account",
}
}
committer, err := user_model.GetUserByEmail(ctx, c.Committer.Email)
if err != nil && !user_model.IsErrUserNotExist(err) {
log.Error("GetUserByEmail: %v", err)
return &asymkey_model.CommitVerification{
Verified: false,
Reason: "gpg.error.no_committer_account", // this error is not right, but such error should seldom happen
}
}
return ParseCommitWithSignatureCommitter(ctx, c, committer)
}
// ParseCommitWithSignatureCommitter parses a commit's GPG or SSH signature.
// If the commit is singed by an instance key, then committer can be nil.
// If the signature exists, even if committer is nil, the returned CommittingUser will be a non-nil fake user.
func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification {
// If no signature just report the committer
// If no signature, just report the committer
if c.Signature == nil {
return &asymkey_model.CommitVerification{
CommittingUser: committer,
Verified: false, // Default value
Reason: "gpg.error.not_signed_commit", // Default value
Verified: false,
Reason: "gpg.error.not_signed_commit",
}
}
// If this a SSH signature handle it differently
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
return ParseCommitWithSSHSignature(ctx, c, committer)
// to support instance key, we need a fake committer user (not really needed, but legacy code accesses the committer without nil-check)
if committer == nil {
committer = &user_model.User{
Name: c.Committer.Name,
Email: c.Committer.Email,
}
}
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
return parseCommitWithSSHSignature(ctx, c, committer)
}
return parseCommitWithGPGSignature(ctx, c, committer)
}
func parseCommitWithGPGSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification {
// Parsing signature
sig, err := asymkey_model.ExtractSignature(c.Signature.Signature)
if err != nil { // Skipping failed to extract sign
@ -165,7 +161,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi
}
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
} else if commitVerification := VerifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
} else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
if commitVerification.Reason == asymkey_model.BadSignature {
defaultReason = asymkey_model.BadSignature
} else {
@ -180,7 +176,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi
} else if defaultGPGSettings == nil {
log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String())
} else if defaultGPGSettings.Sign {
if commitVerification := VerifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
if commitVerification.Reason == asymkey_model.BadSignature {
defaultReason = asymkey_model.BadSignature
} else {
@ -295,7 +291,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
}
}
func VerifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *asymkey_model.CommitVerification {
func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *asymkey_model.CommitVerification {
// First try to find the key in the db
if commitVerification := HashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
return commitVerification
@ -375,8 +371,8 @@ func verifySSHCommitVerificationByInstanceKey(c *git.Commit, committerUser, sign
return verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, sshPubKey, committerUser, signerUser, committerGitEmail)
}
// ParseCommitWithSSHSignature check if signature is good against keystore.
func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committerUser *user_model.User) *asymkey_model.CommitVerification {
// parseCommitWithSSHSignature check if signature is good against keystore.
func parseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committerUser *user_model.User) *asymkey_model.CommitVerification {
// Now try to associate the signature with the committer, if present
if committerUser.ID != 0 {
keys, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{

View File

@ -41,7 +41,7 @@ Initial commit with signed file
Name: "User Two",
Email: "user2@example.com",
}
ret := ParseCommitWithSSHSignature(t.Context(), commit, committingUser)
ret := parseCommitWithSSHSignature(t.Context(), commit, committingUser)
require.NotNil(t, ret)
assert.True(t, ret.Verified)
assert.False(t, ret.Warning)

View File

@ -27,6 +27,7 @@ type Provider interface {
DisplayName() string
IconHTML(size int) template.HTML
CustomURLSettings() *CustomURLSettings
SupportSSHPublicKey() bool
}
// GothProviderCreator provides a function to create a goth.Provider

View File

@ -14,6 +14,13 @@ import (
type BaseProvider struct {
name string
displayName string
// TODO: maybe some providers also support SSH public keys, then they can set this to true
supportSSHPublicKey bool
}
func (b *BaseProvider) SupportSSHPublicKey() bool {
return b.supportSSHPublicKey
}
// Name provides the technical name for this provider

View File

@ -17,6 +17,10 @@ import (
// OpenIDProvider is a GothProvider for OpenID
type OpenIDProvider struct{}
func (o *OpenIDProvider) SupportSSHPublicKey() bool {
return true
}
// Name provides the technical name for this provider
func (o *OpenIDProvider) Name() string {
return "openidConnect"

View File

@ -27,6 +27,9 @@ type Source struct {
GroupTeamMap string
GroupTeamMapRemoval bool
RestrictedGroup string
SSHPublicKeyClaimName string
FullNameClaimName string
}
// FromDB fills up an OAuth2Config from serialized format.

View File

@ -251,7 +251,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
RepoTransfer: transfer,
Topics: util.SliceNilAsEmpty(repo.Topics),
ObjectFormatName: repo.ObjectFormatName,
Licenses: repoLicenses.StringList(),
Licenses: util.SliceNilAsEmpty(repoLicenses.StringList()),
}
}

View File

@ -1,30 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package externalaccount
import (
"context"
"errors"
user_model "code.gitea.io/gitea/models/user"
"github.com/markbates/goth"
)
// Store represents a thing that stores things
type Store interface {
Get(any) any
Set(any, any) error
Release() error
}
// LinkAccountFromStore links the provided user with a stored external user
func LinkAccountFromStore(ctx context.Context, store Store, user *user_model.User) error {
gothUser := store.Get("linkAccountGothUser")
if gothUser == nil {
return errors.New("not in LinkAccount session")
}
return LinkAccountToUser(ctx, user, gothUser.(goth.User))
}

View File

@ -8,7 +8,6 @@ import (
"strconv"
"strings"
"code.gitea.io/gitea/models/auth"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
@ -17,15 +16,11 @@ import (
"github.com/markbates/goth"
)
func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser goth.User) (*user_model.ExternalLoginUser, error) {
authSource, err := auth.GetActiveOAuth2SourceByName(ctx, gothUser.Provider)
if err != nil {
return nil, err
}
func toExternalLoginUser(authSourceID int64, user *user_model.User, gothUser goth.User) *user_model.ExternalLoginUser {
return &user_model.ExternalLoginUser{
ExternalID: gothUser.UserID,
UserID: user.ID,
LoginSourceID: authSource.ID,
LoginSourceID: authSourceID,
RawData: gothUser.RawData,
Provider: gothUser.Provider,
Email: gothUser.Email,
@ -40,15 +35,12 @@ func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser go
AccessTokenSecret: gothUser.AccessTokenSecret,
RefreshToken: gothUser.RefreshToken,
ExpiresAt: gothUser.ExpiresAt,
}, nil
}
}
// LinkAccountToUser link the gothUser to the user
func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
if err != nil {
return err
}
func LinkAccountToUser(ctx context.Context, authSourceID int64, user *user_model.User, gothUser goth.User) error {
externalLoginUser := toExternalLoginUser(authSourceID, user, gothUser)
if err := user_model.LinkExternalToUser(ctx, user, externalLoginUser); err != nil {
return err
@ -72,12 +64,8 @@ func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth
}
// EnsureLinkExternalToUser link the gothUser to the user
func EnsureLinkExternalToUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
if err != nil {
return err
}
func EnsureLinkExternalToUser(ctx context.Context, authSourceID int64, user *user_model.User, gothUser goth.User) error {
externalLoginUser := toExternalLoginUser(authSourceID, user, gothUser)
return user_model.EnsureLinkExternalToUser(ctx, externalLoginUser)
}

View File

@ -18,45 +18,54 @@ type AuthenticationForm struct {
Type int `binding:"Range(2,7)"`
Name string `binding:"Required;MaxSize(30)"`
TwoFactorPolicy string
IsActive bool
IsSyncEnabled bool
Host string
Port int
BindDN string
BindPassword string
UserBase string
UserDN string
AttributeUsername string
AttributeName string
AttributeSurname string
AttributeMail string
AttributeSSHPublicKey string
AttributeAvatar string
AttributesInBind bool
UsePagedSearch bool
SearchPageSize int
Filter string
AdminFilter string
GroupsEnabled bool
GroupDN string
GroupFilter string
GroupMemberUID string
UserUID string
RestrictedFilter string
AllowDeactivateAll bool
IsActive bool
IsSyncEnabled bool
SMTPAuth string
SMTPHost string
SMTPPort int
AllowedDomains string
SecurityProtocol int `binding:"Range(0,2)"`
TLS bool
SkipVerify bool
HeloHostname string
DisableHelo bool
ForceSMTPS bool
PAMServiceName string
PAMEmailDomain string
// LDAP
Host string
Port int
BindDN string
BindPassword string
UserBase string
UserDN string
AttributeUsername string
AttributeName string
AttributeSurname string
AttributeMail string
AttributeSSHPublicKey string
AttributeAvatar string
AttributesInBind bool
UsePagedSearch bool
SearchPageSize int
Filter string
AdminFilter string
GroupsEnabled bool
GroupDN string
GroupFilter string
GroupMemberUID string
UserUID string
RestrictedFilter string
AllowDeactivateAll bool
GroupTeamMap string `binding:"ValidGroupTeamMap"`
GroupTeamMapRemoval bool
// SMTP
SMTPAuth string
SMTPHost string
SMTPPort int
AllowedDomains string
SecurityProtocol int `binding:"Range(0,2)"`
TLS bool
SkipVerify bool
HeloHostname string
DisableHelo bool
ForceSMTPS bool
// PAM
PAMServiceName string
PAMEmailDomain string
// Oauth2 & OIDC
Oauth2Provider string
Oauth2Key string
Oauth2Secret string
@ -76,13 +85,15 @@ type AuthenticationForm struct {
Oauth2RestrictedGroup string
Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"`
Oauth2GroupTeamMapRemoval bool
SSPIAutoCreateUsers bool
SSPIAutoActivateUsers bool
SSPIStripDomainNames bool
SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"`
SSPIDefaultLanguage string
GroupTeamMap string `binding:"ValidGroupTeamMap"`
GroupTeamMapRemoval bool
Oauth2SSHPublicKeyClaimName string
Oauth2FullNameClaimName string
// SSPI
SSPIAutoCreateUsers bool
SSPIAutoActivateUsers bool
SSPIStripDomainNames bool
SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"`
SSPIDefaultLanguage string
}
// Validate validates fields

View File

@ -35,13 +35,6 @@ func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository,
for _, c := range oldCommits {
committerUser := emailUsers.GetByEmail(c.Committer.Email) // FIXME: why ValidateCommitsWithEmails uses "Author", but ParseCommitsWithSignature uses "Committer"?
if committerUser == nil {
committerUser = &user_model.User{
Name: c.Committer.Name,
Email: c.Committer.Email,
}
}
signCommit := &asymkey_model.SignCommit{
UserCommit: c,
Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committerUser),

View File

@ -301,19 +301,30 @@
<input id="oauth2_tenant" name="oauth2_tenant" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.Tenant}}{{end}}">
</div>
{{range .OAuth2Providers}}{{if .CustomURLSettings}}
{{range .OAuth2Providers}}
<input id="{{.Name}}_SupportSSHPublicKey" value="{{.SupportSSHPublicKey}}" type="hidden">
{{if .CustomURLSettings}}
<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true">
<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden">
<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden">
<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden">
<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden">
<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden">
{{end}}{{end}}
{{end}}
{{end}}
<div class="field">
<label for="oauth2_scopes">{{ctx.Locale.Tr "admin.auths.oauth2_scopes"}}</label>
<input id="oauth2_scopes" name="oauth2_scopes" value="{{if $cfg.Scopes}}{{StringUtils.Join $cfg.Scopes ","}}{{end}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "admin.auths.oauth2_full_name_claim_name"}}</label>
<input name="oauth2_full_name_claim_name" value="{{$cfg.FullNameClaimName}}" placeholder="name">
</div>
<div class="field oauth2_ssh_public_key_claim_name">
<label>{{ctx.Locale.Tr "admin.auths.oauth2_ssh_public_key_claim_name"}}</label>
<input name="oauth2_ssh_public_key_claim_name" value="{{$cfg.SSHPublicKeyClaimName}}" placeholder="sshpubkey">
</div>
<div class="field">
<label for="oauth2_required_claim_name">{{ctx.Locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{$cfg.RequiredClaimName}}">

View File

@ -63,19 +63,31 @@
<input id="oauth2_tenant" name="oauth2_tenant" value="{{.oauth2_tenant}}">
</div>
{{range .OAuth2Providers}}{{if .CustomURLSettings}}
{{range .OAuth2Providers}}
<input id="{{.Name}}_SupportSSHPublicKey" value="{{.SupportSSHPublicKey}}" type="hidden">
{{if .CustomURLSettings}}
<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true">
<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden">
<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden">
<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden">
<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden">
<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden">
{{end}}{{end}}
{{end}}
{{end}}
<div class="field">
<label for="oauth2_scopes">{{ctx.Locale.Tr "admin.auths.oauth2_scopes"}}</label>
<input id="oauth2_scopes" name="oauth2_scopes" value="{{.oauth2_scopes}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "admin.auths.oauth2_full_name_claim_name"}}</label>
<input name="oauth2_full_name_claim_name" value="{{.oauth2_full_name_claim_name}}" placeholder="name">
</div>
<div class="field oauth2_ssh_public_key_claim_name">
<label>{{ctx.Locale.Tr "admin.auths.oauth2_ssh_public_key_claim_name"}}</label>
<input name="oauth2_ssh_public_key_claim_name" value="{{.oauth2_ssh_public_key_claim_name}}" placeholder="sshpubkey">
</div>
<div class="field">
<label for="oauth2_required_claim_name">{{ctx.Locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{.oauth2_required_claim_name}}">

View File

@ -1,12 +1,18 @@
{{range .RecentlyPushedNewBranches}}
<div class="ui positive message tw-flex tw-items-center tw-gap-2">
<div class="tw-flex-1 tw-break-anywhere">
{{$timeSince := DateUtils.TimeSince .CommitTime}}
{{$branchLink := HTMLFormat `<a href="%s">%s</a>` .BranchLink .BranchDisplayName}}
{{/* Template Attributes:
* RecentBranchesPromptData
*/}}
{{$data := .RecentBranchesPromptData}}
{{if $data}}
{{range $recentBranch := $data.RecentlyPushedNewBranches}}
<div class="ui positive message flex-text-block">
<div class="tw-flex-1">
{{$timeSince := DateUtils.TimeSince $recentBranch.CommitTime}}
{{$branchLink := HTMLFormat `<a href="%s">%s</a>` $recentBranch.BranchLink .BranchDisplayName}}
{{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $branchLink $timeSince}}
</div>
<a role="button" class="ui compact green button tw-m-0" href="{{QueryBuild .BranchCompareURL "expand" 1}}">
<a role="button" class="ui compact green button" href="{{QueryBuild $recentBranch.BranchCompareURL "expand" 1}}">
{{ctx.Locale.Tr "repo.pulls.compare_changes"}}
</a>
</div>
{{end}}
{{end}}

View File

@ -147,7 +147,7 @@
<div class="flex-text-inline">
{{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}}
<span class="text grey">{{ctx.Locale.Tr "repo.diff.committed_by"}}</span>
{{if ne .Verification.CommittingUser.ID 0}}
{{if and .Verification.CommittingUser .Verification.CommittingUser.ID}}
{{ctx.AvatarUtils.Avatar .Verification.CommittingUser 20}}
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a>
{{else}}

View File

@ -16,7 +16,7 @@
<td class="author">
<div class="tw-flex">
{{$userName := .Author.Name}}
{{if and .User (gt .User.ID 0)}} /* User with id == 0 is a fake user from git author */
{{if .User}}
{{if and .User.FullName DefaultShowFullName}}
{{$userName = .User.FullName}}
{{end}}

View File

@ -15,7 +15,7 @@
</div>
{{end}}
{{template "repo/code/recently_pushed_new_branches" .}}
{{template "repo/code/recently_pushed_new_branches" dict "RecentBranchesPromptData" .RecentBranchesPromptData}}
<div class="{{Iif $showSidebar "repo-grid-filelist-sidebar" "repo-grid-filelist-only"}}">
<div class="repo-home-filelist">

View File

@ -4,6 +4,8 @@
<div class="ui container">
{{template "base/alert" .}}
{{template "repo/code/recently_pushed_new_branches" dict "RecentBranchesPromptData" .RecentBranchesPromptData}}
{{if .PinnedIssues}}
<div id="issue-pins" {{if .IsRepoAdmin}}data-is-repo-admin{{end}}>
{{range .PinnedIssues}}

View File

@ -14,7 +14,7 @@
</div>
{{end}}
{{template "repo/code/recently_pushed_new_branches" .}}
{{template "repo/code/recently_pushed_new_branches" dict "RecentBranchesPromptData" .RecentBranchesPromptData}}
<div class="repo-view-container">
<div class="tw-flex tw-flex-col repo-view-file-tree-container not-mobile {{if not .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}" {{if .IsSigned}}data-user-is-signed-in{{end}}>

View File

@ -5165,7 +5165,7 @@
],
"responses": {
"200": {
"$ref": "#/responses/ArtifactsList"
"$ref": "#/responses/WorkflowRunsList"
},
"400": {
"$ref": "#/responses/error"

View File

@ -1,124 +1,96 @@
<div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-sequence-number="{{.SequenceNumber}}">
<div class="ui container">
{{$statusUnread := 1}}{{$statusRead := 2}}{{$statusPinned := 3}}
{{$notificationUnreadCount := call .PageGlobalData.GetNotificationUnreadCount}}
<div class="tw-flex tw-items-center tw-justify-between tw-mb-[--page-spacing]">
{{$pageTypeIsRead := eq $.PageType "read"}}
<div class="flex-text-block tw-justify-between tw-mb-[--page-spacing]">
<div class="small-menu-items ui compact tiny menu">
<a class="{{if eq .Status 1}}active {{end}}item" href="{{AppSubUrl}}/notifications?q=unread">
<a class="{{if not $pageTypeIsRead}}active{{end}} item" href="{{AppSubUrl}}/notifications?type=unread">
{{ctx.Locale.Tr "notification.unread"}}
<div class="notifications-unread-count ui label {{if not $notificationUnreadCount}}tw-hidden{{end}}">{{$notificationUnreadCount}}</div>
</a>
<a class="{{if eq .Status 2}}active {{end}}item" href="{{AppSubUrl}}/notifications?q=read">
<a class="{{if $pageTypeIsRead}}active{{end}} item" href="{{AppSubUrl}}/notifications?type=read">
{{ctx.Locale.Tr "notification.read"}}
</a>
</div>
{{if and (eq .Status 1)}}
{{if and (not $pageTypeIsRead) $notificationUnreadCount}}
<form action="{{AppSubUrl}}/notifications/purge" method="post">
{{$.CsrfTokenHtml}}
<div class="{{if not $notificationUnreadCount}}tw-hidden{{end}}">
<button class="ui mini button primary tw-mr-0" title="{{ctx.Locale.Tr "notification.mark_all_as_read"}}">
{{svg "octicon-checklist"}}
</button>
</div>
<button class="ui mini button primary tw-mr-0" title="{{ctx.Locale.Tr "notification.mark_all_as_read"}}">
{{svg "octicon-checklist"}}
</button>
</form>
{{end}}
</div>
<div class="tw-p-0">
<div id="notification_table">
{{if not .Notifications}}
<div class="tw-flex tw-items-center tw-flex-col tw-p-4">
{{svg "octicon-inbox" 56 "tw-mb-4"}}
{{if eq .Status 1}}
{{ctx.Locale.Tr "notification.no_unread"}}
<div id="notification_table">
{{range $one := .Notifications}}
<div class="notifications-item" id="notification_{{$one.ID}}" data-status="{{$one.Status}}">
<div class="tw-self-start tw-mt-[2px]">
{{if $one.Issue}}
{{template "shared/issueicon" $one.Issue}}
{{else}}
{{ctx.Locale.Tr "notification.no_read"}}
{{svg "octicon-repo" 16 "text grey"}}
{{end}}
</div>
{{else}}
{{range $notification := .Notifications}}
<div class="notifications-item tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-p-2" id="notification_{{.ID}}" data-status="{{.Status}}">
<div class="notifications-icon tw-ml-2 tw-mr-1 tw-self-start tw-mt-1">
{{if .Issue}}
{{template "shared/issueicon" .Issue}}
{{else}}
{{svg "octicon-repo" 16 "text grey"}}
{{end}}
</div>
<a class="notifications-link tw-flex tw-flex-1 tw-flex-col silenced" href="{{.Link ctx}}">
<div class="notifications-top-row tw-text-13 tw-break-anywhere">
{{.Repository.FullName}} {{if .Issue}}<span class="text light-3">#{{.Issue.Index}}</span>{{end}}
{{if eq .Status 3}}
{{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}}
{{end}}
</div>
<div class="notifications-bottom-row tw-text-16 tw-py-0.5">
<span class="issue-title tw-break-anywhere">
{{if .Issue}}
{{.Issue.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}
{{else}}
{{.Repository.FullName}}
{{end}}
</span>
</div>
</a>
<div class="notifications-updated tw-items-center tw-mr-2">
{{if .Issue}}
{{DateUtils.TimeSince .Issue.UpdatedUnix}}
{{else}}
{{DateUtils.TimeSince .UpdatedUnix}}
{{end}}
</div>
<div class="notifications-buttons tw-items-center tw-justify-end tw-gap-1 tw-px-1">
{{if ne .Status 3}}
<form action="{{AppSubUrl}}/notifications/status" method="post">
{{$.CsrfTokenHtml}}
<input type="hidden" name="notification_id" value="{{.ID}}">
<input type="hidden" name="status" value="pinned">
<button class="btn interact-bg tw-p-2" title="{{ctx.Locale.Tr "notification.pin"}}"
data-url="{{AppSubUrl}}/notifications/status"
data-status="pinned"
data-page="{{$.Page.Paginater.Current}}"
data-notification-id="{{.ID}}"
data-q="{{$.Keyword}}">
{{svg "octicon-pin"}}
</button>
</form>
{{end}}
{{if or (eq .Status 1) (eq .Status 3)}}
<form action="{{AppSubUrl}}/notifications/status" method="post">
{{$.CsrfTokenHtml}}
<input type="hidden" name="notification_id" value="{{.ID}}">
<input type="hidden" name="status" value="read">
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}">
<button class="btn interact-bg tw-p-2" title="{{ctx.Locale.Tr "notification.mark_as_read"}}"
data-url="{{AppSubUrl}}/notifications/status"
data-status="read"
data-page="{{$.Page.Paginater.Current}}"
data-notification-id="{{.ID}}"
data-q="{{$.Keyword}}">
{{svg "octicon-check"}}
</button>
</form>
{{else if eq .Status 2}}
<form action="{{AppSubUrl}}/notifications/status" method="post">
{{$.CsrfTokenHtml}}
<input type="hidden" name="notification_id" value="{{.ID}}">
<input type="hidden" name="status" value="unread">
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}">
<button class="btn interact-bg tw-p-2" title="{{ctx.Locale.Tr "notification.mark_as_unread"}}"
data-url="{{AppSubUrl}}/notifications/status"
data-status="unread"
data-page="{{$.Page.Paginater.Current}}"
data-notification-id="{{.ID}}"
data-q="{{$.Keyword}}">
{{svg "octicon-bell"}}
</button>
</form>
{{end}}
</div>
<a class="notifications-link silenced tw-flex-1" href="{{$one.Link ctx}}">
<div class="flex-text-block tw-text-[0.95em]">
{{$one.Repository.FullName}} {{if $one.Issue}}<span class="text light-3">#{{$one.Issue.Index}}</span>{{end}}
{{if eq $one.Status $statusPinned}}
{{svg "octicon-pin" 13 "text blue"}}
{{end}}
</div>
<div class="tw-text-16 tw-py-0.5">
{{if $one.Issue}}
{{$one.Issue.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}
{{else}}
{{$one.Repository.FullName}}
{{end}}
</div>
</a>
<div class="notifications-updated flex-text-inline">
{{if $one.Issue}}
{{DateUtils.TimeSince $one.Issue.UpdatedUnix}}
{{else}}
{{DateUtils.TimeSince $one.UpdatedUnix}}
{{end}}
</div>
<form class="notifications-buttons" action="{{AppSubUrl}}/notifications/status?type={{$.PageType}}&page={{$.Page.Paginater.Current}}&perPage={{$.Page.Paginater.PagingNum}}" method="post"
hx-boost="true" hx-target="#notification_div" hx-swap="outerHTML"
>
{{$.CsrfTokenHtml}}
<input type="hidden" name="notification_id" value="{{$one.ID}}">
{{if ne $one.Status $statusPinned}}
<button class="btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "notification.pin"}}"
name="notification_action" value="pin"
>
{{svg "octicon-pin"}}
</button>
{{end}}
{{if or (eq $one.Status $statusUnread) (eq $one.Status $statusPinned)}}
<button class="btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "notification.mark_as_read"}}"
name="notification_action" value="mark_as_read"
>
{{svg "octicon-check"}}
</button>
{{else if eq $one.Status $statusRead}}
<button class="btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "notification.mark_as_unread"}}"
name="notification_action" value="mark_as_unread"
>
{{svg "octicon-bell"}}
</button>
{{end}}
</form>
</div>
{{else}}
<div class="empty-placeholder">
{{svg "octicon-inbox" 56 "tw-mb-4"}}
{{if $pageTypeIsRead}}
{{ctx.Locale.Tr "notification.no_read"}}
{{else}}
{{ctx.Locale.Tr "notification.no_unread"}}
{{end}}
{{end}}
</div>
</div>
{{end}}
</div>
{{template "base/paginate" .}}
</div>

View File

@ -35,37 +35,12 @@
{{end}}
</div>
{{if not (and ($.UserDisabledFeatures.Contains "manage_credentials") (not $.EnableNotifyMail))}}
{{if not ($.UserDisabledFeatures.Contains "manage_credentials")}}
<h4 class="ui top attached header">
{{ctx.Locale.Tr "settings.manage_emails"}}
</h4>
<div class="ui attached segment">
<div class="ui list flex-items-block">
{{if $.EnableNotifyMail}}
<div class="item">
<form class="ui form tw-w-full" action="{{AppSubUrl}}/user/settings/account/email" method="post">
{{$.CsrfTokenHtml}}
<input name="_method" type="hidden" value="NOTIFICATION">
<div class="field">
<label>{{ctx.Locale.Tr "settings.email_desc"}}</label>
<div class="ui selection dropdown">
<input name="preference" type="hidden" value="{{.EmailNotificationsPreference}}">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="text"></div>
<div class="menu">
<div data-value="enabled" class="{{if eq .EmailNotificationsPreference "enabled"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.enable"}}</div>
<div data-value="andyourown" class="{{if eq .EmailNotificationsPreference "andyourown"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.andyourown"}}</div>
<div data-value="onmention" class="{{if eq .EmailNotificationsPreference "onmention"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.onmention"}}</div>
<div data-value="disabled" class="{{if eq .EmailNotificationsPreference "disabled"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.disable"}}</div>
</div>
</div>
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "settings.email_notifications.submit"}}</button>
</div>
</form>
</div>
{{end}}
{{if not ($.UserDisabledFeatures.Contains "manage_credentials")}}
{{range .Emails}}
<div class="item tw-flex-wrap">

View File

@ -4,11 +4,16 @@
<a class="{{if .PageIsSettingsProfile}}active {{end}}item" href="{{AppSubUrl}}/user/settings">
{{ctx.Locale.Tr "settings.profile"}}
</a>
{{if not (and ($.UserDisabledFeatures.Contains "manage_credentials" "deletion") (not $.EnableNotifyMail))}}
{{if not ($.UserDisabledFeatures.Contains "manage_credentials" "deletion")}}
<a class="{{if .PageIsSettingsAccount}}active {{end}}item" href="{{AppSubUrl}}/user/settings/account">
{{ctx.Locale.Tr "settings.account"}}
</a>
{{end}}
{{if $.EnableNotifyMail}}
<a class="{{if .PageIsSettingsNotifications}}active {{end}}item" href="{{AppSubUrl}}/user/settings/notifications">
{{ctx.Locale.Tr "notifications"}}
</a>
{{end}}
<a class="{{if .PageIsSettingsAppearance}}active {{end}}item" href="{{AppSubUrl}}/user/settings/appearance">
{{ctx.Locale.Tr "settings.appearance"}}
</a>

Some files were not shown because too many files have changed in this diff Show More