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:
commit
714fcc9fb1
@ -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],
|
||||
|
@ -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`
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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{}
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -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{
|
||||
|
@ -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,
|
||||
|
158
cmd/main.go
158
cmd/main.go
@ -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])
|
||||
|
@ -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",
|
||||
|
@ -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{
|
||||
|
@ -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.
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
2
go.mod
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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 (
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 συγγραφείς
|
||||
|
@ -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 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_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)
|
||||
|
@ -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
|
||||
|
@ -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 بانی
|
||||
|
@ -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 n’a 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 l‘artefact « %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 d’authentificati
|
||||
|
||||
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 d’un 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 d’organisation, visible à tout le monde. Assurez-vous qu’il 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 d’organisation, visible uniquement aux membres de l’organisation. Assurez-vous qu’il soit privé et initialisez-le avec un README dans le répertoire de profil pour commencer.
|
||||
repo_name_helper=Idéalement, le nom d’un 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 n’a 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 d’un 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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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人の作成者
|
||||
|
@ -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 계정에 대한 접근 권한이 사라지게 됩니다. 계속하시겠습니까?
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 автора(ов)
|
||||
|
@ -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 කතුවරුන්
|
||||
|
@ -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ť?
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 автори
|
||||
|
@ -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 位作者
|
||||
|
@ -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 位作者
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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":
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
88
routers/web/auth/oauth_signin_sync.go
Normal file
88
routers/web/auth/oauth_signin_sync.go
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
73
routers/web/repo/common_recentbranches.go
Normal file
73
routers/web/repo/common_recentbranches.go
Normal 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}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
62
routers/web/user/setting/notifications.go
Normal file
62
routers/web/user/setting/notifications.go
Normal 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")
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
16
services/agit/agit_test.go
Normal file
16
services/agit/agit_test.go
Normal 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"))
|
||||
}
|
@ -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{
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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}}">
|
||||
|
@ -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}}">
|
||||
|
@ -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}}
|
||||
|
@ -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}}
|
||||
|
@ -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}}
|
||||
|
@ -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">
|
||||
|
@ -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}}
|
||||
|
@ -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}}>
|
||||
|
2
templates/swagger/v1_json.tmpl
generated
2
templates/swagger/v1_json.tmpl
generated
@ -5165,7 +5165,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/ArtifactsList"
|
||||
"$ref": "#/responses/WorkflowRunsList"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user