Merge branch 'main' into fix-incorrect-recently-pushed-new-branches-check

This commit is contained in:
yp05327 2023-07-24 09:17:19 +09:00 committed by GitHub
commit 17876b4e89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
153 changed files with 2869 additions and 1810 deletions

View File

@ -420,7 +420,7 @@ rules:
no-restricted-exports: [0]
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, location, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, self, status, statusbar, stop, toolbar, top, __dirname, __filename]
no-restricted-imports: [0]
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement]
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
no-return-assign: [0]
no-return-await: [0]
no-script-url: [2]
@ -666,7 +666,6 @@ rules:
unicorn/no-unnecessary-await: [2]
unicorn/no-unreadable-array-destructuring: [0]
unicorn/no-unreadable-iife: [2]
unicorn/no-unsafe-regex: [0]
unicorn/no-unused-properties: [2]
unicorn/no-useless-fallback-in-spread: [2]
unicorn/no-useless-length-check: [2]

View File

@ -84,6 +84,7 @@ rules:
media-feature-name-value-allowed-list: null
media-feature-name-value-no-unknown: true
media-feature-range-notation: null
media-query-no-invalid: true
named-grid-areas-no-invalid: true
no-descending-specificity: null
no-duplicate-at-import-rules: true

View File

@ -4,6 +4,34 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.20.1](https://github.com/go-gitea/gitea/releases/tag/1.20.1) - 2023-07-22
* SECURITY
* Disallow dangerous URL schemes (#25960) (#25964)
* ENHANCEMENTS
* Show the mismatched ROOT_URL warning on the sign-in page if OAuth2 is enabled (#25947) (#25972)
* Make pending commit status yellow again (#25935) (#25968)
* BUGFIXES
* Fix version in rpm repodata/primary.xml.gz (#26009) (#26048)
* Fix env config parsing for "GITEA____APP_NAME" (#26001) (#26013)
* ParseScope with owner/repo always sets owner to zero (#25987) (#25989)
* Fix SSPI auth panic (#25955) (#25969)
* Avoid creating directories when loading config (#25944) (#25957)
* Make environment-to-ini work with INSTALL_LOCK=true (#25926) (#25937)
* Ignore `runs-on` with expressions when warning no matched runners (#25917) (#25933)
* Avoid opening/closing PRs which are already merged (#25883) (#25903)
* DOCS
* RPM Registry: Show zypper commands for SUSE based distros as well (#25981) (#26020)
* Correctly refer to dev tags as nightly in the docker docs (#26004) (#26019)
* Update path related documents (#25417) (#25982)
* MISC
* Adding remaining enum for migration repo model type. (#26021) (#26034)
* Fix the route for pull-request's authors (#26016) (#26018)
* Fix commit status color on dashboard repolist (#25993) (#25998)
* Avoid hard-coding height in language dropdown menu (#25986) (#25997)
* Add shutting down notice (#25920) (#25922)
* Fix incorrect milestone count when provide a keyword (#25880) (#25904)
## [1.20.0](https://github.com/go-gitea/gitea/releases/tag/v1.20.0) - 2023-07-16
* BREAKING

File diff suppressed because one or more lines are too long

View File

@ -9,30 +9,31 @@ import (
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var (
// CmdActions represents the available actions sub-commands.
CmdActions = cli.Command{
CmdActions = &cli.Command{
Name: "actions",
Usage: "",
Description: "Commands for managing Gitea Actions",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken,
},
}
subcmdActionsGenRunnerToken = cli.Command{
subcmdActionsGenRunnerToken = &cli.Command{
Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server",
Action: runGenerateActionsRunnerToken,
Aliases: []string{"grt"},
Flags: []cli.Flag{
cli.StringFlag{
Name: "scope, s",
Value: "",
Usage: "{owner}[/{repo}] - leave empty for a global runner",
&cli.StringFlag{
Name: "scope",
Aliases: []string{"s"},
Value: "",
Usage: "{owner}[/{repo}] - leave empty for a global runner",
},
},
}

View File

@ -26,15 +26,15 @@ import (
"code.gitea.io/gitea/services/auth/source/smtp"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var (
// CmdAdmin represents the available admin sub-command.
CmdAdmin = cli.Command{
CmdAdmin = &cli.Command{
Name: "admin",
Usage: "Command line interface to perform common administrative operations",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
subcmdUser,
subcmdRepoSyncReleases,
subcmdRegenerate,
@ -43,37 +43,37 @@ var (
},
}
subcmdRepoSyncReleases = cli.Command{
subcmdRepoSyncReleases = &cli.Command{
Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases,
}
subcmdRegenerate = cli.Command{
subcmdRegenerate = &cli.Command{
Name: "regenerate",
Usage: "Regenerate specific files",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
microcmdRegenHooks,
microcmdRegenKeys,
},
}
microcmdRegenHooks = cli.Command{
microcmdRegenHooks = &cli.Command{
Name: "hooks",
Usage: "Regenerate git-hooks",
Action: runRegenerateHooks,
}
microcmdRegenKeys = cli.Command{
microcmdRegenKeys = &cli.Command{
Name: "keys",
Usage: "Regenerate authorized_keys file",
Action: runRegenerateKeys,
}
subcmdAuth = cli.Command{
subcmdAuth = &cli.Command{
Name: "auth",
Usage: "Modify external auth providers",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
microcmdAuthAddOauth,
microcmdAuthUpdateOauth,
cmdAuthAddLdapBindDn,
@ -87,44 +87,44 @@ var (
},
}
microcmdAuthList = cli.Command{
microcmdAuthList = &cli.Command{
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
Flags: []cli.Flag{
cli.IntFlag{
&cli.IntFlag{
Name: "min-width",
Usage: "Minimal cell width including any padding for the formatted table",
Value: 0,
},
cli.IntFlag{
&cli.IntFlag{
Name: "tab-width",
Usage: "width of tab characters in formatted table (equivalent number of spaces)",
Value: 8,
},
cli.IntFlag{
&cli.IntFlag{
Name: "padding",
Usage: "padding added to a cell before computing its width",
Value: 1,
},
cli.StringFlag{
&cli.StringFlag{
Name: "pad-char",
Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`,
Value: "\t",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "vertical-bars",
Usage: "Set to true to print vertical bars between columns",
},
},
}
idFlag = cli.Int64Flag{
idFlag = &cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}
microcmdAuthDelete = cli.Command{
microcmdAuthDelete = &cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag},
@ -132,207 +132,208 @@ var (
}
oauthCLIFlags = []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
cli.StringFlag{
&cli.StringFlag{
Name: "provider",
Value: "",
Usage: "OAuth2 Provider",
},
cli.StringFlag{
&cli.StringFlag{
Name: "key",
Value: "",
Usage: "Client ID (Key)",
},
cli.StringFlag{
&cli.StringFlag{
Name: "secret",
Value: "",
Usage: "Client Secret",
},
cli.StringFlag{
&cli.StringFlag{
Name: "auto-discover-url",
Value: "",
Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
},
cli.StringFlag{
&cli.StringFlag{
Name: "use-custom-urls",
Value: "false",
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
},
cli.StringFlag{
&cli.StringFlag{
Name: "custom-tenant-id",
Value: "",
Usage: "Use custom Tenant ID for OAuth endpoints",
},
cli.StringFlag{
&cli.StringFlag{
Name: "custom-auth-url",
Value: "",
Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
},
cli.StringFlag{
&cli.StringFlag{
Name: "custom-token-url",
Value: "",
Usage: "Use a custom Token URL (option for GitLab/GitHub)",
},
cli.StringFlag{
&cli.StringFlag{
Name: "custom-profile-url",
Value: "",
Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
},
cli.StringFlag{
&cli.StringFlag{
Name: "custom-email-url",
Value: "",
Usage: "Use a custom Email URL (option for GitHub)",
},
cli.StringFlag{
&cli.StringFlag{
Name: "icon-url",
Value: "",
Usage: "Custom icon URL for OAuth2 login source",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
cli.StringSliceFlag{
&cli.StringSliceFlag{
Name: "scopes",
Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source",
},
cli.StringFlag{
&cli.StringFlag{
Name: "required-claim-name",
Value: "",
Usage: "Claim name that has to be set to allow users to login with this source",
},
cli.StringFlag{
&cli.StringFlag{
Name: "required-claim-value",
Value: "",
Usage: "Claim value that has to be set to allow users to login with this source",
},
cli.StringFlag{
&cli.StringFlag{
Name: "group-claim-name",
Value: "",
Usage: "Claim name providing group names for this source",
},
cli.StringFlag{
&cli.StringFlag{
Name: "admin-group",
Value: "",
Usage: "Group Claim value for administrator users",
},
cli.StringFlag{
&cli.StringFlag{
Name: "restricted-group",
Value: "",
Usage: "Group Claim value for restricted users",
},
cli.StringFlag{
&cli.StringFlag{
Name: "group-team-map",
Value: "",
Usage: "JSON mapping between groups and org teams",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "group-team-map-removal",
Usage: "Activate automatic team membership removal depending on groups",
},
}
microcmdAuthUpdateOauth = cli.Command{
microcmdAuthUpdateOauth = &cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: runUpdateOauth,
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
}
microcmdAuthAddOauth = cli.Command{
microcmdAuthAddOauth = &cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: runAddOauth,
Flags: oauthCLIFlags,
}
subcmdSendMail = cli.Command{
subcmdSendMail = &cli.Command{
Name: "sendmail",
Usage: "Send a message to all users",
Action: runSendMail,
Flags: []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "title",
Usage: `a title of a message`,
Value: "",
},
cli.StringFlag{
&cli.StringFlag{
Name: "content",
Usage: "a content of a message",
Value: "",
},
cli.BoolFlag{
Name: "force,f",
Usage: "A flag to bypass a confirmation step",
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "A flag to bypass a confirmation step",
},
},
}
smtpCLIFlags = []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
cli.StringFlag{
&cli.StringFlag{
Name: "auth-type",
Value: "PLAIN",
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
},
cli.StringFlag{
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "SMTP Host",
},
cli.IntFlag{
&cli.IntFlag{
Name: "port",
Usage: "SMTP Port",
},
cli.BoolTFlag{
&cli.BoolFlag{
Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
},
cli.BoolTFlag{
&cli.BoolFlag{
Name: "skip-verify",
Usage: "Skip TLS verify.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "helo-hostname",
Value: "",
Usage: "Hostname sent with HELO. Leave blank to send current hostname",
},
cli.BoolTFlag{
&cli.BoolFlag{
Name: "disable-helo",
Usage: "Disable SMTP helo.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "allowed-domains",
Value: "",
Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
},
cli.BoolTFlag{
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.",
},
cli.BoolTFlag{
&cli.BoolFlag{
Name: "active",
Usage: "This Authentication Source is Activated.",
},
}
microcmdAuthAddSMTP = cli.Command{
microcmdAuthAddSMTP = &cli.Command{
Name: "add-smtp",
Usage: "Add new SMTP authentication source",
Action: runAddSMTP,
Flags: smtpCLIFlags,
}
microcmdAuthUpdateSMTP = cli.Command{
microcmdAuthUpdateSMTP = &cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP,
@ -611,19 +612,19 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
conf.AllowedDomains = c.String("allowed-domains")
}
if c.IsSet("force-smtps") {
conf.ForceSMTPS = c.BoolT("force-smtps")
conf.ForceSMTPS = c.Bool("force-smtps")
}
if c.IsSet("skip-verify") {
conf.SkipVerify = c.BoolT("skip-verify")
conf.SkipVerify = c.Bool("skip-verify")
}
if c.IsSet("helo-hostname") {
conf.HeloHostname = c.String("helo-hostname")
}
if c.IsSet("disable-helo") {
conf.DisableHelo = c.BoolT("disable-helo")
conf.DisableHelo = c.Bool("disable-helo")
}
if c.IsSet("skip-local-2fa") {
conf.SkipLocalTwoFA = c.BoolT("skip-local-2fa")
conf.SkipLocalTwoFA = c.Bool("skip-local-2fa")
}
return nil
}
@ -647,7 +648,7 @@ func runAddSMTP(c *cli.Context) error {
}
active := true
if c.IsSet("active") {
active = c.BoolT("active")
active = c.Bool("active")
}
var smtpConfig smtp.Source
@ -696,7 +697,7 @@ func runUpdateSMTP(c *cli.Context) error {
}
if c.IsSet("active") {
source.IsActive = c.BoolT("active")
source.IsActive = c.Bool("active")
}
source.Cfg = smtpConfig

View File

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
type (
@ -25,117 +25,117 @@ type (
var (
commonLdapCLIFlags = []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "name",
Usage: "Authentication name.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "not-active",
Usage: "Deactivate the authentication source.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "active",
Usage: "Activate the authentication source.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "security-protocol",
Usage: "Security protocol name.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-tls-verify",
Usage: "Disable TLS verification.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "host",
Usage: "The address where the LDAP server can be reached.",
},
cli.IntFlag{
&cli.IntFlag{
Name: "port",
Usage: "The port to use when connecting to the LDAP server.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "user-search-base",
Usage: "The LDAP base at which user accounts will be searched for.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "user-filter",
Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "admin-filter",
Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "restricted-filter",
Usage: "An LDAP filter specifying if a user should be given restricted status.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "allow-deactivate-all",
Usage: "Allow empty search results to deactivate all users.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "username-attribute",
Usage: "The attribute of the users LDAP record containing the user name.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "firstname-attribute",
Usage: "The attribute of the users LDAP record containing the users first name.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "surname-attribute",
Usage: "The attribute of the users LDAP record containing the users surname.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "email-attribute",
Usage: "The attribute of the users LDAP record containing the users email address.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "public-ssh-key-attribute",
Usage: "The attribute of the users LDAP record containing the users public ssh key.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
cli.StringFlag{
&cli.StringFlag{
Name: "avatar-attribute",
Usage: "The attribute of the users LDAP record containing the users avatar.",
},
}
ldapBindDnCLIFlags = append(commonLdapCLIFlags,
cli.StringFlag{
&cli.StringFlag{
Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.",
},
cli.StringFlag{
&cli.StringFlag{
Name: "bind-password",
Usage: "The password for the Bind DN, if any.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "attributes-in-bind",
Usage: "Fetch attributes in bind DN context.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "synchronize-users",
Usage: "Enable user synchronization.",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "disable-synchronize-users",
Usage: "Disable user synchronization.",
},
cli.UintFlag{
&cli.UintFlag{
Name: "page-size",
Usage: "Search page size.",
})
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
cli.StringFlag{
&cli.StringFlag{
Name: "user-dn",
Usage: "The users DN.",
})
cmdAuthAddLdapBindDn = cli.Command{
cmdAuthAddLdapBindDn = &cli.Command{
Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error {
@ -144,7 +144,7 @@ var (
Flags: ldapBindDnCLIFlags,
}
cmdAuthUpdateLdapBindDn = cli.Command{
cmdAuthUpdateLdapBindDn = &cli.Command{
Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error {
@ -153,7 +153,7 @@ var (
Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
}
cmdAuthAddLdapSimpleAuth = cli.Command{
cmdAuthAddLdapSimpleAuth = &cli.Command{
Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error {
@ -162,7 +162,7 @@ var (
Flags: ldapSimpleAuthCLIFlags,
}
cmdAuthUpdateLdapSimpleAuth = cli.Command{
cmdAuthUpdateLdapSimpleAuth = &cli.Command{
Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error {

View File

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/services/auth/source/ldap"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
func TestAddLdapBindDn(t *testing.T) {

View File

@ -4,13 +4,13 @@
package cmd
import (
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var subcmdUser = cli.Command{
var subcmdUser = &cli.Command{
Name: "user",
Usage: "Modify users",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
microcmdUserCreate,
microcmdUserList,
microcmdUserChangePassword,

View File

@ -12,23 +12,25 @@ import (
pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var microcmdUserChangePassword = cli.Command{
var microcmdUserChangePassword = &cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Value: "",
Usage: "The user to change password for",
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "The user to change password for",
},
cli.StringFlag{
Name: "password,p",
Value: "",
Usage: "New password to set for user",
&cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Value: "",
Usage: "New password to set for user",
},
},
}

View File

@ -14,52 +14,52 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var microcmdUserCreate = cli.Command{
var microcmdUserCreate = &cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
Flags: []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
},
cli.StringFlag{
&cli.StringFlag{
Name: "username",
Usage: "Username",
},
cli.StringFlag{
&cli.StringFlag{
Name: "password",
Usage: "User password",
},
cli.StringFlag{
&cli.StringFlag{
Name: "email",
Usage: "User email address",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
},
cli.IntFlag{
&cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "access-token",
Usage: "Generate access token for the user",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},

View File

@ -11,26 +11,28 @@ import (
"code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var microcmdUserDelete = cli.Command{
var microcmdUserDelete = &cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
cli.Int64Flag{
&cli.Int64Flag{
Name: "id",
Usage: "ID of user of the user to delete",
},
cli.StringFlag{
Name: "username,u",
Usage: "Username of the user to delete",
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username of the user to delete",
},
cli.StringFlag{
Name: "email,e",
Usage: "Email of the user to delete",
&cli.StringFlag{
Name: "email",
Aliases: []string{"e"},
Usage: "Email of the user to delete",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},

View File

@ -9,27 +9,29 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var microcmdUserGenerateAccessToken = cli.Command{
var microcmdUserGenerateAccessToken = &cli.Command{
Name: "generate-access-token",
Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Usage: "Username",
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username",
},
cli.StringFlag{
Name: "token-name,t",
Usage: "Token name",
Value: "gitea-admin",
&cli.StringFlag{
Name: "token-name",
Aliases: []string{"t"},
Usage: "Token name",
Value: "gitea-admin",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "raw",
Usage: "Display only the token value",
},
cli.StringFlag{
&cli.StringFlag{
Name: "scopes",
Value: "",
Usage: "Comma separated list of scopes to apply to access token",

View File

@ -10,15 +10,15 @@ import (
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var microcmdUserList = cli.Command{
var microcmdUserList = &cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "admin",
Usage: "List only admin users",
},

View File

@ -9,23 +9,25 @@ import (
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var microcmdUserMustChangePassword = cli.Command{
var microcmdUserMustChangePassword = &cli.Command{
Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "all,A",
Usage: "All users must change password, except those explicitly excluded with --exclude",
&cli.BoolFlag{
Name: "all",
Aliases: []string{"A"},
Usage: "All users must change password, except those explicitly excluded with --exclude",
},
cli.StringSliceFlag{
Name: "exclude,e",
Usage: "Do not change the must-change-password flag for these users",
&cli.StringSliceFlag{
Name: "exclude",
Aliases: []string{"e"},
Usage: "Do not change the must-change-password flag for these users",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it",
},
@ -48,7 +50,7 @@ func runMustChangePassword(c *cli.Context) error {
return err
}
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args(), exclude)
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)
if err != nil {
return err
}

View File

@ -20,43 +20,43 @@ import (
"strings"
"time"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdCert represents the available cert sub-command.
var CmdCert = cli.Command{
var CmdCert = &cli.Command{
Name: "cert",
Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server.
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Action: runCert,
Flags: []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
},
cli.StringFlag{
&cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
cli.IntFlag{
&cli.IntFlag{
Name: "rsa-bits",
Value: 2048,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
cli.StringFlag{
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
cli.DurationFlag{
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},

View File

@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// argsSet checks that all the required arguments are set. args is a list of
@ -109,15 +109,24 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
func globalBool(c *cli.Context, name string) bool {
for _, ctx := range c.Lineage() {
if ctx.Bool(name) {
return true
}
}
return false
}
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
return func(c *cli.Context) error {
level := defaultLevel
if c.Bool("quiet") || c.GlobalBoolT("quiet") {
if globalBool(c, "quiet") {
level = log.FATAL
}
if c.Bool("debug") || c.GlobalBool("debug") || c.Bool("verbose") || c.GlobalBool("verbose") {
if globalBool(c, "debug") || globalBool(c, "verbose") {
level = log.TRACE
}
log.SetConsoleLogger(log.DEFAULT, "console-default", level)

View File

@ -10,11 +10,11 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdConvert represents the available convert sub-command.
var CmdConvert = cli.Command{
var CmdConvert = &cli.Command{
Name: "convert",
Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",

View File

@ -8,11 +8,11 @@ import (
"os"
"strings"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdDocs represents the available docs sub-command.
var CmdDocs = cli.Command{
var CmdDocs = &cli.Command{
Name: "docs",
Usage: "Output CLI documentation",
Description: "A command to output Gitea's CLI documentation, optionally to a file.",
@ -23,8 +23,9 @@ var CmdDocs = cli.Command{
Usage: "Output man pages instead",
},
&cli.StringFlag{
Name: "output, o",
Usage: "Path to output to instead of stdout (will overwrite if exists)",
Name: "output",
Aliases: []string{"o"},
Usage: "Path to output to instead of stdout (will overwrite if exists)",
},
},
}

View File

@ -18,57 +18,58 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
"xorm.io/xorm"
)
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = cli.Command{
var CmdDoctor = &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Action: runDoctor,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "list",
Usage: "List the available checks",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "default",
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
},
cli.StringSliceFlag{
&cli.StringSliceFlag{
Name: "run",
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "all",
Usage: "Run all the available checks",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "fix",
Usage: "Automatically fix what we can",
},
cli.StringFlag{
&cli.StringFlag{
Name: "log-file",
Usage: `Name of the log file (default: "doctor.log"). Set to "-" to output to stdout, set to "" to disable`,
},
cli.BoolFlag{
Name: "color, H",
Usage: "Use color for outputted information",
&cli.BoolFlag{
Name: "color",
Aliases: []string{"H"},
Usage: "Use color for outputted information",
},
},
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
cmdRecreateTable,
},
}
var cmdRecreateTable = cli.Command{
var cmdRecreateTable = &cli.Command{
Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
Usage: "Print SQL commands sent",
},

View File

@ -22,7 +22,7 @@ import (
"gitea.com/go-chi/session"
"github.com/mholt/archiver/v3"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
@ -96,64 +96,71 @@ var outputTypeEnum = &outputType{
}
// CmdDump represents the available dump sub-command.
var CmdDump = cli.Command{
var CmdDump = &cli.Command{
Name: "dump",
Usage: "Dump Gitea files and database",
Description: `Dump compresses all related files and database into zip file.
It can be used for backup and capture Gitea server image to send to maintainer`,
Action: runDump,
Flags: []cli.Flag{
cli.StringFlag{
Name: "file, f",
Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
&cli.StringFlag{
Name: "file",
Aliases: []string{"f"},
Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
},
cli.BoolFlag{
Name: "verbose, V",
Usage: "Show process details",
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"V"},
Usage: "Show process details",
},
cli.BoolFlag{
Name: "quiet, q",
Usage: "Only display warnings and errors",
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display warnings and errors",
},
cli.StringFlag{
Name: "tempdir, t",
Value: os.TempDir(),
Usage: "Temporary dir path",
&cli.StringFlag{
Name: "tempdir",
Aliases: []string{"t"},
Value: os.TempDir(),
Usage: "Temporary dir path",
},
cli.StringFlag{
Name: "database, d",
Usage: "Specify the database SQL syntax",
&cli.StringFlag{
Name: "database",
Aliases: []string{"d"},
Usage: "Specify the database SQL syntax",
},
cli.BoolFlag{
Name: "skip-repository, R",
Usage: "Skip the repository dumping",
&cli.BoolFlag{
Name: "skip-repository",
Aliases: []string{"R"},
Usage: "Skip the repository dumping",
},
cli.BoolFlag{
Name: "skip-log, L",
Usage: "Skip the log dumping",
&cli.BoolFlag{
Name: "skip-log",
Aliases: []string{"L"},
Usage: "Skip the log dumping",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-custom-dir",
Usage: "Skip custom directory",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-lfs-data",
Usage: "Skip LFS data",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-attachment-data",
Usage: "Skip attachment data",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-package-data",
Usage: "Skip package data",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "skip-index",
Usage: "Skip bleve index data",
},
cli.GenericFlag{
&cli.GenericFlag{
Name: "type",
Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),

View File

@ -19,57 +19,58 @@ import (
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/migrations"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdDumpRepository represents the available dump repository sub-command.
var CmdDumpRepository = cli.Command{
var CmdDumpRepository = &cli.Command{
Name: "dump-repo",
Usage: "Dump the repository from git/github/gitea/gitlab",
Description: "This is a command for dumping the repository data.",
Action: runDumpRepository,
Flags: []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "git_service",
Value: "",
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
},
cli.StringFlag{
Name: "repo_dir, r",
Value: "./data",
Usage: "Repository dir path to store the data",
&cli.StringFlag{
Name: "repo_dir",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to store the data",
},
cli.StringFlag{
&cli.StringFlag{
Name: "clone_addr",
Value: "",
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
},
cli.StringFlag{
&cli.StringFlag{
Name: "auth_username",
Value: "",
Usage: "The username to visit the clone_addr",
},
cli.StringFlag{
&cli.StringFlag{
Name: "auth_password",
Value: "",
Usage: "The password to visit the clone_addr",
},
cli.StringFlag{
&cli.StringFlag{
Name: "auth_token",
Value: "",
Usage: "The personal token to visit the clone_addr",
},
cli.StringFlag{
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "The data will be stored on a directory with owner name if not empty",
},
cli.StringFlag{
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "The data will be stored on a directory with repository name if not empty",
},
cli.StringFlag{
&cli.StringFlag{
Name: "units",
Value: "",
Usage: `Which items will be migrated, one or more units should be separated as comma.

View File

@ -19,70 +19,74 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdEmbedded represents the available extract sub-command.
var (
CmdEmbedded = cli.Command{
CmdEmbedded = &cli.Command{
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
subcmdList,
subcmdView,
subcmdExtract,
},
}
subcmdList = cli.Command{
subcmdList = &cli.Command{
Name: "list",
Usage: "List files matching the given pattern",
Action: runList,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include-vendored,vendor",
Usage: "Include files under public/vendor as well",
&cli.BoolFlag{
Name: "include-vendored",
Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
},
},
}
subcmdView = cli.Command{
subcmdView = &cli.Command{
Name: "view",
Usage: "View a file matching the given pattern",
Action: runView,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include-vendored,vendor",
Usage: "Include files under public/vendor as well",
&cli.BoolFlag{
Name: "include-vendored",
Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
},
},
}
subcmdExtract = cli.Command{
subcmdExtract = &cli.Command{
Name: "extract",
Usage: "Extract resources",
Action: runExtract,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include-vendored,vendor",
Usage: "Include files under public/vendor as well",
&cli.BoolFlag{
Name: "include-vendored",
Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "overwrite",
Usage: "Overwrite files if they already exist",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "rename",
Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "custom",
Usage: "Extract to the 'custom' directory as per app.ini",
},
cli.StringFlag{
Name: "destination,dest-dir",
Usage: "Extract to the specified directory",
&cli.StringFlag{
Name: "destination",
Aliases: []string{"dest-dir"},
Usage: "Extract to the specified directory",
},
},
}
@ -99,7 +103,7 @@ type assetFile struct {
func initEmbeddedExtractor(c *cli.Context) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args())
patterns, err := compileCollectPatterns(c.Args().Slice())
if err != nil {
return err
}
@ -175,7 +179,7 @@ func runExtractDo(c *cli.Context) error {
return err
}
if len(c.Args()) == 0 {
if c.NArg() == 0 {
return fmt.Errorf("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
}

View File

@ -11,43 +11,43 @@ import (
"code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var (
// CmdGenerate represents the available generate sub-command.
CmdGenerate = cli.Command{
CmdGenerate = &cli.Command{
Name: "generate",
Usage: "Command line interface for running generators",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
subcmdSecret,
},
}
subcmdSecret = cli.Command{
subcmdSecret = &cli.Command{
Name: "secret",
Usage: "Generate a secret token",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey,
},
}
microcmdGenerateInternalToken = cli.Command{
microcmdGenerateInternalToken = &cli.Command{
Name: "INTERNAL_TOKEN",
Usage: "Generate a new INTERNAL_TOKEN",
Action: runGenerateInternalToken,
}
microcmdGenerateLfsJwtSecret = cli.Command{
microcmdGenerateLfsJwtSecret = &cli.Command{
Name: "JWT_SECRET",
Aliases: []string{"LFS_JWT_SECRET"},
Usage: "Generate a new JWT_SECRET",
Action: runGenerateLfsJwtSecret,
}
microcmdGenerateSecretKey = cli.Command{
microcmdGenerateSecretKey = &cli.Command{
Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey,

View File

@ -20,7 +20,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
const (
@ -29,12 +29,12 @@ const (
var (
// CmdHook represents the available hooks sub-command.
CmdHook = cli.Command{
CmdHook = &cli.Command{
Name: "hook",
Usage: "Delegate commands to corresponding Git hooks",
Description: "This should only be called by Git",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
subcmdHookPreReceive,
subcmdHookUpdate,
subcmdHookPostReceive,
@ -42,47 +42,47 @@ var (
},
}
subcmdHookPreReceive = cli.Command{
subcmdHookPreReceive = &cli.Command{
Name: "pre-receive",
Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPreReceive,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
}
subcmdHookUpdate = cli.Command{
subcmdHookUpdate = &cli.Command{
Name: "update",
Usage: "Delegate update Git hook",
Description: "This command should only be called by Git",
Action: runHookUpdate,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
}
subcmdHookPostReceive = cli.Command{
subcmdHookPostReceive = &cli.Command{
Name: "post-receive",
Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPostReceive,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
}
// Note: new hook since git 2.29
subcmdHookProcReceive = cli.Command{
subcmdHookProcReceive = &cli.Command{
Name: "proc-receive",
Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookProcReceive,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},

View File

@ -11,35 +11,39 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdKeys represents the available keys sub-command
var CmdKeys = cli.Command{
var CmdKeys = &cli.Command{
Name: "keys",
Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runKeys,
Flags: []cli.Flag{
cli.StringFlag{
Name: "expected, e",
Value: "git",
Usage: "Expected user for whom provide key commands",
&cli.StringFlag{
Name: "expected",
Aliases: []string{"e"},
Value: "git",
Usage: "Expected user for whom provide key commands",
},
cli.StringFlag{
Name: "username, u",
Value: "",
Usage: "Username trying to log in by SSH",
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "Username trying to log in by SSH",
},
cli.StringFlag{
Name: "type, t",
Value: "",
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
&cli.StringFlag{
Name: "type",
Aliases: []string{"t"},
Value: "",
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
},
cli.StringFlag{
Name: "content, k",
Value: "",
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
&cli.StringFlag{
Name: "content",
Aliases: []string{"k"},
Value: "",
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
},
},
}
@ -73,6 +77,6 @@ func runKeys(c *cli.Context) error {
if extra.Error != nil {
return extra.Error
}
fmt.Println(strings.TrimSpace(authorizedString))
_, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString))
return nil
}

View File

@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
func runSendMail(c *cli.Context) error {

196
cmd/main.go Normal file
View File

@ -0,0 +1,196 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"os"
"reflect"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v2"
)
// cmdHelp is our own help subcommand with more 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(c *cli.Context) (err error) {
args := c.Args()
if args.Present() {
err = cli.ShowCommandHelp(c, args.First())
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
CustomPath: %s
ConfigFile: %s
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
return err
},
}
return c
}
var helpFlag = cli.HelpFlag
func init() {
// cli.HelpFlag = nil TODO: after https://github.com/urfave/cli/issues/1794 we can use this
}
func appGlobalFlags() []cli.Flag {
return []cli.Flag{
// make the builtin flags at the top
helpFlag,
cli.VersionFlag,
// 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 prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
command.Subcommands = append(command.Subcommands, cmdHelp())
}
for i := range command.Subcommands {
prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
}
}
// 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(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
var args setting.ArgWorkPathAndCustomConf
ctxLineage := ctx.Lineage()
for i := len(ctxLineage) - 1; i >= 0; i-- {
curCtx := ctxLineage[i]
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")
}
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if ctx.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx)
}
return action(ctx)
}
}
func reflectGet(v any, fieldName string) any {
e := reflect.ValueOf(v).Elem()
return e.FieldByName(fieldName).Interface()
}
// https://cli.urfave.org/migrate-v1-to-v2/#flag-aliases-are-done-differently
// Sadly v2 doesn't warn you if a comma is in the name. (https://github.com/urfave/cli/issues/1103)
func checkCommandFlags(c any) bool {
var cmds []*cli.Command
if app, ok := c.(*cli.App); ok {
cmds = app.Commands
} else {
cmds = c.(*cli.Command).Subcommands
}
ok := true
for _, cmd := range cmds {
for _, flag := range cmd.Flags {
flagName := reflectGet(flag, "Name").(string)
if strings.Contains(flagName, ",") {
ok = false
log.Error("cli.Flag can't have comma in its Name: %q, use Aliases instead", flagName)
}
}
if !checkCommandFlags(cmd) {
ok = false
}
}
return ok
}
func NewMainApp() *cli.App {
app := cli.NewApp()
app.EnableBashCompletion = true
// these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{
CmdWeb,
CmdServ,
CmdHook,
CmdDump,
CmdAdmin,
CmdMigrate,
CmdKeys,
CmdConvert,
CmdDoctor,
CmdManager,
CmdEmbedded,
CmdMigrateStorage,
CmdDumpRepository,
CmdRestoreRepository,
CmdActions,
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
}
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
CmdCert,
CmdGenerate,
CmdDocs,
}
app.DefaultCommand = CmdWeb.Name
globalFlags := appGlobalFlags()
app.Flags = append(app.Flags, globalFlags...)
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 {
prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
}
app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...)
if !checkCommandFlags(app) {
panic("some flags are incorrect") // this is a runtime check to help developers
}
return app
}

View File

@ -4,9 +4,17 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
func TestMain(m *testing.M) {
@ -14,3 +22,110 @@ func TestMain(m *testing.M) {
GiteaRootPath: "..",
})
}
func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
}
func newTestApp() *cli.App {
app := NewMainApp()
testCmd := &cli.Command{
Name: "test-cmd",
Action: func(ctx *cli.Context) error {
_, _ = fmt.Fprint(app.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
},
}
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name
return app
}
func TestCliCmd(t *testing.T) {
defaultWorkPath := filepath.Dir(setting.AppPath)
defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)"
cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct {
env map[string]string
cmd string
exp string
}{
// main command help
{
cmd: "./gitea help",
exp: "DEFAULT CONFIGURATION:",
},
// parse paths
{
cmd: "./gitea test-cmd",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
},
{
cmd: "./gitea -c /tmp/app.ini test-cmd",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
cmd: "./gitea test-cmd -c /tmp/app.ini",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd",
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd --work-path /tmp/other",
exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd --config /tmp/app-other.ini",
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
},
}
app := newTestApp()
var envBackup []string
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") {
envBackup = append(envBackup, s)
}
}
clearGiteaEnv := func() {
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") {
_ = os.Unsetenv(s)
}
}
}
defer func() {
clearGiteaEnv()
for _, s := range envBackup {
k, v, _ := strings.Cut(s, "=")
_ = os.Setenv(k, v)
}
}()
for _, c := range cases {
clearGiteaEnv()
for k, v := range c.env {
_ = os.Setenv(k, v)
}
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
out := new(strings.Builder)
app.Writer = out
err := app.Run(args)
assert.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd)
outStr := out.String()
assert.Contains(t, outStr, c.exp, c.cmd)
}
}

View File

@ -9,16 +9,16 @@ import (
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var (
// CmdManager represents the manager command
CmdManager = cli.Command{
CmdManager = &cli.Command{
Name: "manager",
Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
subcmdShutdown,
subcmdRestart,
subcmdReloadTemplates,
@ -27,80 +27,80 @@ var (
subCmdProcesses,
},
}
subcmdShutdown = cli.Command{
subcmdShutdown = &cli.Command{
Name: "shutdown",
Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runShutdown,
}
subcmdRestart = cli.Command{
subcmdRestart = &cli.Command{
Name: "restart",
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runRestart,
}
subcmdReloadTemplates = cli.Command{
subcmdReloadTemplates = &cli.Command{
Name: "reload-templates",
Usage: "Reload template files in the running process",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runReloadTemplates,
}
subcmdFlushQueues = cli.Command{
subcmdFlushQueues = &cli.Command{
Name: "flush-queues",
Usage: "Flush queues in the running process",
Action: runFlushQueues,
Flags: []cli.Flag{
cli.DurationFlag{
&cli.DurationFlag{
Name: "timeout",
Value: 60 * time.Second,
Usage: "Timeout for the flushing process",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "non-blocking",
Usage: "Set to true to not wait for flush to complete before returning",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
}
subCmdProcesses = cli.Command{
subCmdProcesses = &cli.Command{
Name: "processes",
Usage: "Display running processes within the current process",
Action: runProcesses,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "flat",
Usage: "Show processes as flat table rather than as tree",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "no-system",
Usage: "Do not show system processes",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "stacktraces",
Usage: "Show stacktraces",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "json",
Usage: "Output as json",
},
cli.StringFlag{
&cli.StringFlag{
Name: "cancel",
Usage: "Process PID to cancel. (Only available for non-system processes.)",
},

View File

@ -10,49 +10,61 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
var (
defaultLoggingFlags = []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "logger",
Usage: `Logger name - will default to "default"`,
}, cli.StringFlag{
},
&cli.StringFlag{
Name: "writer",
Usage: "Name of the log writer - will default to mode",
}, cli.StringFlag{
},
&cli.StringFlag{
Name: "level",
Usage: "Logging level for the new logger",
}, cli.StringFlag{
Name: "stacktrace-level, L",
Usage: "Stacktrace logging level",
}, cli.StringFlag{
Name: "flags, F",
Usage: "Flags for the logger",
}, cli.StringFlag{
Name: "expression, e",
Usage: "Matching expression for the logger",
}, cli.StringFlag{
Name: "prefix, p",
Usage: "Prefix for the logger",
}, cli.BoolFlag{
},
&cli.StringFlag{
Name: "stacktrace-level",
Aliases: []string{"L"},
Usage: "Stacktrace logging level",
},
&cli.StringFlag{
Name: "flags",
Aliases: []string{"F"},
Usage: "Flags for the logger",
},
&cli.StringFlag{
Name: "expression",
Aliases: []string{"e"},
Usage: "Matching expression for the logger",
},
&cli.StringFlag{
Name: "prefix",
Aliases: []string{"p"},
Usage: "Prefix for the logger",
},
&cli.BoolFlag{
Name: "color",
Usage: "Use color in the logs",
}, cli.BoolFlag{
},
&cli.BoolFlag{
Name: "debug",
},
}
subcmdLogging = cli.Command{
subcmdLogging = &cli.Command{
Name: "logging",
Usage: "Adjust logging commands",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
{
Name: "pause",
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
@ -61,7 +73,7 @@ var (
Name: "resume",
Usage: "Resume logging",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
@ -70,7 +82,7 @@ var (
Name: "release-and-reopen",
Usage: "Cause Gitea to release and re-open files used for logging",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
@ -80,9 +92,9 @@ var (
Usage: "Remove a logger",
ArgsUsage: "[name] Name of logger to remove",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
}, cli.StringFlag{
}, &cli.StringFlag{
Name: "logger",
Usage: `Logger name - will default to "default"`,
},
@ -91,32 +103,45 @@ var (
}, {
Name: "add",
Usage: "Add a logger",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
{
Name: "file",
Usage: "Add a file logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.StringFlag{
Name: "filename, f",
Usage: "Filename for the logger - this must be set.",
}, cli.BoolTFlag{
Name: "rotate, r",
Usage: "Rotate logs",
}, cli.Int64Flag{
Name: "max-size, s",
Usage: "Maximum size in bytes before rotation",
}, cli.BoolTFlag{
Name: "daily, d",
Usage: "Rotate logs daily",
}, cli.IntFlag{
Name: "max-days, D",
Usage: "Maximum number of daily logs to keep",
}, cli.BoolTFlag{
Name: "compress, z",
Usage: "Compress rotated logs",
}, cli.IntFlag{
Name: "compression-level, Z",
Usage: "Compression level to use",
&cli.StringFlag{
Name: "filename",
Aliases: []string{"f"},
Usage: "Filename for the logger - this must be set.",
},
&cli.BoolFlag{
Name: "rotate",
Aliases: []string{"r"},
Usage: "Rotate logs",
},
&cli.Int64Flag{
Name: "max-size",
Aliases: []string{"s"},
Usage: "Maximum size in bytes before rotation",
},
&cli.BoolFlag{
Name: "daily",
Aliases: []string{"d"},
Usage: "Rotate logs daily",
},
&cli.IntFlag{
Name: "max-days",
Aliases: []string{"D"},
Usage: "Maximum number of daily logs to keep",
},
&cli.BoolFlag{
Name: "compress",
Aliases: []string{"z"},
Usage: "Compress rotated logs",
},
&cli.IntFlag{
Name: "compression-level",
Aliases: []string{"Z"},
Usage: "Compression level to use",
},
}...),
Action: runAddFileLogger,
@ -124,18 +149,25 @@ var (
Name: "conn",
Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.BoolFlag{
Name: "reconnect-on-message, R",
Usage: "Reconnect to host for every message",
}, cli.BoolFlag{
Name: "reconnect, r",
Usage: "Reconnect to host when connection is dropped",
}, cli.StringFlag{
Name: "protocol, P",
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)",
}, cli.StringFlag{
Name: "address, a",
Usage: "Host address and port to connect to (defaults to :7020)",
&cli.BoolFlag{
Name: "reconnect-on-message",
Aliases: []string{"R"},
Usage: "Reconnect to host for every message",
},
&cli.BoolFlag{
Name: "reconnect",
Aliases: []string{"r"},
Usage: "Reconnect to host when connection is dropped",
},
&cli.StringFlag{
Name: "protocol",
Aliases: []string{"P"},
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)",
},
&cli.StringFlag{
Name: "address",
Aliases: []string{"a"},
Usage: "Host address and port to connect to (defaults to :7020)",
},
}...),
Action: runAddConnLogger,
@ -145,9 +177,10 @@ var (
Name: "log-sql",
Usage: "Set LogSQL",
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
}, cli.BoolFlag{
},
&cli.BoolFlag{
Name: "off",
Usage: "Switch off SQL logging",
},

View File

@ -11,11 +11,11 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdMigrate represents the available migrate sub-command.
var CmdMigrate = cli.Command{
var CmdMigrate = &cli.Command{
Name: "migrate",
Usage: "Migrate the database",
Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",

View File

@ -20,70 +20,73 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdMigrateStorage represents the available migrate storage sub-command.
var CmdMigrateStorage = cli.Command{
var CmdMigrateStorage = &cli.Command{
Name: "migrate-storage",
Usage: "Migrate the storage",
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
Action: runMigrateStorage,
Flags: []cli.Flag{
cli.StringFlag{
Name: "type, t",
Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'",
&cli.StringFlag{
Name: "type",
Aliases: []string{"t"},
Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'",
},
cli.StringFlag{
Name: "storage, s",
Value: "",
Usage: "New storage type: local (default) or minio",
&cli.StringFlag{
Name: "storage",
Aliases: []string{"s"},
Value: "",
Usage: "New storage type: local (default) or minio",
},
cli.StringFlag{
Name: "path, p",
Value: "",
Usage: "New storage placement if store is local (leave blank for default)",
&cli.StringFlag{
Name: "path",
Aliases: []string{"p"},
Value: "",
Usage: "New storage placement if store is local (leave blank for default)",
},
cli.StringFlag{
&cli.StringFlag{
Name: "minio-endpoint",
Value: "",
Usage: "Minio storage endpoint",
},
cli.StringFlag{
&cli.StringFlag{
Name: "minio-access-key-id",
Value: "",
Usage: "Minio storage accessKeyID",
},
cli.StringFlag{
&cli.StringFlag{
Name: "minio-secret-access-key",
Value: "",
Usage: "Minio storage secretAccessKey",
},
cli.StringFlag{
&cli.StringFlag{
Name: "minio-bucket",
Value: "",
Usage: "Minio storage bucket",
},
cli.StringFlag{
&cli.StringFlag{
Name: "minio-location",
Value: "",
Usage: "Minio storage location to create bucket",
},
cli.StringFlag{
&cli.StringFlag{
Name: "minio-base-path",
Value: "",
Usage: "Minio storage base path on the bucket",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "minio-use-ssl",
Usage: "Enable SSL for minio",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "minio-insecure-skip-verify",
Usage: "Skip SSL verification",
},
cli.StringFlag{
&cli.StringFlag{
Name: "minio-checksum-algorithm",
Value: "",
Usage: "Minio checksum algorithm (default/md5)",

View File

@ -9,38 +9,39 @@ import (
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// CmdRestoreRepository represents the available restore a repository sub-command.
var CmdRestoreRepository = cli.Command{
var CmdRestoreRepository = &cli.Command{
Name: "restore-repo",
Usage: "Restore the repository from disk",
Description: "This is a command for restoring the repository data.",
Action: runRestoreRepository,
Flags: []cli.Flag{
cli.StringFlag{
Name: "repo_dir, r",
Value: "./data",
Usage: "Repository dir path to restore from",
&cli.StringFlag{
Name: "repo_dir",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to restore from",
},
cli.StringFlag{
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "Restore destination owner name",
},
cli.StringFlag{
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "Restore destination repository name",
},
cli.StringFlag{
&cli.StringFlag{
Name: "units",
Value: "",
Usage: `Which items will be restored, one or more units should be separated as comma.
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "validation",
Usage: "Sanity check the content of the files before trying to load them",
},

View File

@ -32,7 +32,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
const (
@ -40,17 +40,17 @@ const (
)
// CmdServ represents the available serv sub-command.
var CmdServ = cli.Command{
var CmdServ = &cli.Command{
Name: "serv",
Usage: "This command should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ,
Flags: []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "enable-pprof",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "debug",
},
},
@ -119,7 +119,7 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
}
_ = private.SSHLog(ctx, true, logMsg)
}
return cli.NewExitError("", 1)
return cli.Exit("", 1)
}
// handleCliResponseExtra handles the extra response from the cli sub-commands
@ -130,7 +130,7 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
_, _ = fmt.Fprintln(os.Stdout, extra.UserMsg)
}
if extra.HasError() {
return cli.NewExitError(extra.Error, 1)
return cli.Exit(extra.Error, 1)
}
return nil
}
@ -147,20 +147,20 @@ func runServ(c *cli.Context) error {
return nil
}
if len(c.Args()) < 1 {
if c.NArg() < 1 {
if err := cli.ShowSubcommandHelp(c); err != nil {
fmt.Printf("error showing subcommand help: %v\n", err)
}
return nil
}
keys := strings.Split(c.Args()[0], "-")
keys := strings.Split(c.Args().First(), "-")
if len(keys) != 2 || keys[0] != "key" {
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args()[0])
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First())
}
keyID, err := strconv.ParseInt(keys[1], 10, 64)
if err != nil {
return fail(ctx, "Key ID parsing error", "Invalid key argument: %s", c.Args()[1])
return fail(ctx, "Key ID parsing error", "Invalid key argument: %s", c.Args().Get(1))
}
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")

View File

@ -15,22 +15,24 @@ import (
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/install"
"github.com/felixge/fgprof"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
// PIDFile could be set from build tag
var PIDFile = "/run/gitea.pid"
// CmdWeb represents the available web sub-command.
var CmdWeb = cli.Command{
var CmdWeb = &cli.Command{
Name: "web",
Usage: "Start Gitea web server",
Description: `Gitea web server is the only thing you need to run,
@ -38,26 +40,29 @@ and it takes care of all the other things for you`,
Before: PrepareConsoleLoggerLevel(log.INFO),
Action: runWeb,
Flags: []cli.Flag{
cli.StringFlag{
Name: "port, p",
Value: "3000",
Usage: "Temporary port number to prevent conflict",
&cli.StringFlag{
Name: "port",
Aliases: []string{"p"},
Value: "3000",
Usage: "Temporary port number to prevent conflict",
},
cli.StringFlag{
&cli.StringFlag{
Name: "install-port",
Value: "3000",
Usage: "Temporary port number to run the install page on to prevent conflict",
},
cli.StringFlag{
Name: "pid, P",
Value: PIDFile,
Usage: "Custom pid file path",
&cli.StringFlag{
Name: "pid",
Aliases: []string{"P"},
Value: PIDFile,
Usage: "Custom pid file path",
},
cli.BoolFlag{
Name: "quiet, q",
Usage: "Only display Fatal logging errors until logging is set-up",
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display Fatal logging errors until logging is set-up",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "verbose",
Usage: "Set initial logging to TRACE level until logging is properly set-up",
},
@ -172,6 +177,20 @@ func serveInstalled(ctx *cli.Context) error {
}
}
// in old versions, user's custom web files are placed in "custom/public", and they were served as "http://domain.com/assets/xxx"
// now, Gitea only serves pre-defined files in the "custom/public" folder basing on the web root, the user should move their custom files to "custom/public/assets"
publicFiles, _ := public.AssetFS().ListFiles(".")
publicFilesSet := container.SetOf(publicFiles...)
publicFilesSet.Remove(".well-known")
publicFilesSet.Remove("assets")
publicFilesSet.Remove("robots.txt")
for _, fn := range publicFilesSet.Values() {
log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn)
}
if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil {
log.Error(`Found legacy public asset "robots.txt" in CustomPath. Please move it to %s/public/robots.txt`, setting.CustomPath)
}
routers.InitWebInstalled(graceful.GetManager().HammerContext())
// We check that AppDataPath exists here (it should have been created during installation)

View File

@ -18,7 +18,7 @@ import (
"syscall"
"github.com/google/go-github/v53/github"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
)
@ -32,55 +32,55 @@ func main() {
app.ArgsUsage = "<PR-to-backport>"
app.Flags = []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "version",
Usage: "Version branch to backport on to",
},
cli.StringFlag{
&cli.StringFlag{
Name: "upstream",
Value: "origin",
Usage: "Upstream remote for the Gitea upstream",
},
cli.StringFlag{
&cli.StringFlag{
Name: "release-branch",
Value: "",
Usage: "Release branch to backport on. Will default to release/<version>",
},
cli.StringFlag{
&cli.StringFlag{
Name: "cherry-pick",
Usage: "SHA to cherry-pick as backport",
},
cli.StringFlag{
&cli.StringFlag{
Name: "backport-branch",
Usage: "Backport branch to backport on to (default: backport-<pr>-<version>",
},
cli.StringFlag{
&cli.StringFlag{
Name: "remote",
Value: "",
Usage: "Remote for your fork of the Gitea upstream",
},
cli.StringFlag{
&cli.StringFlag{
Name: "fork-user",
Value: "",
Usage: "Forked user name on Github",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "no-fetch",
Usage: "Set this flag to prevent fetch of remote branches",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "no-amend-message",
Usage: "Set this flag to prevent automatic amendment of the commit message",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "no-push",
Usage: "Set this flag to prevent pushing the backport up to your fork",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "no-xdg-open",
Usage: "Set this flag to not use xdg-open to open the PR URL",
},
cli.BoolFlag{
&cli.BoolFlag{
Name: "continue",
Usage: "Set this flag to continue from a git cherry-pick that has broken",
},
@ -151,7 +151,7 @@ func runBackport(c *cli.Context) error {
localReleaseBranch := path.Join(upstream, upstreamReleaseBranch)
args := c.Args()
args := c.Args().Slice()
if len(args) == 0 && pr == "" {
return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
} else if len(args) != 1 && pr == "" {

View File

@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
func main() {
@ -46,22 +46,22 @@ func main() {
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
on the configuration cheat sheet.`
app.Flags = []cli.Flag{
cli.StringFlag{
&cli.StringFlag{
Name: "custom-path, C",
Value: setting.CustomPath,
Usage: "Custom path file path",
},
cli.StringFlag{
&cli.StringFlag{
Name: "config, c",
Value: setting.CustomConf,
Usage: "Custom configuration file path",
},
cli.StringFlag{
&cli.StringFlag{
Name: "work-path, w",
Value: setting.AppWorkPath,
Usage: "Set the gitea working path",
},
cli.StringFlag{
&cli.StringFlag{
Name: "out, o",
Value: "",
Usage: "Destination file to write to",

View File

@ -12,7 +12,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; These values are environment-dependent but form the basis of a lot of values. They will be
;; reported as part of the default configuration when running `gitea --help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
;; reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
;;
;; - _`AppPath`_: This is the absolute path of the running gitea binary.
;; - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy:

View File

@ -15,7 +15,7 @@ If you are planning to contribute you'll want to download and install Hugo on
your local machine.
The installation of Hugo is out of the scope of this document, so please take
the [official install instructions](https://gohugo.io/overview/installing/) to
the [official install instructions](https://gohugo.io/installation/) to
get Hugo up and running.
## Development

View File

@ -40,7 +40,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
## Default Configuration (non-`app.ini` configuration)
These values are environment-dependent but form the basis of a lot of values. They will be
reported as part of the default configuration when running `gitea --help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
- _`AppPath`_: This is the absolute path of the running gitea binary.
- _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy:

View File

@ -56,7 +56,11 @@ is set under the "Configuration" tab on the site administration page.
To make Gitea serve custom public files (like pages and images), use the folder
`$GITEA_CUSTOM/public/` as the webroot. Symbolic links will be followed.
At the moment, only files in the `public/assets/` folder are served.
At the moment, only the following files are served:
- `public/robots.txt`
- files in the `public/.well-known/` folder
- files in the `public/assets/` folder
For example, a file `image.png` stored in `$GITEA_CUSTOM/public/assets/`, can be accessed with
the url `http://gitea.domain.tld/assets/image.png`.

View File

@ -18,7 +18,7 @@ menu:
# Secrets
Secrets allow you to store sensitive information in your user, organization or repository.
Secrets are available on Gitea 1.19+.
Secrets are available on Gitea 1.19+ and are only visible in 1.20+ when ACTIONS are enabled
# Naming your secrets

3
go.mod
View File

@ -98,7 +98,7 @@ require (
github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.11
github.com/urfave/cli v1.22.14
github.com/urfave/cli/v2 v2.25.7
github.com/xanzy/go-gitlab v0.86.0
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
@ -278,6 +278,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect

7
go.sum
View File

@ -80,7 +80,6 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.57.0 h1:X/QmUmFhpUvLgPSQb7fWOSi1wvqGn6tJ7w2a59c4xsg=
github.com/ClickHouse/ch-go v0.57.0/go.mod h1:DR3iBn7OrrDj+KeUp1LbdxLEUDbW+5Qwdl/qkc+PQ+Y=
@ -1162,8 +1161,8 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
@ -1198,6 +1197,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=

154
main.go
View File

@ -2,8 +2,7 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Gitea (git with a cup of tea) is a painless self-hosted Git Service.
package main // import "code.gitea.io/gitea"
package main
import (
"fmt"
@ -22,17 +21,13 @@ import (
_ "code.gitea.io/gitea/modules/markup/csv"
_ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode"
"github.com/urfave/cli"
)
// these flags will be set by the build flags
var (
// Version holds the current Gitea version
Version = "development"
// Tags holds the build tags used
Tags = ""
// MakeVersion holds the current Make version if built with make
MakeVersion = ""
Version = "development" // program version for this build
Tags = "" // the Golang build tags
MakeVersion = "" // "make" program version if built with make
)
func init() {
@ -41,110 +36,12 @@ func init() {
setting.AppStartTime = time.Now().UTC()
}
// cmdHelp is our own help subcommand with more information
// test cases:
// ./gitea help
// ./gitea -h
// ./gitea web help
// ./gitea web -h (due to cli lib limitation, this won't call our cmdHelp, so no extra info)
// ./gitea admin
// ./gitea admin help
// ./gitea admin auth help
// ./gitea -c /tmp/app.ini -h
// ./gitea -c /tmp/app.ini help
// ./gitea help -c /tmp/app.ini
// GITEA_WORK_DIR=/tmp ./gitea help
// GITEA_WORK_DIR=/tmp ./gitea help --work-path /tmp/other
// GITEA_WORK_DIR=/tmp ./gitea help --config /tmp/app-other.ini
var cmdHelp = cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *cli.Context) (err error) {
args := c.Args()
if args.Present() {
err = cli.ShowCommandHelp(c, args.First())
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
CustomPath: %s
ConfigFile: %s
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
return err
},
}
func main() {
app := cli.NewApp()
app := cmd.NewMainApp()
app.Name = "Gitea"
app.Usage = "A painless self-hosted Git service"
app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
app.Version = Version + formatBuiltWith()
app.EnableBashCompletion = true
// these sub-commands need to use config file
subCmdWithIni := []cli.Command{
cmd.CmdWeb,
cmd.CmdServ,
cmd.CmdHook,
cmd.CmdDump,
cmd.CmdAdmin,
cmd.CmdMigrate,
cmd.CmdKeys,
cmd.CmdConvert,
cmd.CmdDoctor,
cmd.CmdManager,
cmd.CmdEmbedded,
cmd.CmdMigrateStorage,
cmd.CmdDumpRepository,
cmd.CmdRestoreRepository,
cmd.CmdActions,
cmdHelp, // TODO: the "help" sub-command was used to show the more information for "work path" and "custom config", in the future, it should avoid doing so
}
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []cli.Command{
cmd.CmdCert,
cmd.CmdGenerate,
cmd.CmdDocs,
}
// 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.
globalFlags := []cli.Flag{
cli.HelpFlag,
cli.StringFlag{
Name: "custom-path, C",
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
cli.StringFlag{
Name: "config, c",
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
cli.StringFlag{
Name: "work-path, w",
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
}
// Set the default to be equivalent to cmdWeb and add the default flags
app.Flags = append(app.Flags, globalFlags...)
app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) // TODO: the web flags polluted the global flags, they are not really global flags
app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = cmd.PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithIni {
prepareSubcommands(&subCmdWithIni[i], globalFlags)
}
app.Commands = append(app.Commands, subCmdWithIni...)
app.Commands = append(app.Commands, subCmdStandalone...)
err := app.Run(os.Args)
if err != nil {
@ -154,45 +51,6 @@ func main() {
log.GetManager().Close()
}
func prepareSubcommands(command *cli.Command, defaultFlags []cli.Flag) {
command.Flags = append(command.Flags, defaultFlags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
command.Subcommands = append(command.Subcommands, cmdHelp)
}
for i := range command.Subcommands {
prepareSubcommands(&command.Subcommands[i], defaultFlags)
}
}
// 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 any) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
var args setting.ArgWorkPathAndCustomConf
curCtx := ctx
for curCtx != nil {
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")
}
curCtx = curCtx.Parent()
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if ctx.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp.Action.(func(ctx *cli.Context) error)(ctx)
}
return action.(func(*cli.Context) error)(ctx)
}
}
func formatBuiltWith() string {
version := runtime.Version()
if len(MakeVersion) > 0 {

View File

@ -31,7 +31,7 @@ func init() {
// ActionArtifact is a file that is stored in the artifact storage.
type ActionArtifact struct {
ID int64 `xorm:"pk autoincr"`
RunID int64 `xorm:"index UNIQUE(runid_name)"` // The run id of the artifact
RunID int64 `xorm:"index unique(runid_name_path)"` // The run id of the artifact
RunnerID int64
RepoID int64 `xorm:"index"`
OwnerID int64
@ -40,27 +40,28 @@ type ActionArtifact struct {
FileSize int64 // The size of the artifact in bytes
FileCompressedSize int64 // The size of the artifact in bytes after gzip compression
ContentEncoding string // The content encoding of the artifact
ArtifactPath string // The path to the artifact when runner uploads it
ArtifactName string `xorm:"UNIQUE(runid_name)"` // The name of the artifact when runner uploads it
Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it
ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it
Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated index"`
}
// CreateArtifact create a new artifact with task info or get same named artifact in the same run
func CreateArtifact(ctx context.Context, t *ActionTask, artifactName string) (*ActionArtifact, error) {
func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string) (*ActionArtifact, error) {
if err := t.LoadJob(ctx); err != nil {
return nil, err
}
artifact, err := getArtifactByArtifactName(ctx, t.Job.RunID, artifactName)
artifact, err := getArtifactByNameAndPath(ctx, t.Job.RunID, artifactName, artifactPath)
if errors.Is(err, util.ErrNotExist) {
artifact := &ActionArtifact{
RunID: t.Job.RunID,
RunnerID: t.RunnerID,
RepoID: t.RepoID,
OwnerID: t.OwnerID,
CommitSHA: t.CommitSHA,
Status: ArtifactStatusUploadPending,
ArtifactName: artifactName,
ArtifactPath: artifactPath,
RunID: t.Job.RunID,
RunnerID: t.RunnerID,
RepoID: t.RepoID,
OwnerID: t.OwnerID,
CommitSHA: t.CommitSHA,
Status: ArtifactStatusUploadPending,
}
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
return nil, err
@ -72,9 +73,9 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName string) (*A
return artifact, nil
}
func getArtifactByArtifactName(ctx context.Context, runID int64, name string) (*ActionArtifact, error) {
func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath string) (*ActionArtifact, error) {
var art ActionArtifact
has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ?", runID, name).Get(&art)
has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ?", runID, name, fpath).Get(&art)
if err != nil {
return nil, err
} else if !has {
@ -109,14 +110,42 @@ func ListArtifactsByRunID(ctx context.Context, runID int64) ([]*ActionArtifact,
return arts, db.GetEngine(ctx).Where("run_id=?", runID).Find(&arts)
}
// ListArtifactsByRunIDAndArtifactName returns an artifacts of a run by artifact name
func ListArtifactsByRunIDAndArtifactName(ctx context.Context, runID int64, artifactName string) ([]*ActionArtifact, error) {
arts := make([]*ActionArtifact, 0, 10)
return arts, db.GetEngine(ctx).Where("run_id=? AND artifact_name=?", runID, artifactName).Find(&arts)
}
// ListUploadedArtifactsByRunID returns all uploaded artifacts of a run
func ListUploadedArtifactsByRunID(ctx context.Context, runID int64) ([]*ActionArtifact, error) {
arts := make([]*ActionArtifact, 0, 10)
return arts, db.GetEngine(ctx).Where("run_id=? AND status=?", runID, ArtifactStatusUploadConfirmed).Find(&arts)
}
// ActionArtifactMeta is the meta data of an artifact
type ActionArtifactMeta struct {
ArtifactName string
FileSize int64
}
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) {
arts := make([]*ActionArtifactMeta, 0, 10)
return arts, db.GetEngine(ctx).Table("action_artifact").
Where("run_id=? AND status=?", runID, ArtifactStatusUploadConfirmed).
GroupBy("artifact_name").
Select("artifact_name, sum(file_size) as file_size").
Find(&arts)
}
// ListArtifactsByRepoID returns all artifacts of a repo
func ListArtifactsByRepoID(ctx context.Context, repoID int64) ([]*ActionArtifact, error) {
arts := make([]*ActionArtifact, 0, 10)
return arts, db.GetEngine(ctx).Where("repo_id=?", repoID).Find(&arts)
}
// ListArtifactsByRunIDAndName returns artifacts by name of a run
func ListArtifactsByRunIDAndName(ctx context.Context, runID int64, name string) ([]*ActionArtifact, error) {
arts := make([]*ActionArtifact, 0, 10)
return arts, db.GetEngine(ctx).Where("run_id=? AND artifact_name=?", runID, name).Find(&arts)
}

View File

@ -391,10 +391,10 @@ func (a *Action) GetIssueInfos() []string {
}
// GetIssueTitle returns the title of first issue associated
// with the action.
// with the action. This function will be invoked in template so keep db.DefaultContext here
func (a *Action) GetIssueTitle() string {
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
issue, err := issues_model.GetIssueByIndex(a.RepoID, index)
issue, err := issues_model.GetIssueByIndex(db.DefaultContext, a.RepoID, index)
if err != nil {
log.Error("GetIssueByIndex: %v", err)
return "500 when get issue"
@ -404,9 +404,9 @@ func (a *Action) GetIssueTitle() string {
// GetIssueContent returns the content of first issue associated with
// this action.
func (a *Action) GetIssueContent() string {
func (a *Action) GetIssueContent(ctx context.Context) string {
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
issue, err := issues_model.GetIssueByIndex(a.RepoID, index)
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
if err != nil {
log.Error("GetIssueByIndex: %v", err)
return "500 when get issue"

View File

@ -47,21 +47,21 @@ type ActivityStats struct {
func GetActivityStats(ctx context.Context, repo *repo_model.Repository, timeFrom time.Time, releases, issues, prs, code bool) (*ActivityStats, error) {
stats := &ActivityStats{Code: &git.CodeActivityStats{}}
if releases {
if err := stats.FillReleases(repo.ID, timeFrom); err != nil {
if err := stats.FillReleases(ctx, repo.ID, timeFrom); err != nil {
return nil, fmt.Errorf("FillReleases: %w", err)
}
}
if prs {
if err := stats.FillPullRequests(repo.ID, timeFrom); err != nil {
if err := stats.FillPullRequests(ctx, repo.ID, timeFrom); err != nil {
return nil, fmt.Errorf("FillPullRequests: %w", err)
}
}
if issues {
if err := stats.FillIssues(repo.ID, timeFrom); err != nil {
if err := stats.FillIssues(ctx, repo.ID, timeFrom); err != nil {
return nil, fmt.Errorf("FillIssues: %w", err)
}
}
if err := stats.FillUnresolvedIssues(repo.ID, timeFrom, issues, prs); err != nil {
if err := stats.FillUnresolvedIssues(ctx, repo.ID, timeFrom, issues, prs); err != nil {
return nil, fmt.Errorf("FillUnresolvedIssues: %w", err)
}
if code {
@ -205,41 +205,41 @@ func (stats *ActivityStats) PublishedReleaseCount() int {
}
// FillPullRequests returns pull request information for activity page
func (stats *ActivityStats) FillPullRequests(repoID int64, fromTime time.Time) error {
func (stats *ActivityStats) FillPullRequests(ctx context.Context, repoID int64, fromTime time.Time) error {
var err error
var count int64
// Merged pull requests
sess := pullRequestsForActivityStatement(repoID, fromTime, true)
sess := pullRequestsForActivityStatement(ctx, repoID, fromTime, true)
sess.OrderBy("pull_request.merged_unix DESC")
stats.MergedPRs = make(issues_model.PullRequestList, 0)
if err = sess.Find(&stats.MergedPRs); err != nil {
return err
}
if err = stats.MergedPRs.LoadAttributes(); err != nil {
if err = stats.MergedPRs.LoadAttributes(ctx); err != nil {
return err
}
// Merged pull request authors
sess = pullRequestsForActivityStatement(repoID, fromTime, true)
sess = pullRequestsForActivityStatement(ctx, repoID, fromTime, true)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("pull_request").Get(&count); err != nil {
return err
}
stats.MergedPRAuthorCount = count
// Opened pull requests
sess = pullRequestsForActivityStatement(repoID, fromTime, false)
sess = pullRequestsForActivityStatement(ctx, repoID, fromTime, false)
sess.OrderBy("issue.created_unix ASC")
stats.OpenedPRs = make(issues_model.PullRequestList, 0)
if err = sess.Find(&stats.OpenedPRs); err != nil {
return err
}
if err = stats.OpenedPRs.LoadAttributes(); err != nil {
if err = stats.OpenedPRs.LoadAttributes(ctx); err != nil {
return err
}
// Opened pull request authors
sess = pullRequestsForActivityStatement(repoID, fromTime, false)
sess = pullRequestsForActivityStatement(ctx, repoID, fromTime, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("pull_request").Get(&count); err != nil {
return err
}
@ -248,8 +248,8 @@ func (stats *ActivityStats) FillPullRequests(repoID int64, fromTime time.Time) e
return nil
}
func pullRequestsForActivityStatement(repoID int64, fromTime time.Time, merged bool) *xorm.Session {
sess := db.GetEngine(db.DefaultContext).Where("pull_request.base_repo_id=?", repoID).
func pullRequestsForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, merged bool) *xorm.Session {
sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", repoID).
Join("INNER", "issue", "pull_request.issue_id = issue.id")
if merged {
@ -264,12 +264,12 @@ func pullRequestsForActivityStatement(repoID int64, fromTime time.Time, merged b
}
// FillIssues returns issue information for activity page
func (stats *ActivityStats) FillIssues(repoID int64, fromTime time.Time) error {
func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTime time.Time) error {
var err error
var count int64
// Closed issues
sess := issuesForActivityStatement(repoID, fromTime, true, false)
sess := issuesForActivityStatement(ctx, repoID, fromTime, true, false)
sess.OrderBy("issue.closed_unix DESC")
stats.ClosedIssues = make(issues_model.IssueList, 0)
if err = sess.Find(&stats.ClosedIssues); err != nil {
@ -277,14 +277,14 @@ func (stats *ActivityStats) FillIssues(repoID int64, fromTime time.Time) error {
}
// Closed issue authors
sess = issuesForActivityStatement(repoID, fromTime, true, false)
sess = issuesForActivityStatement(ctx, repoID, fromTime, true, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
return err
}
stats.ClosedIssueAuthorCount = count
// New issues
sess = issuesForActivityStatement(repoID, fromTime, false, false)
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
sess.OrderBy("issue.created_unix ASC")
stats.OpenedIssues = make(issues_model.IssueList, 0)
if err = sess.Find(&stats.OpenedIssues); err != nil {
@ -292,7 +292,7 @@ func (stats *ActivityStats) FillIssues(repoID int64, fromTime time.Time) error {
}
// Opened issue authors
sess = issuesForActivityStatement(repoID, fromTime, false, false)
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
return err
}
@ -302,12 +302,12 @@ func (stats *ActivityStats) FillIssues(repoID int64, fromTime time.Time) error {
}
// FillUnresolvedIssues returns unresolved issue and pull request information for activity page
func (stats *ActivityStats) FillUnresolvedIssues(repoID int64, fromTime time.Time, issues, prs bool) error {
func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int64, fromTime time.Time, issues, prs bool) error {
// Check if we need to select anything
if !issues && !prs {
return nil
}
sess := issuesForActivityStatement(repoID, fromTime, false, true)
sess := issuesForActivityStatement(ctx, repoID, fromTime, false, true)
if !issues || !prs {
sess.And("issue.is_pull = ?", prs)
}
@ -316,8 +316,8 @@ func (stats *ActivityStats) FillUnresolvedIssues(repoID int64, fromTime time.Tim
return sess.Find(&stats.UnresolvedIssues)
}
func issuesForActivityStatement(repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
sess := db.GetEngine(db.DefaultContext).Where("issue.repo_id = ?", repoID).
func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_closed = ?", closed)
if !unresolved {
@ -336,12 +336,12 @@ func issuesForActivityStatement(repoID int64, fromTime time.Time, closed, unreso
}
// FillReleases returns release information for activity page
func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error {
func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, fromTime time.Time) error {
var err error
var count int64
// Published releases list
sess := releasesForActivityStatement(repoID, fromTime)
sess := releasesForActivityStatement(ctx, repoID, fromTime)
sess.OrderBy("release.created_unix DESC")
stats.PublishedReleases = make([]*repo_model.Release, 0)
if err = sess.Find(&stats.PublishedReleases); err != nil {
@ -349,7 +349,7 @@ func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error
}
// Published releases authors
sess = releasesForActivityStatement(repoID, fromTime)
sess = releasesForActivityStatement(ctx, repoID, fromTime)
if _, err = sess.Select("count(distinct release.publisher_id) as `count`").Table("release").Get(&count); err != nil {
return err
}
@ -358,8 +358,8 @@ func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error
return nil
}
func releasesForActivityStatement(repoID int64, fromTime time.Time) *xorm.Session {
return db.GetEngine(db.DefaultContext).Where("release.repo_id = ?", repoID).
func releasesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
return db.GetEngine(ctx).Where("release.repo_id = ?", repoID).
And("release.is_draft = ?", false).
And("release.created_unix >= ?", fromTime.Unix())
}

View File

@ -31,10 +31,6 @@ func TestGetCommitStatuses(t *testing.T) {
assert.Equal(t, structs.CommitStatusPending, statuses[0].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[0].APIURL(db.DefaultContext))
assert.Equal(t, "cov/awesomeness", statuses[1].Context)
assert.Equal(t, structs.CommitStatusWarning, statuses[1].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[1].APIURL(db.DefaultContext))
assert.Equal(t, "cov/awesomeness", statuses[2].Context)
assert.Equal(t, structs.CommitStatusSuccess, statuses[2].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[2].APIURL(db.DefaultContext))

View File

@ -465,8 +465,9 @@ func (comments CommentList) loadReviews(ctx context.Context) error {
return nil
}
// loadAttributes loads all attributes
func (comments CommentList) loadAttributes(ctx context.Context) (err error) {
// LoadAttributes loads attributes of the comments, except for attachments and
// comments
func (comments CommentList) LoadAttributes(ctx context.Context) (err error) {
if err = comments.LoadPosters(ctx); err != nil {
return err
}
@ -501,9 +502,3 @@ func (comments CommentList) loadAttributes(ctx context.Context) (err error) {
return comments.loadDependentIssues(ctx)
}
// LoadAttributes loads attributes of the comments, except for attachments and
// comments
func (comments CommentList) LoadAttributes() error {
return comments.loadAttributes(db.DefaultContext)
}

View File

@ -354,7 +354,7 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
return err
}
if err = issue.Comments.loadAttributes(ctx); err != nil {
if err = issue.Comments.LoadAttributes(ctx); err != nil {
return err
}
if issue.IsTimetrackerEnabled(ctx) {
@ -502,7 +502,7 @@ func (issue *Issue) GetLastEventLabelFake() string {
}
// GetIssueByIndex returns raw issue without loading attributes by index in a repository.
func GetIssueByIndex(repoID, index int64) (*Issue, error) {
func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
if index < 1 {
return nil, ErrIssueNotExist{}
}
@ -510,7 +510,7 @@ func GetIssueByIndex(repoID, index int64) (*Issue, error) {
RepoID: repoID,
Index: index,
}
has, err := db.GetEngine(db.DefaultContext).Get(issue)
has, err := db.GetEngine(ctx).Get(issue)
if err != nil {
return nil, err
} else if !has {
@ -520,12 +520,12 @@ func GetIssueByIndex(repoID, index int64) (*Issue, error) {
}
// GetIssueWithAttrsByIndex returns issue by index in a repository.
func GetIssueWithAttrsByIndex(repoID, index int64) (*Issue, error) {
issue, err := GetIssueByIndex(repoID, index)
func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
issue, err := GetIssueByIndex(ctx, repoID, index)
if err != nil {
return nil, err
}
return issue, issue.LoadAttributes(db.DefaultContext)
return issue, issue.LoadAttributes(ctx)
}
// GetIssueByID returns an issue by given ID.
@ -846,7 +846,7 @@ func GetPinnedIssues(ctx context.Context, repoID int64, isPull bool) ([]*Issue,
return nil, err
}
err = IssueList(issues).LoadAttributes()
err = IssueList(issues).LoadAttributes(ctx)
if err != nil {
return nil, err
}

View File

@ -526,7 +526,7 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
}
// loadAttributes loads all attributes, expect for attachments and comments
func (issues IssueList) loadAttributes(ctx context.Context) error {
func (issues IssueList) LoadAttributes(ctx context.Context) error {
if _, err := issues.LoadRepositories(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: LoadRepositories: %w", err)
}
@ -562,12 +562,6 @@ func (issues IssueList) loadAttributes(ctx context.Context) error {
return nil
}
// LoadAttributes loads attributes of the issues, except for attachments and
// comments
func (issues IssueList) LoadAttributes() error {
return issues.loadAttributes(db.DefaultContext)
}
// LoadComments loads comments
func (issues IssueList) LoadComments(ctx context.Context) error {
return issues.loadComments(ctx, builder.NewCond())

View File

@ -39,7 +39,7 @@ func TestIssueList_LoadAttributes(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
}
assert.NoError(t, issueList.LoadAttributes())
assert.NoError(t, issueList.LoadAttributes(db.DefaultContext))
for _, issue := range issueList {
assert.EqualValues(t, issue.RepoID, issue.Repo.ID)
for _, label := range issue.Labels {

View File

@ -440,7 +440,7 @@ func Issues(ctx context.Context, opts *IssuesOptions) ([]*Issue, error) {
return nil, fmt.Errorf("unable to query Issues: %w", err)
}
if err := issues.LoadAttributes(); err != nil {
if err := issues.LoadAttributes(ctx); err != nil {
return nil, fmt.Errorf("unable to LoadAttributes for Issues: %w", err)
}

View File

@ -51,16 +51,16 @@ func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xor
}
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2)
sess := db.GetEngine(db.DefaultContext).
sess := db.GetEngine(ctx).
Join("INNER", "issue", "issue.id = pull_request.issue_id").
Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?", repoID, branch, false, false, PullRequestFlowGithub)
return prs, sess.Find(&prs)
}
// CanMaintainerWriteToBranch check whether user is a maintainer and could write to the branch
func CanMaintainerWriteToBranch(p access_model.Permission, branch string, user *user_model.User) bool {
func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission, branch string, user *user_model.User) bool {
if p.CanWrite(unit.TypeCode) {
return true
}
@ -69,18 +69,18 @@ func CanMaintainerWriteToBranch(p access_model.Permission, branch string, user *
return false
}
prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch)
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, p.Units[0].RepoID, branch)
if err != nil {
return false
}
for _, pr := range prs {
if pr.AllowMaintainerEdit {
err = pr.LoadBaseRepo(db.DefaultContext)
err = pr.LoadBaseRepo(ctx)
if err != nil {
continue
}
prPerm, err := access_model.GetUserRepoPermission(db.DefaultContext, pr.BaseRepo, user)
prPerm, err := access_model.GetUserRepoPermission(ctx, pr.BaseRepo, user)
if err != nil {
continue
}
@ -104,9 +104,9 @@ func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch
// GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
// by given base information (repo and branch).
func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) {
prs := make([]*PullRequest, 0, 2)
return prs, db.GetEngine(db.DefaultContext).
return prs, db.GetEngine(ctx).
Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
repoID, branch, false, false).
OrderBy("issue.updated_unix DESC").
@ -154,7 +154,7 @@ func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest,
// PullRequestList defines a list of pull requests
type PullRequestList []*PullRequest
func (prs PullRequestList) loadAttributes(ctx context.Context) error {
func (prs PullRequestList) LoadAttributes(ctx context.Context) error {
if len(prs) == 0 {
return nil
}
@ -199,8 +199,3 @@ func (prs PullRequestList) GetIssueIDs() []int64 {
}
return issueIDs
}
// LoadAttributes load all the prs attributes
func (prs PullRequestList) LoadAttributes() error {
return prs.loadAttributes(db.DefaultContext)
}

View File

@ -148,7 +148,7 @@ func TestHasUnmergedPullRequestsByHeadInfo(t *testing.T) {
func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(1, "branch2")
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(db.DefaultContext, 1, "branch2")
assert.NoError(t, err)
assert.Len(t, prs, 1)
for _, pr := range prs {
@ -159,7 +159,7 @@ func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) {
func TestGetUnmergedPullRequestsByBaseInfo(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(1, "master")
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(db.DefaultContext, 1, "master")
assert.NoError(t, err)
assert.Len(t, prs, 1)
pr := prs[0]
@ -242,13 +242,13 @@ func TestPullRequestList_LoadAttributes(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
}
assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes())
assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext))
for _, pr := range prs {
assert.NotNil(t, pr.Issue)
assert.Equal(t, pr.IssueID, pr.Issue.ID)
}
assert.NoError(t, issues_model.PullRequestList([]*issues_model.PullRequest{}).LoadAttributes())
assert.NoError(t, issues_model.PullRequestList([]*issues_model.PullRequest{}).LoadAttributes(db.DefaultContext))
}
// TODO TestAddTestPullRequestTask

View File

@ -43,11 +43,7 @@ func (t *TrackedTime) AfterLoad() {
}
// LoadAttributes load Issue, User
func (t *TrackedTime) LoadAttributes() (err error) {
return t.loadAttributes(db.DefaultContext)
}
func (t *TrackedTime) loadAttributes(ctx context.Context) (err error) {
func (t *TrackedTime) LoadAttributes(ctx context.Context) (err error) {
// Load the issue
if t.Issue == nil {
t.Issue, err = GetIssueByID(ctx, t.IssueID)
@ -76,9 +72,9 @@ func (t *TrackedTime) loadAttributes(ctx context.Context) (err error) {
}
// LoadAttributes load Issue, User
func (tl TrackedTimeList) LoadAttributes() error {
func (tl TrackedTimeList) LoadAttributes(ctx context.Context) error {
for _, t := range tl {
if err := t.LoadAttributes(); err != nil {
if err := t.LoadAttributes(ctx); err != nil {
return err
}
}
@ -143,8 +139,8 @@ func GetTrackedTimes(ctx context.Context, options *FindTrackedTimesOptions) (tra
}
// CountTrackedTimes returns count of tracked times that fit to the given options.
func CountTrackedTimes(opts *FindTrackedTimesOptions) (int64, error) {
sess := db.GetEngine(db.DefaultContext).Where(opts.toCond())
func CountTrackedTimes(ctx context.Context, opts *FindTrackedTimesOptions) (int64, error) {
sess := db.GetEngine(ctx).Where(opts.toCond())
if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
}
@ -157,8 +153,8 @@ func GetTrackedSeconds(ctx context.Context, opts FindTrackedTimesOptions) (track
}
// AddTime will add the given time (in seconds) to the issue
func AddTime(user *user_model.User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
func AddTime(ctx context.Context, user *user_model.User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
@ -276,7 +272,7 @@ func DeleteTime(t *TrackedTime) error {
}
defer committer.Close()
if err := t.loadAttributes(ctx); err != nil {
if err := t.LoadAttributes(ctx); err != nil {
return err
}

View File

@ -25,7 +25,7 @@ func TestAddTime(t *testing.T) {
assert.NoError(t, err)
// 3661 = 1h 1min 1s
trackedTime, err := issues_model.AddTime(user3, issue1, 3661, time.Now())
trackedTime, err := issues_model.AddTime(db.DefaultContext, user3, issue1, 3661, time.Now())
assert.NoError(t, err)
assert.Equal(t, int64(3), trackedTime.UserID)
assert.Equal(t, int64(1), trackedTime.IssueID)

View File

@ -128,8 +128,8 @@ func InsertIssueComments(comments []*issues_model.Comment) error {
}
// InsertPullRequests inserted pull requests
func InsertPullRequests(prs ...*issues_model.PullRequest) error {
ctx, committer, err := db.TxContext(db.DefaultContext)
func InsertPullRequests(ctx context.Context, prs ...*issues_model.PullRequest) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}

View File

@ -122,7 +122,7 @@ func TestMigrate_InsertPullRequests(t *testing.T) {
Issue: i,
}
err := InsertPullRequests(p)
err := InsertPullRequests(db.DefaultContext, p)
assert.NoError(t, err)
_ = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: i.ID})

View File

@ -511,6 +511,10 @@ var migrations = []Migration{
NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable),
// v264 -> v265
NewMigration("Add branch table", v1_21.AddBranchTable),
// v265 -> v266
NewMigration("Alter Actions Artifact table", v1_21.AlterActionArtifactTable),
// v266 -> v267
NewMigration("Reduce commit status", v1_21.ReduceCommitStatus),
}
// GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,19 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_21 //nolint
import (
"xorm.io/xorm"
)
func AlterActionArtifactTable(x *xorm.Engine) error {
// ActionArtifact is a file that is stored in the artifact storage.
type ActionArtifact struct {
RunID int64 `xorm:"index unique(runid_name_path)"` // The run id of the artifact
ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it
ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when
}
return x.Sync(new(ActionArtifact))
}

View File

@ -0,0 +1,26 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_21 //nolint
import (
"xorm.io/xorm"
)
func ReduceCommitStatus(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if _, err := sess.Exec(`UPDATE commit_status SET state='pending' WHERE state='running'`); err != nil {
return err
}
if _, err := sess.Exec(`UPDATE commit_status SET state='failure' WHERE state='warning'`); err != nil {
return err
}
return sess.Commit()
}

View File

@ -35,7 +35,7 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) {
// CanEnableEditor checks if the user is allowed to write to the branch of the repo
func CanEnableEditor() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) {
if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
ctx.NotFound("CanWriteToBranch denies permission", nil)
return
}

View File

@ -66,13 +66,13 @@ type Repository struct {
}
// CanWriteToBranch checks if the branch is writable by the user
func (r *Repository) CanWriteToBranch(user *user_model.User, branch string) bool {
return issues_model.CanMaintainerWriteToBranch(r.Permission, branch, user)
func (r *Repository) CanWriteToBranch(ctx context.Context, user *user_model.User, branch string) bool {
return issues_model.CanMaintainerWriteToBranch(ctx, r.Permission, branch, user)
}
// CanEnableEditor returns true if repository is editable and user has proper access level.
func (r *Repository) CanEnableEditor(user *user_model.User) bool {
return r.IsViewBranch && r.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
func (r *Repository) CanEnableEditor(ctx context.Context, user *user_model.User) bool {
return r.IsViewBranch && r.CanWriteToBranch(ctx, user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
}
// CanCreateBranch returns true if repository is editable and user has proper access level.
@ -118,7 +118,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use
sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
canCommit := r.CanEnableEditor(doer) && userCanPush
canCommit := r.CanEnableEditor(ctx, doer) && userCanPush
if requireSigned {
canCommit = canCommit && sign
}
@ -134,7 +134,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use
return CanCommitToBranchResults{
CanCommitToBranch: canCommit,
EditorEnabled: r.CanEnableEditor(doer),
EditorEnabled: r.CanEnableEditor(ctx, doer),
UserCanPush: userCanPush,
RequireSigned: requireSigned,
WillSign: sign,
@ -660,13 +660,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
return cancel
}
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("GetTagNamesByRepoID", err)
return cancel
}
ctx.Data["Tags"] = tags
branchOpts := git_model.FindBranchOptions{
RepoIDs: []int64{ctx.Repo.Repository.ID},
IsDeletedBranch: util.OptionalBoolFalse,
@ -680,7 +673,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
return cancel
}
// non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
// non-empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
if branchesTotal == 0 { // fallback to do a sync immediately
branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
@ -689,24 +682,19 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
}
}
// FIXME: use paganation and async loading
branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch}
brs, err := git_model.FindBranchNames(ctx, branchOpts)
if err != nil {
ctx.ServerError("GetBranches", err)
return cancel
}
// always put default branch on the top
ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...)
ctx.Data["BranchesCount"] = branchesTotal
// If not branch selected, try default one.
// If default branch doesn't exist, fall back to some other branch.
// If no branch is set in the request URL, try to guess a default one.
if len(ctx.Repo.BranchName) == 0 {
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
} else if len(brs) > 0 {
ctx.Repo.BranchName = brs[0]
} else {
ctx.Repo.BranchName, _ = gitRepo.GetDefaultBranch()
if ctx.Repo.BranchName == "" {
// If it still can't get a default branch, fall back to default branch from setting.
// Something might be wrong. Either site admin should fix the repo sync or Gitea should fix a potential bug.
ctx.Repo.BranchName = setting.Repository.DefaultBranch
}
}
ctx.Repo.RefName = ctx.Repo.BranchName
}

View File

@ -28,27 +28,15 @@ func AssetFS() *assetfs.LayeredFS {
return assetfs.Layered(CustomAssets(), BuiltinAssets())
}
// AssetsHandlerFunc implements the static handler for serving custom or original assets.
func AssetsHandlerFunc(prefix string) http.HandlerFunc {
// FileHandlerFunc implements the static handler for serving files in "public" assets
func FileHandlerFunc() http.HandlerFunc {
assetFS := AssetFS()
prefix = strings.TrimSuffix(prefix, "/") + "/"
return func(resp http.ResponseWriter, req *http.Request) {
subPath := req.URL.Path
if !strings.HasPrefix(subPath, prefix) {
return
}
subPath = strings.TrimPrefix(subPath, prefix)
if req.Method != "GET" && req.Method != "HEAD" {
resp.WriteHeader(http.StatusNotFound)
return
}
if handleRequest(resp, req, assetFS, subPath) {
return
}
resp.WriteHeader(http.StatusNotFound)
handleRequest(resp, req, assetFS, req.URL.Path)
}
}
@ -71,16 +59,17 @@ func setWellKnownContentType(w http.ResponseWriter, file string) {
}
}
func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) {
// actually, fs (http.FileSystem) is designed to be a safe interface, relative paths won't bypass its parent directory, it's also fine to do a clean here
f, err := fs.Open(util.PathJoinRelX("assets", file))
f, err := fs.Open(util.PathJoinRelX(file))
if err != nil {
if os.IsNotExist(err) {
return false
w.WriteHeader(http.StatusNotFound)
return
}
w.WriteHeader(http.StatusInternalServerError)
log.Error("[Static] Open %q failed: %v", file, err)
return true
return
}
defer f.Close()
@ -88,17 +77,16 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem,
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Error("[Static] %q exists, but fails to open: %v", file, err)
return true
return
}
// Try to serve index file
// need to serve index file? (no at the moment)
if fi.IsDir() {
w.WriteHeader(http.StatusNotFound)
return true
return
}
serveContent(w, req, fi, fi.ModTime(), f)
return true
}
type GzipBytesProvider interface {

View File

@ -303,21 +303,23 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo
// GenerateRepoOptions contains the template units to generate
type GenerateRepoOptions struct {
Name string
DefaultBranch string
Description string
Private bool
GitContent bool
Topics bool
GitHooks bool
Webhooks bool
Avatar bool
IssueLabels bool
Name string
DefaultBranch string
Description string
Private bool
GitContent bool
Topics bool
GitHooks bool
Webhooks bool
Avatar bool
IssueLabels bool
ProtectedBranch bool
}
// IsValid checks whether at least one option is chosen for generation
func (gro GenerateRepoOptions) IsValid() bool {
return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels // or other items as they are added
return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar ||
gro.IssueLabels || gro.ProtectedBranch // or other items as they are added
}
// GenerateRepository generates a repository from a template

View File

@ -349,9 +349,4 @@ func loadServerFrom(rootCfg ConfigProvider) {
default:
LandingPageURL = LandingPage(landingPage)
}
HasRobotsTxt, err = util.IsFile(path.Join(CustomPath, "robots.txt"))
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", path.Join(CustomPath, "robots.txt"), err)
}
}

View File

@ -16,26 +16,17 @@ const (
CommitStatusError CommitStatusState = "error"
// CommitStatusFailure is for when the CommitStatus is Failure
CommitStatusFailure CommitStatusState = "failure"
// CommitStatusWarning is for when the CommitStatus is Warning
CommitStatusWarning CommitStatusState = "warning"
// CommitStatusRunning is for when the CommitStatus is Running
CommitStatusRunning CommitStatusState = "running"
)
// NoBetterThan returns true if this State is no better than the given State
func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool {
switch css {
case CommitStatusError:
return true
case CommitStatusFailure:
return css2 != CommitStatusError
case CommitStatusWarning:
return css2 != CommitStatusError && css2 != CommitStatusFailure
case CommitStatusPending:
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning
default:
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning && css2 != CommitStatusPending
commitStatusPriorities := map[CommitStatusState]int{
CommitStatusError: 0,
CommitStatusFailure: 1,
CommitStatusPending: 2,
CommitStatusSuccess: 3,
}
return commitStatusPriorities[css] <= commitStatusPriorities[css2]
}
// IsPending represents if commit status state is pending
@ -57,8 +48,3 @@ func (css CommitStatusState) IsError() bool {
func (css CommitStatusState) IsFailure() bool {
return css == CommitStatusFailure
}
// IsWarning represents if commit status state is warning
func (css CommitStatusState) IsWarning() bool {
return css == CommitStatusWarning
}

View File

@ -238,6 +238,8 @@ type GenerateRepoOption struct {
Avatar bool `json:"avatar"`
// include labels in template repo
Labels bool `json:"labels"`
// include protected branches in template repo
ProtectedBranch bool `json:"protected_branch"`
}
// CreateBranchRepoOption options when creating a branch in a repository

View File

@ -1448,7 +1448,7 @@ issues.context.quote_reply = Quote Reply
issues.context.reference_issue = Reference in New Issue
issues.context.edit = Edit
issues.context.delete = Delete
issues.no_content = There is no content yet.
issues.no_content = No description provided.
issues.close = Close Issue
issues.comment_pull_merged_at = merged commit %[1]s into %[2]s %[3]s
issues.comment_manually_pull_merged_at = manually merged commit %[1]s into %[2]s %[3]s

View File

@ -639,6 +639,9 @@ cancel=Cancelar
language=Idioma
ui=Tema
hidden_comment_types=Tipos de comentários ocultos
hidden_comment_types_description=Os tipos de comentários marcados aqui não serão exibidos nas páginas de issues. Marcar "Rótulo", por exemplo, remove todos os comentários "<usuário> adicionou/removeu <rótulo>".
hidden_comment_types.ref_tooltip=Comentários onde este issue foi referenciado de outro issue/commit/…
hidden_comment_types.issue_ref_tooltip=Comentários onde o usuário altera o branch/tag associado ao issue
comment_type_group_reference=Referência
comment_type_group_label=Rótulo
comment_type_group_milestone=Marco
@ -759,6 +762,7 @@ key_content=Conteúdo
principal_content=Conteúdo
add_key_success=A chave SSH "%s" foi adicionada.
add_gpg_key_success=A chave GPG "%s" foi adicionada.
add_principal_success=O principal "%s" foi adicionado ao certificado SSH.
delete_key=Remover
ssh_key_deletion=Remover a chave SSH
gpg_key_deletion=Remover a chave GPG
@ -821,6 +825,7 @@ create_oauth2_application_success=Você criou com sucesso um novo aplicativo OAu
update_oauth2_application_success=Você alterou com sucesso o aplicativo OAuth2.
oauth2_application_name=Nome do aplicativo
oauth2_confidential_client=Cliente Confidencial. Selecione para aplicativos que mantêm a confidencialidade do segredo, como aplicativos web. Não selecione para aplicativos nativos, incluindo aplicativos desktop e celulares.
oauth2_redirect_uris=URIs de redirecionamento. Por favor use uma nova linha para cada URI.
save_application=Salvar
oauth2_client_id=Client ID
oauth2_client_secret=Client Secret
@ -1897,6 +1902,7 @@ settings.hooks=Webhooks
settings.githooks=Hooks do Git
settings.basic_settings=Configurações básicas
settings.mirror_settings=Opções de espelhamento
settings.mirror_settings.docs=Configure o seu repositório para sincronizar automaticamente commits, tags e branches de outro repositório.
settings.mirror_settings.docs.doc_link_title=Como posso espelhar repositórios?
settings.mirror_settings.mirrored_repository=Repositório espelhado
settings.mirror_settings.direction=Sentido
@ -1910,6 +1916,7 @@ settings.sync_mirror=Sincronizar agora
settings.mirror_sync_in_progress=Sincronização do espelhamento está em andamento. Verifique novamente em um minuto.
settings.site=Site
settings.update_settings=Atualizar configurações
settings.update_mirror_settings=Atualizar espelho
settings.branches.switch_default_branch=Alterar
settings.branches.update_default_branch=Atualizar Branch Padrão
settings.branches.add_new_rule=Adicionar Nova Regra
@ -2166,6 +2173,7 @@ settings.protect_disable_push=Desabilitar push
settings.protect_disable_push_desc=Nenhum push será permitido neste branch.
settings.protect_enable_push=Habilitar push
settings.protect_enable_push_desc=Qualquer pessoa com acesso de escrita terá permissão para realizar push neste branch (mas não forçar o push).
settings.protect_enable_merge=Permitir merge
settings.protect_whitelist_committers=Lista permitida para push
settings.protect_whitelist_committers_desc=Somente usuários ou equipes da lista permitida serão autorizados realizar push neste branch (mas não forçar o push).
settings.protect_whitelist_deploy_keys=Dar permissão às chaves de deploy com acesso de gravação para push.
@ -2193,6 +2201,8 @@ settings.require_signed_commits_desc=Rejeitar pushes para este branch se não es
settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida
settings.protect_patterns=Padrões
settings.protect_protected_file_patterns=Padrões de arquivos protegidos (separados usando ponto e vírgula ';'):
settings.protect_unprotected_file_patterns=Padrões de arquivos desprotegidos (separados usando ponto e vírgula ';'):
settings.protect_unprotected_file_patterns_desc=Arquivos não protegidos que podem ser alterados diretamente se o usuário tiver acesso de gravação, ignorando as restrições de push. Vários padrões podem ser separados usando ponto e vírgula (\;'). Veja <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> documentação para sintaxe de padrões. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.add_protected_branch=Habilitar proteção
settings.delete_protected_branch=Desabilitar proteção
settings.update_protect_branch_success=Proteção do branch "%s" foi atualizada.
@ -2388,12 +2398,16 @@ branch.delete=`Excluir branch "%s"`
branch.delete_html=Excluir Branch
branch.delete_desc=A exclusão de um branch é permanente. Isto <strong>NÃO PODERÁ</strong> ser desfeito. Continuar?
branch.deletion_success=Branch "%s" excluído.
branch.deletion_failed=Falha ao excluir o branch "%s".
branch.create_branch=Criar branch <strong>%s</strong>
branch.create_from=`a partir de "%s"`
branch.create_success=Branch "%s" criado.
branch.branch_already_exists=Branch "%s" já existe neste repositório.
branch.deleted_by=Excluído por %s
branch.restore_success=Branch "%s" restaurado.
branch.restore_failed=Ocorreu um erro ao restaurar o branch "%s".
branch.protected_deletion_failed=Branch "%s" é protegido. Ele não pode ser excluído.
branch.default_deletion_failed=Branch "%s" é o branch padrão. Ele não pode ser excluído.
branch.restore=`Restaurar branch "%s"`
branch.download=`Baixar branch "%s"`
branch.rename=`Renomear branch "%s"`
@ -2406,12 +2420,15 @@ branch.rename_branch_to=Renomear "%s" para:
branch.confirm_rename_branch=Renomear branch
branch.create_branch_operation=Criar branch
branch.new_branch=Criar novo branch
branch.new_branch_from=`Criar novo branch a partir de "%s"`
branch.renamed=Branch %s foi renomeado para %s.
tag.create_tag=Criar tag <strong>%s</strong>
tag.create_tag_operation=Criar tag
tag.confirm_create_tag=Criar tag
tag.create_tag_from=`Criar nova tag a partir de "%s"`
tag.create_success=Tag "%s" criada.
topic.manage_topics=Gerenciar Tópicos
topic.done=Feito
@ -2460,6 +2477,7 @@ settings.permission=Permissões
settings.repoadminchangeteam=O administrador do repositório pode adicionar e remover o acesso para equipes
settings.visibility=Visibilidade
settings.visibility.public=Pública
settings.visibility.limited=Limitado (Visível apenas para usuários autenticados)
settings.visibility.limited_shortname=Limitado
settings.visibility.private=Privada (Visível apenas para membros da organização)
settings.visibility.private_shortname=Privado
@ -2531,6 +2549,7 @@ teams.remove_all_repos_title=Remover todos os repositórios da equipe
teams.remove_all_repos_desc=Isto irá remover todos os repositórios da equipe.
teams.add_all_repos_title=Adicionar todos os repositórios
teams.add_all_repos_desc=Isto irá adicionar todos os repositórios da organização à equipe.
teams.add_nonexistent_repo=O repositório que você está tentando adicionar não existe. Crie-o antes de adicioná-lo.
teams.add_duplicate_users=Usuário já é um membro da equipe.
teams.repos.none=Nenhum repositório pode ser acessado por essa equipe.
teams.members.none=Nenhum membro nesta equipe.
@ -2560,6 +2579,7 @@ first_page=Primeira
last_page=Última
total=Total: %d
dashboard.new_version_hint=Uma nova versão está disponível: %s. Versão atual: %s. Visite <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">o blog</a> para mais informações.
dashboard.statistic=Resumo
dashboard.operations=Operações de manutenção
dashboard.system_status=Status do sistema
@ -2994,6 +3014,7 @@ config.git_gc_timeout=Tempo limite para execução do GC
config.log_config=Configuração de log
config.disabled_logger=Desabilitado
config.access_log_mode=Modo log Access
config.access_log_template=Modelo do registro de acesso
config.xorm_log_sql=Log SQL
config.get_setting_failed=Falha ao obter configuração %s
@ -3037,6 +3058,7 @@ monitor.queue.settings.maxnumberworkers.error=Número máximo de executores deve
monitor.queue.settings.submit=Atualizar configurações
monitor.queue.settings.changed=Configurações atualizadas
monitor.queue.settings.remove_all_items=Remover tudo
monitor.queue.settings.remove_all_items_done=Todos os itens da fila foram removidos.
notices.system_notice_list=Avisos do sistema
notices.view_detail_header=Ver detalhes do aviso
@ -3146,6 +3168,7 @@ error.unit_not_allowed=Você não tem permissão para acessar esta seção do re
title=Pacotes
desc=Gerenciar pacotes do repositório.
empty=Não há pacotes ainda.
empty.documentation=Para obter mais informações sobre o registro de pacotes, consulte <a target="_blank" rel="noopener noreferrer" href="%s">a documentação</a>.
empty.repo=Você enviou um pacote, mas ele não está aqui? Vá para <a href="%[1]s">configurações do pacote</a> e vincule-o a este repositório.
filter.type=Tipo
filter.type.all=Todos
@ -3246,6 +3269,7 @@ pypi.install=Para instalar o pacote usando pip, execute o seguinte comando:
pypi.documentation=Para obter mais informações sobre o registro PyPI, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">a documentação</a>.
rpm.registry=Configure este registro pela linha de comando:
rpm.install=Para instalar o pacote, execute o seguinte comando:
rpm.documentation=Para obter mais informações sobre o registro RPM, consulte <a target="_blank" rel="noopener noreferrer" href="%s">a documentação</a>.
rubygems.install=Para instalar o pacote usando gem, execute o seguinte comando:
rubygems.install2=ou adicione-o ao Gemfile:
rubygems.dependencies.runtime=Dependências de Execução
@ -3312,11 +3336,13 @@ name=Nome
creation=Adicionar Segredo
creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_
creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos.
creation.success=O segredo "%s" foi adicionado.
creation.failed=Falha ao adicionar segredo.
deletion=Excluir segredo
deletion.description=A exclusão de um segredo é permanente e não pode ser desfeita. Continuar?
deletion.success=O segredo foi excluído.
deletion.failed=Falha ao excluir segredo.
management=Gerenciamento de Segredos
[actions]
actions=Ações

View File

@ -79,6 +79,8 @@ milestones=Kilometre Taşları
ok=Tamam
cancel=İptal
rerun=Yeniden çalıştır
rerun_all=Tüm görevleri yeniden çalıştır
save=Kaydet
add=Ekle
add_all=Tümünü Ekle
@ -113,11 +115,19 @@ unknown=Bilinmiyor
rss_feed=RSS Beslemesi
pin=Sabitle
unpin=Sabitlemeyi kaldır
artifacts=Yapılar
concept_system_global=Genel
concept_user_individual=Bireysel
concept_code_repository=Depo
concept_user_organization=Organizasyon
show_timestamps=Zaman damgalarını göster
show_log_seconds=Saniyeleri göster
show_full_screen=Tam ekran göster
[aria]
navbar=Gezinti Çubuğu
@ -314,6 +324,7 @@ repos=Depolar
users=Kullanıcılar
organizations=Organizasyonlar
search=Ara
go_to=Git
code=Kod
search.type.tooltip=Arama türü
search.fuzzy=Bulanık
@ -517,6 +528,7 @@ lang_select_error=Listeden bir dil seçin.
username_been_taken=Bu kullanıcı adı daha önce alınmış.
username_change_not_local_user=Yerel olmayan kullanıcılar kendi kullanıcı adlarını değiştiremezler.
username_has_not_been_changed=Kullanıcı adı değişmedi
repo_name_been_taken=Depo adı zaten kullanılıyor.
repository_force_private=Gizliyi Zorla devrede: gizli depolar herkese açık yapılamaz.
repository_files_already_exist=Bu depo için dosyalar zaten var. Sistem yöneticisine başvurun.
@ -565,6 +577,7 @@ target_branch_not_exist=Hedef dal mevcut değil.
[user]
change_avatar=Profil resmini değiştir…
joined_on=%s tarihinde katıldı
repositories=Depolar
activity=Genel Aktivite
followers=Takipçiler
@ -759,6 +772,8 @@ ssh_principal_deletion_desc=Bir SSH Sertifika Sorumlusunun kaldırılması, hesa
ssh_key_deletion_success=SSH anahtarı silindi.
gpg_key_deletion_success=GPG anahtarı silindi.
ssh_principal_deletion_success=Sorumlu kaldırıldı.
added_on=%s tarihinde eklendi
valid_until_date=%s tarihine kadar geçerli
valid_forever=Sürekli geçerlidir
last_used=Son kullanım
no_activity=Yeni aktivite yok
@ -790,6 +805,10 @@ access_token_deletion_cancel_action=İptal
access_token_deletion_confirm_action=Sil
access_token_deletion_desc=Bir erişim anahtarını silmek, onu kullanan uygulamaların hesabınıza erişimini kaldırır. Bu geri alınamaz. Devam edilsin mi?
delete_token_success=Jeton silindi. Onu kullanan uygulamalar artık hesabınıza erişemez.
repo_and_org_access=Depo ve Organizasyon Erişimi
permissions_access_all=Tümü (herkese açık, özel ve sınırlı)
select_permissions=İzinleri seçin
permissions_list=İzinler:
manage_oauth2_applications=OAuth2 Uygulamalarını Yönet
edit_oauth2_application=OAuth2 Uygulamalarını Düzenle
@ -1048,6 +1067,8 @@ migrate.migrating_labels=Etiketleri Taşıma
migrate.migrating_releases=Sürümleri Taşıma
migrate.migrating_issues=Konuları Taşıma
migrate.migrating_pulls=Değişiklik İsteklerini Taşıma
migrate.cancel_migrating_title=Göçü İptal Et
migrate.cancel_migrating_confirm=Bu göçü iptal etmek istiyor musunuz?
mirror_from=şunun yansıması
forked_from=şundan çatallanmış
@ -1176,6 +1197,8 @@ editor.filename_is_invalid=Dosya adı geçersiz: "%s".
editor.branch_does_not_exist=Bu depoda "%s" dalı yok.
editor.branch_already_exists=Bu depoda "%s" dalı zaten var.
editor.directory_is_a_file=Dizin adı "%s" zaten bu depoda bir dosya adı olarak kullanılmaktadır.
editor.file_is_a_symlink=`"%s" sembolik bir bağlantıdır. Sembolik bağlantılar web düzenleyicisinde düzenlenemez`
editor.filename_is_a_directory=Dosya adı "%s" zaten bu depoda bir dizin adı olarak kullanılmaktadır.
editor.file_editing_no_longer_exists=Düzenlenmekte olan "%s" dosyası artık bu depoda yer almıyor.
editor.file_deleting_no_longer_exists=Silinen "%s" dosyası artık bu depoda yer almıyor.
editor.file_changed_while_editing=Düzenlemeye başladığınızdan beri dosya içeriği değişti. Görmek için <a target="_blank" rel="noopener noreferrer" href="%s">burayı tıklayın</a> veya üzerine yazmak için <strong>değişiklikleri yine de işleyin</strong>.
@ -1341,6 +1364,10 @@ issues.filter_label_exclude=`Etiketleri hariç tutmak için <code>alt</code> + <
issues.filter_label_no_select=Tüm etiketler
issues.filter_label_select_no_label=Etiket Yok
issues.filter_milestone=Kilometre Taşı
issues.filter_milestone_all=Tüm kilometre taşları
issues.filter_milestone_none=Kilometre taşı yok
issues.filter_milestone_open=Kilometre taşlarını
issues.filter_milestone_closed=Kapanmış kilometre taşları
issues.filter_project=Proje
issues.filter_project_all=Tüm projeler
issues.filter_project_none=Proje yok
@ -2386,6 +2413,7 @@ branch.included_desc=Bu dal varsayılan dalın bir parçasıdır
branch.included=Dahil
branch.create_new_branch=Şu daldan dal oluştur:
branch.confirm_create_branch=Dal oluştur
branch.warning_rename_default_branch=Varsayılan dalın adını değiştiriyorsunuz.
branch.confirm_rename_branch=Dalı yeniden adlandır
branch.create_branch_operation=Dal oluştur
branch.new_branch=Yeni dal oluştur
@ -2944,6 +2972,7 @@ config.mailer_sendmail_timeout=Sendmail Zaman Aşımı
config.mailer_use_dummy=Sahte
config.test_email_placeholder=E-posta (ör. test@example.com)
config.send_test_mail=Test E-postası Gönder
config.send_test_mail_submit=Gönder
config.test_mail_failed=`"%s" adresine deneme e-postası gönderilemedi: %v`
config.test_mail_sent=`"%s" adresine bir deneme e-postası gönderildi.`
@ -2983,13 +3012,16 @@ config.git_pull_timeout=Çekme İşlemi Zaman Aşımı
config.git_gc_timeout=GC İşlemi Zaman Aşımı
config.log_config=Log Yapılandırması
config.logger_name_fmt=Günlükçü: %s
config.disabled_logger=Devre Dışı
config.access_log_mode=Erişim Günlüğü Kipi
config.access_log_template=Erişim Günlüğü Şablonu
config.xorm_log_sql=SQL Günlüğü
config.get_setting_failed=%s ayarı alınamadı
config.set_setting_failed=%s ayarı yapılamadı
monitor.stats=İstatistikler
monitor.cron=Cron Görevleri
monitor.name=İsim
@ -2999,6 +3031,8 @@ monitor.previous=Önceki Zaman
monitor.execute_times=Çalıştırma
monitor.process=Çalışan Süreçler
monitor.stacktrace=Yığın izleme
monitor.processes_count=%d İşlem
monitor.download_diagnosis_report=Tanı raporunu indir
monitor.desc=ıklama
monitor.start=Başlangıç Zamanı
monitor.execute_time=Çalıştırma Zamanı
@ -3019,11 +3053,14 @@ monitor.queue.numberinqueue=Kuyruktaki Sayı
monitor.queue.review=Yapılandırmayı İncele
monitor.queue.review_add=Çalışanları İncele/Ekle
monitor.queue.settings.title=Havuz Ayarları
monitor.queue.settings.desc=Havuzlar, çalışan kuyruğu tıkanmasına bir yanıt olarak dinamik olarak büyürler.
monitor.queue.settings.maxnumberworkers=En fazla çalışan Sayısı
monitor.queue.settings.maxnumberworkers.placeholder=Şu anda %[1]d
monitor.queue.settings.maxnumberworkers.error=En fazla çalışan sayısı bir sayı olmalıdır
monitor.queue.settings.submit=Ayarları Güncelle
monitor.queue.settings.changed=Ayarlar Güncellendi
monitor.queue.settings.remove_all_items=Tümünü kaldır
monitor.queue.settings.remove_all_items_done=Kuyruktaki tüm öğeler kaldırıldı.
notices.system_notice_list=Sistem Bildirimleri
notices.view_detail_header=Bildirim Ayrıntılarını Görüntüle
@ -3158,9 +3195,15 @@ versions=Sürümler
versions.view_all=Tümünü görüntüle
dependency.id=Kimlik
dependency.version=Sürüm
alpine.registry=Bu kütüğü, <code>/etc/apk/repositories</code> dosyanıza url'yi ekleyerek kurun:
alpine.registry.key=Kütüğün açık RSA anahtarını, indeks imzasını doğrulamak için <code>/etc/apk/keys/</code> dizinine indirin:
alpine.registry.info=Aşağıdaki listeden $branch ve $repository seçin.
alpine.install=Paketi kurmak için, aşağıdaki komutu çalıştırın:
alpine.documentation=Alpine kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
alpine.repository=Depo Bilgisi
alpine.repository.branches=Dallar
alpine.repository.repositories=Depolar
alpine.repository.architectures=Mimariler
cargo.registry=Bu kütüğü Cargo yapılandırma dosyasına (örneğin <code>~/.cargo/config.toml</code>) ayarlayın:
cargo.install=Paketi Cargo kullanarak kurmak için, şu komutu çalıştırın:
cargo.documentation=Cargo kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/cargo/">belgeye</a> bakabilirsiniz.
@ -3193,11 +3236,21 @@ container.layers=Görüntü Katmanları
container.labels=Etiketler
container.labels.key=Anahtar
container.labels.value=Değer
cran.registry=Bu kütüğü <code>Rprofile.site</code> dosyasında ayarlayın:
cran.install=Paketi kurmak için, aşağıdaki komutu çalıştırın:
cran.documentation=CRAN kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/cran/">belgeye</a> bakabilirsiniz.
debian.registry=Bu kütüğü komut satırını kullanarak kurun:
debian.registry.info=Aşağıdaki listeden $distribution ve $component seçin.
debian.install=Paketi kurmak için, aşağıdaki komutu çalıştırın:
debian.documentation=Debian kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
debian.repository=Depo Bilgisi
debian.repository.distributions=Dağıtımlar
debian.repository.components=Bileşenler
debian.repository.architectures=Mimariler
generic.download=Paketi komut satırında indirin:
generic.documentation=Genel kütük hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">belgeye</a> bakabilirsiniz.
go.install=Paketi komut satırını kullanarak yükleyin:
go.documentation=Go kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
helm.registry=Bu kütüğü komut satırını kullanarak kurun:
helm.install=Paketi kurmak için, aşağıdaki komutu çalıştırın:
helm.documentation=Helm kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">belgeye</a> bakabilirsiniz.
@ -3226,6 +3279,7 @@ pypi.install=Paketi pip ile kurmak için, şu komutu çalıştırın:
pypi.documentation=PyPI kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">belgeye</a> bakabilirsiniz.
rpm.registry=Bu kütüğü komut satırını kullanarak kurun:
rpm.install=Paketi kurmak için, aşağıdaki komutu çalıştırın:
rpm.documentation=RPM kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
rubygems.install=Paketi gem ile kurmak için, şu komutu çalıştırın:
rubygems.install2=veya paketi Gemfile dosyasına ekleyin:
rubygems.dependencies.runtime=Çalışma Zamanı Bağımlılıkları
@ -3298,6 +3352,7 @@ deletion=Gizliliği kaldır
deletion.description=Bir gizliliği kaldırma kalıcıdır ve geri alınamaz. Devam edilsin mi?
deletion.success=Gizlilik kaldırıldı.
deletion.failed=Gizlilik kaldırılamadı.
management=Gizlilik Yönetimi
[actions]
actions=İşlemler

866
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"@citation-js/plugin-csl": "0.6.8",
"@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6",
"@github/markdown-toolbar-element": "2.1.1",
"@github/markdown-toolbar-element": "2.2.0",
"@github/relative-time-element": "4.3.0",
"@github/text-expander-element": "2.5.0",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
@ -34,11 +34,11 @@
"mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.3",
"monaco-editor": "0.40.0",
"monaco-editor-webpack-plugin": "7.0.1",
"monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.2.12",
"pretty-ms": "8.0.0",
"sortablejs": "1.15.0",
"swagger-ui-dist": "5.1.0",
"swagger-ui-dist": "5.1.3",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tippy.js": "6.3.7",
@ -49,16 +49,16 @@
"vue-bar-graph": "2.0.0",
"vue-loader": "17.2.2",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.88.1",
"webpack": "5.88.2",
"webpack-cli": "5.1.4",
"wrap-ansi": "8.1.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "3.2.1",
"@playwright/test": "1.35.1",
"@playwright/test": "1.36.1",
"@stoplight/spectral-cli": "6.8.0",
"@vitejs/plugin-vue": "4.2.3",
"eslint": "8.44.0",
"eslint": "8.45.0",
"eslint-plugin-array-func": "3.1.8",
"eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-import": "2.27.5",
@ -67,19 +67,19 @@
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "1.15.0",
"eslint-plugin-sonarjs": "0.19.0",
"eslint-plugin-unicorn": "47.0.0",
"eslint-plugin-unicorn": "48.0.0",
"eslint-plugin-vue": "9.15.1",
"eslint-plugin-wc": "1.5.0",
"jsdom": "22.1.0",
"markdownlint-cli": "0.35.0",
"postcss-html": "1.5.0",
"stylelint": "15.9.0",
"stylelint": "15.10.2",
"stylelint-declaration-block-no-ignored-properties": "2.7.0",
"stylelint-declaration-strict-value": "1.9.2",
"stylelint-stylistic": "0.4.2",
"stylelint-stylistic": "0.4.3",
"svgo": "3.0.2",
"updates": "14.3.2",
"vite-string-plugin": "1.1.0",
"updates": "14.3.4",
"vite-string-plugin": "1.1.1",
"vitest": "0.33.0"
},
"browserslist": [

View File

@ -0,0 +1,6 @@
# This site is running a Gitea instance.
# Gitea related security problems could be reported to Gitea community.
# Site related security problems should be reported to this site's admin.
Contact: https://github.com/go-gitea/gitea/blob/main/SECURITY.md
Policy: https://github.com/go-gitea/gitea/blob/main/SECURITY.md
Preferred-Languages: en

View File

@ -62,17 +62,12 @@ package actions
//
import (
"compress/gzip"
"crypto/md5"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"sort"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/context"
@ -85,11 +80,6 @@ import (
web_types "code.gitea.io/gitea/modules/web/types"
)
const (
artifactXTfsFileLengthHeader = "x-tfs-filelength"
artifactXActionsResultsMD5Header = "x-actions-results-md5"
)
const artifactRouteBase = "/_apis/pipelines/workflows/{run_id}/artifacts"
type artifactContextKeyType struct{}
@ -121,11 +111,10 @@ func ArtifactsRoutes(prefix string) *web.Route {
// retrieve, list and confirm artifacts
m.Combo("").Get(r.listArtifacts).Post(r.getUploadArtifactURL).Patch(r.comfirmUploadArtifact)
// handle container artifacts list and download
m.Group("/{artifact_id}", func() {
m.Put("/upload", r.uploadArtifact)
m.Get("/path", r.getDownloadArtifactURL)
m.Get("/download", r.downloadArtifact)
})
m.Put("/{artifact_hash}/upload", r.uploadArtifact)
// handle artifacts download
m.Get("/{artifact_hash}/download_url", r.getDownloadArtifactURL)
m.Get("/{artifact_id}/download", r.downloadArtifact)
})
return m
@ -173,10 +162,10 @@ type artifactRoutes struct {
fs storage.ObjectStorage
}
func (ar artifactRoutes) buildArtifactURL(runID, artifactID int64, suffix string) string {
func (ar artifactRoutes) buildArtifactURL(runID int64, artifactHash, suffix string) string {
uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ar.prefix, "/") +
strings.ReplaceAll(artifactRouteBase, "{run_id}", strconv.FormatInt(runID, 10)) +
"/" + strconv.FormatInt(artifactID, 10) + "/" + suffix
"/" + artifactHash + "/" + suffix
return uploadURL
}
@ -189,20 +178,9 @@ type getUploadArtifactResponse struct {
FileContainerResourceURL string `json:"fileContainerResourceUrl"`
}
func (ar artifactRoutes) validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
task := ctx.ActionTask
runID := ctx.ParamsInt64("run_id")
if task.Job.RunID != runID {
log.Error("Error runID not match")
ctx.Error(http.StatusBadRequest, "run-id does not match")
return nil, 0, false
}
return task, runID, true
}
// getUploadArtifactURL generates a URL for uploading an artifact
func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
task, runID, ok := ar.validateRunID(ctx)
_, runID, ok := validateRunID(ctx)
if !ok {
return
}
@ -214,131 +192,59 @@ func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
return
}
artifact, err := actions.CreateArtifact(ctx, task, req.Name)
if err != nil {
log.Error("Error creating artifact: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
// use md5(artifact_name) to create upload url
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name)))
resp := getUploadArtifactResponse{
FileContainerResourceURL: ar.buildArtifactURL(runID, artifact.ID, "upload"),
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "upload"),
}
log.Debug("[artifact] get upload url: %s, artifact id: %d", resp.FileContainerResourceURL, artifact.ID)
log.Debug("[artifact] get upload url: %s", resp.FileContainerResourceURL)
ctx.JSON(http.StatusOK, resp)
}
// getUploadFileSize returns the size of the file to be uploaded.
// The raw size is the size of the file as reported by the header X-TFS-FileLength.
func (ar artifactRoutes) getUploadFileSize(ctx *ArtifactContext) (int64, int64, error) {
contentLength := ctx.Req.ContentLength
xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
if xTfsLength > 0 {
return xTfsLength, contentLength, nil
}
return contentLength, contentLength, nil
}
func (ar artifactRoutes) saveUploadChunk(ctx *ArtifactContext,
artifact *actions.ActionArtifact,
contentSize, runID int64,
) (int64, error) {
contentRange := ctx.Req.Header.Get("Content-Range")
start, end, length := int64(0), int64(0), int64(0)
if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil {
return -1, fmt.Errorf("parse content range error: %v", err)
}
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d.chunk", runID, artifact.ID, start, end)
// use io.TeeReader to avoid reading all body to md5 sum.
// it writes data to hasher after reading end
// if hash is not matched, delete the read-end result
hasher := md5.New()
r := io.TeeReader(ctx.Req.Body, hasher)
// save chunk to storage
writtenSize, err := ar.fs.Save(storagePath, r, -1)
if err != nil {
return -1, fmt.Errorf("save chunk to storage error: %v", err)
}
// check md5
reqMd5String := ctx.Req.Header.Get(artifactXActionsResultsMD5Header)
chunkMd5String := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
log.Debug("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String)
if reqMd5String != chunkMd5String || writtenSize != contentSize {
if err := ar.fs.Delete(storagePath); err != nil {
log.Error("Error deleting chunk: %s, %v", storagePath, err)
}
return -1, fmt.Errorf("md5 not match")
}
log.Debug("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, end: %d",
storagePath, contentSize, artifact.ID, start, end)
return length, nil
}
// The rules are from https://github.com/actions/toolkit/blob/main/packages/artifact/src/internal/path-and-artifact-name-validation.ts#L32
var invalidArtifactNameChars = strings.Join([]string{"\\", "/", "\"", ":", "<", ">", "|", "*", "?", "\r", "\n"}, "")
func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
_, runID, ok := ar.validateRunID(ctx)
task, runID, ok := validateRunID(ctx)
if !ok {
return
}
artifactID := ctx.ParamsInt64("artifact_id")
artifact, err := actions.GetArtifactByID(ctx, artifactID)
if errors.Is(err, util.ErrNotExist) {
log.Error("Error getting artifact: %v", err)
ctx.Error(http.StatusNotFound, err.Error())
return
} else if err != nil {
log.Error("Error getting artifact: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
// itemPath is generated from upload-artifact action
// it's formatted as {artifact_name}/{artfict_path_in_runner}
itemPath := util.PathJoinRel(ctx.Req.URL.Query().Get("itemPath"))
artifactName := strings.Split(itemPath, "/")[0]
// checkArtifactName checks if the artifact name contains invalid characters.
// If the name contains invalid characters, an error is returned.
if strings.ContainsAny(artifactName, invalidArtifactNameChars) {
log.Error("Error checking artifact name contains invalid character")
ctx.Error(http.StatusBadRequest, err.Error())
artifactName, artifactPath, ok := parseArtifactItemPath(ctx)
if !ok {
return
}
// get upload file size
fileSize, contentLength, err := ar.getUploadFileSize(ctx)
fileRealTotalSize, contentLength, err := getUploadFileSize(ctx)
if err != nil {
log.Error("Error getting upload file size: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
log.Error("Error get upload file size: %v", err)
ctx.Error(http.StatusInternalServerError, "Error get upload file size")
return
}
// save chunk
chunkAllLength, err := ar.saveUploadChunk(ctx, artifact, contentLength, runID)
// create or get artifact with name and path
artifact, err := actions.CreateArtifact(ctx, task, artifactName, artifactPath)
if err != nil {
log.Error("Error saving upload chunk: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
log.Error("Error create or get artifact: %v", err)
ctx.Error(http.StatusInternalServerError, "Error create or get artifact")
return
}
// if artifact name is not set, update it
if artifact.ArtifactName == "" {
artifact.ArtifactName = artifactName
artifact.ArtifactPath = itemPath // path in container
artifact.FileSize = fileSize // this is total size of all chunks
artifact.FileCompressedSize = chunkAllLength
// save chunk to storage, if success, return chunk stotal size
// if artifact is not gzip when uploading, chunksTotalSize == fileRealTotalSize
// if artifact is gzip when uploading, chunksTotalSize < fileRealTotalSize
chunksTotalSize, err := saveUploadChunk(ar.fs, ctx, artifact, contentLength, runID)
if err != nil {
log.Error("Error save upload chunk: %v", err)
ctx.Error(http.StatusInternalServerError, "Error save upload chunk")
return
}
// update artifact size if zero
if artifact.FileSize == 0 || artifact.FileCompressedSize == 0 {
artifact.FileSize = fileRealTotalSize
artifact.FileCompressedSize = chunksTotalSize
artifact.ContentEncoding = ctx.Req.Header.Get("Content-Encoding")
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
log.Error("Error updating artifact: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
log.Error("Error update artifact: %v", err)
ctx.Error(http.StatusInternalServerError, "Error update artifact")
return
}
}
@ -351,135 +257,26 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
// comfirmUploadArtifact comfirm upload artifact.
// if all chunks are uploaded, merge them to one file.
func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) {
_, runID, ok := ar.validateRunID(ctx)
_, runID, ok := validateRunID(ctx)
if !ok {
return
}
if err := ar.mergeArtifactChunks(ctx, runID); err != nil {
log.Error("Error merging chunks: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
artifactName := ctx.Req.URL.Query().Get("artifactName")
if artifactName == "" {
log.Error("Error artifact name is empty")
ctx.Error(http.StatusBadRequest, "Error artifact name is empty")
return
}
if err := mergeChunksForRun(ctx, ar.fs, runID, artifactName); err != nil {
log.Error("Error merge chunks: %v", err)
ctx.Error(http.StatusInternalServerError, "Error merge chunks")
return
}
ctx.JSON(http.StatusOK, map[string]string{
"message": "success",
})
}
type chunkItem struct {
ArtifactID int64
Start int64
End int64
Path string
}
func (ar artifactRoutes) mergeArtifactChunks(ctx *ArtifactContext, runID int64) error {
storageDir := fmt.Sprintf("tmp%d", runID)
var chunks []*chunkItem
if err := ar.fs.IterateObjects(storageDir, func(path string, obj storage.Object) error {
item := chunkItem{Path: path}
if _, err := fmt.Sscanf(path, storageDir+"/%d-%d-%d.chunk", &item.ArtifactID, &item.Start, &item.End); err != nil {
return fmt.Errorf("parse content range error: %v", err)
}
chunks = append(chunks, &item)
return nil
}); err != nil {
return err
}
// group chunks by artifact id
chunksMap := make(map[int64][]*chunkItem)
for _, c := range chunks {
chunksMap[c.ArtifactID] = append(chunksMap[c.ArtifactID], c)
}
for artifactID, cs := range chunksMap {
// get artifact to handle merged chunks
artifact, err := actions.GetArtifactByID(ctx, cs[0].ArtifactID)
if err != nil {
return fmt.Errorf("get artifact error: %v", err)
}
sort.Slice(cs, func(i, j int) bool {
return cs[i].Start < cs[j].Start
})
allChunks := make([]*chunkItem, 0)
startAt := int64(-1)
// check if all chunks are uploaded and in order and clean repeated chunks
for _, c := range cs {
// startAt is -1 means this is the first chunk
// previous c.ChunkEnd + 1 == c.ChunkStart means this chunk is in order
// StartAt is not -1 and c.ChunkStart is not startAt + 1 means there is a chunk missing
if c.Start == (startAt + 1) {
allChunks = append(allChunks, c)
startAt = c.End
}
}
// if the last chunk.End + 1 is not equal to chunk.ChunkLength, means chunks are not uploaded completely
if startAt+1 != artifact.FileCompressedSize {
log.Debug("[artifact] chunks are not uploaded completely, artifact_id: %d", artifactID)
break
}
// use multiReader
readers := make([]io.Reader, 0, len(allChunks))
closeReaders := func() {
for _, r := range readers {
_ = r.(io.Closer).Close() // it guarantees to be io.Closer by the following loop's Open function
}
readers = nil
}
defer closeReaders()
for _, c := range allChunks {
var readCloser io.ReadCloser
if readCloser, err = ar.fs.Open(c.Path); err != nil {
return fmt.Errorf("open chunk error: %v, %s", err, c.Path)
}
readers = append(readers, readCloser)
}
mergedReader := io.MultiReader(readers...)
// if chunk is gzip, decompress it
if artifact.ContentEncoding == "gzip" {
var err error
mergedReader, err = gzip.NewReader(mergedReader)
if err != nil {
return fmt.Errorf("gzip reader error: %v", err)
}
}
// save merged file
storagePath := fmt.Sprintf("%d/%d/%d.chunk", runID%255, artifactID%255, time.Now().UnixNano())
written, err := ar.fs.Save(storagePath, mergedReader, -1)
if err != nil {
return fmt.Errorf("save merged file error: %v", err)
}
if written != artifact.FileSize {
return fmt.Errorf("merged file size is not equal to chunk length")
}
// save storage path to artifact
log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath)
artifact.StoragePath = storagePath
artifact.Status = actions.ArtifactStatusUploadConfirmed
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
return fmt.Errorf("update artifact error: %v", err)
}
closeReaders() // close before delete
// drop chunks
for _, c := range cs {
if err := ar.fs.Delete(c.Path); err != nil {
return fmt.Errorf("delete chunk file error: %v", err)
}
}
}
return nil
}
type (
listArtifactsResponse struct {
Count int64 `json:"count"`
@ -492,7 +289,7 @@ type (
)
func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
_, runID, ok := ar.validateRunID(ctx)
_, runID, ok := validateRunID(ctx)
if !ok {
return
}
@ -503,17 +300,35 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
artficatsData := make([]listArtifactsResponseItem, 0, len(artifacts))
for _, a := range artifacts {
artficatsData = append(artficatsData, listArtifactsResponseItem{
Name: a.ArtifactName,
FileContainerResourceURL: ar.buildArtifactURL(runID, a.ID, "path"),
})
if len(artifacts) == 0 {
log.Debug("[artifact] handleListArtifacts, no artifacts")
ctx.Error(http.StatusNotFound)
return
}
var (
items []listArtifactsResponseItem
values = make(map[string]bool)
)
for _, art := range artifacts {
if values[art.ArtifactName] {
continue
}
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(art.ArtifactName)))
item := listArtifactsResponseItem{
Name: art.ArtifactName,
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "download_url"),
}
items = append(items, item)
values[art.ArtifactName] = true
log.Debug("[artifact] handleListArtifacts, name: %s, url: %s", item.Name, item.FileContainerResourceURL)
}
respData := listArtifactsResponse{
Count: int64(len(artficatsData)),
Value: artficatsData,
Count: int64(len(items)),
Value: items,
}
ctx.JSON(http.StatusOK, respData)
}
@ -529,37 +344,56 @@ type (
}
)
// getDownloadArtifactURL generates download url for each artifact
func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
_, runID, ok := ar.validateRunID(ctx)
_, runID, ok := validateRunID(ctx)
if !ok {
return
}
artifactID := ctx.ParamsInt64("artifact_id")
artifact, err := actions.GetArtifactByID(ctx, artifactID)
if errors.Is(err, util.ErrNotExist) {
log.Error("Error getting artifact: %v", err)
ctx.Error(http.StatusNotFound, err.Error())
itemPath := util.PathJoinRel(ctx.Req.URL.Query().Get("itemPath"))
if !validateArtifactHash(ctx, itemPath) {
return
} else if err != nil {
log.Error("Error getting artifact: %v", err)
}
artifacts, err := actions.ListArtifactsByRunIDAndArtifactName(ctx, runID, itemPath)
if err != nil {
log.Error("Error getting artifacts: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
downloadURL := ar.buildArtifactURL(runID, artifact.ID, "download")
itemPath := util.PathJoinRel(ctx.Req.URL.Query().Get("itemPath"))
respData := downloadArtifactResponse{
Value: []downloadArtifactResponseItem{{
if len(artifacts) == 0 {
log.Debug("[artifact] getDownloadArtifactURL, no artifacts")
ctx.Error(http.StatusNotFound)
return
}
if itemPath != artifacts[0].ArtifactName {
log.Error("Error dismatch artifact name, itemPath: %v, artifact: %v", itemPath, artifacts[0].ArtifactName)
ctx.Error(http.StatusBadRequest, "Error dismatch artifact name")
return
}
var items []downloadArtifactResponseItem
for _, artifact := range artifacts {
downloadURL := ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
item := downloadArtifactResponseItem{
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
ItemType: "file",
ContentLocation: downloadURL,
}},
}
log.Debug("[artifact] getDownloadArtifactURL, path: %s, url: %s", item.Path, item.ContentLocation)
items = append(items, item)
}
respData := downloadArtifactResponse{
Value: items,
}
ctx.JSON(http.StatusOK, respData)
}
// downloadArtifact downloads artifact content
func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
_, runID, ok := ar.validateRunID(ctx)
_, runID, ok := validateRunID(ctx)
if !ok {
return
}
@ -589,9 +423,11 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
}
defer fd.Close()
if strings.HasSuffix(artifact.ArtifactPath, ".gz") {
// if artifact is compressed, set content-encoding header to gzip
if artifact.ContentEncoding == "gzip" {
ctx.Resp.Header().Set("Content-Encoding", "gzip")
}
log.Debug("[artifact] downloadArtifact, name: %s, path: %s, storage: %s, size: %d", artifact.ArtifactName, artifact.ArtifactPath, artifact.StoragePath, artifact.FileSize)
ctx.ServeContent(fd, &context.ServeHeaderOptions{
Filename: artifact.ArtifactName,
LastModified: artifact.CreatedUnix.AsLocalTime(),

View File

@ -0,0 +1,187 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"crypto/md5"
"encoding/base64"
"fmt"
"io"
"sort"
"time"
"code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
)
func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
artifact *actions.ActionArtifact,
contentSize, runID int64,
) (int64, error) {
// parse content-range header, format: bytes 0-1023/146515
contentRange := ctx.Req.Header.Get("Content-Range")
start, end, length := int64(0), int64(0), int64(0)
if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil {
return -1, fmt.Errorf("parse content range error: %v", err)
}
// build chunk store path
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d.chunk", runID, artifact.ID, start, end)
// use io.TeeReader to avoid reading all body to md5 sum.
// it writes data to hasher after reading end
// if hash is not matched, delete the read-end result
hasher := md5.New()
r := io.TeeReader(ctx.Req.Body, hasher)
// save chunk to storage
writtenSize, err := st.Save(storagePath, r, -1)
if err != nil {
return -1, fmt.Errorf("save chunk to storage error: %v", err)
}
// check md5
reqMd5String := ctx.Req.Header.Get(artifactXActionsResultsMD5Header)
chunkMd5String := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String)
// if md5 not match, delete the chunk
if reqMd5String != chunkMd5String || writtenSize != contentSize {
if err := st.Delete(storagePath); err != nil {
log.Error("Error deleting chunk: %s, %v", storagePath, err)
}
return -1, fmt.Errorf("md5 not match")
}
log.Info("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, end: %d",
storagePath, contentSize, artifact.ID, start, end)
// return chunk total size
return length, nil
}
type chunkFileItem struct {
ArtifactID int64
Start int64
End int64
Path string
}
func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) {
storageDir := fmt.Sprintf("tmp%d", runID)
var chunks []*chunkFileItem
if err := st.IterateObjects(storageDir, func(path string, obj storage.Object) error {
item := chunkFileItem{Path: path}
if _, err := fmt.Sscanf(path, storageDir+"/%d-%d-%d.chunk", &item.ArtifactID, &item.Start, &item.End); err != nil {
return fmt.Errorf("parse content range error: %v", err)
}
chunks = append(chunks, &item)
return nil
}); err != nil {
return nil, err
}
// chunks group by artifact id
chunksMap := make(map[int64][]*chunkFileItem)
for _, c := range chunks {
chunksMap[c.ArtifactID] = append(chunksMap[c.ArtifactID], c)
}
return chunksMap, nil
}
func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int64, artifactName string) error {
// read all db artifacts by name
artifacts, err := actions.ListArtifactsByRunIDAndName(ctx, runID, artifactName)
if err != nil {
return err
}
// read all uploading chunks from storage
chunksMap, err := listChunksByRunID(st, runID)
if err != nil {
return err
}
// range db artifacts to merge chunks
for _, art := range artifacts {
chunks, ok := chunksMap[art.ID]
if !ok {
log.Debug("artifact %d chunks not found", art.ID)
continue
}
if err := mergeChunksForArtifact(ctx, chunks, st, art); err != nil {
return err
}
}
return nil
}
func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st storage.ObjectStorage, artifact *actions.ActionArtifact) error {
sort.Slice(chunks, func(i, j int) bool {
return chunks[i].Start < chunks[j].Start
})
allChunks := make([]*chunkFileItem, 0)
startAt := int64(-1)
// check if all chunks are uploaded and in order and clean repeated chunks
for _, c := range chunks {
// startAt is -1 means this is the first chunk
// previous c.ChunkEnd + 1 == c.ChunkStart means this chunk is in order
// StartAt is not -1 and c.ChunkStart is not startAt + 1 means there is a chunk missing
if c.Start == (startAt + 1) {
allChunks = append(allChunks, c)
startAt = c.End
}
}
// if the last chunk.End + 1 is not equal to chunk.ChunkLength, means chunks are not uploaded completely
if startAt+1 != artifact.FileCompressedSize {
log.Debug("[artifact] chunks are not uploaded completely, artifact_id: %d", artifact.ID)
return nil
}
// use multiReader
readers := make([]io.Reader, 0, len(allChunks))
closeReaders := func() {
for _, r := range readers {
_ = r.(io.Closer).Close() // it guarantees to be io.Closer by the following loop's Open function
}
readers = nil
}
defer closeReaders()
for _, c := range allChunks {
var readCloser io.ReadCloser
var err error
if readCloser, err = st.Open(c.Path); err != nil {
return fmt.Errorf("open chunk error: %v, %s", err, c.Path)
}
readers = append(readers, readCloser)
}
mergedReader := io.MultiReader(readers...)
// if chunk is gzip, use gz as extension
// download-artifact action will use content-encoding header to decide if it should decompress the file
extension := "chunk"
if artifact.ContentEncoding == "gzip" {
extension = "chunk.gz"
}
// save merged file
storagePath := fmt.Sprintf("%d/%d/%d.%s", artifact.RunID%255, artifact.ID%255, time.Now().UnixNano(), extension)
written, err := st.Save(storagePath, mergedReader, -1)
if err != nil {
return fmt.Errorf("save merged file error: %v", err)
}
if written != artifact.FileCompressedSize {
return fmt.Errorf("merged file size is not equal to chunk length")
}
defer func() {
closeReaders() // close before delete
// drop chunks
for _, c := range chunks {
if err := st.Delete(c.Path); err != nil {
log.Warn("Error deleting chunk: %s, %v", c.Path, err)
}
}
}()
// save storage path to artifact
log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath)
artifact.StoragePath = storagePath
artifact.Status = actions.ArtifactStatusUploadConfirmed
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
return fmt.Errorf("update artifact error: %v", err)
}
return nil
}

View File

@ -0,0 +1,82 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"crypto/md5"
"fmt"
"net/http"
"strconv"
"strings"
"code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
const (
artifactXTfsFileLengthHeader = "x-tfs-filelength"
artifactXActionsResultsMD5Header = "x-actions-results-md5"
)
// The rules are from https://github.com/actions/toolkit/blob/main/packages/artifact/src/internal/path-and-artifact-name-validation.ts#L32
var invalidArtifactNameChars = strings.Join([]string{"\\", "/", "\"", ":", "<", ">", "|", "*", "?", "\r", "\n"}, "")
func validateArtifactName(ctx *ArtifactContext, artifactName string) bool {
if strings.ContainsAny(artifactName, invalidArtifactNameChars) {
log.Error("Error checking artifact name contains invalid character")
ctx.Error(http.StatusBadRequest, "Error checking artifact name contains invalid character")
return false
}
return true
}
func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
task := ctx.ActionTask
runID := ctx.ParamsInt64("run_id")
if task.Job.RunID != runID {
log.Error("Error runID not match")
ctx.Error(http.StatusBadRequest, "run-id does not match")
return nil, 0, false
}
return task, runID, true
}
func validateArtifactHash(ctx *ArtifactContext, artifactName string) bool {
paramHash := ctx.Params("artifact_hash")
// use artifact name to create upload url
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(artifactName)))
if paramHash == artifactHash {
return true
}
log.Error("Invalid artifact hash: %s", paramHash)
ctx.Error(http.StatusBadRequest, "Invalid artifact hash")
return false
}
func parseArtifactItemPath(ctx *ArtifactContext) (string, string, bool) {
// itemPath is generated from upload-artifact action
// it's formatted as {artifact_name}/{artfict_path_in_runner}
itemPath := util.PathJoinRel(ctx.Req.URL.Query().Get("itemPath"))
artifactName := strings.Split(itemPath, "/")[0]
artifactPath := strings.TrimPrefix(itemPath, artifactName+"/")
if !validateArtifactHash(ctx, artifactName) {
return "", "", false
}
if !validateArtifactName(ctx, artifactName) {
return "", "", false
}
return artifactName, artifactPath, true
}
// getUploadFileSize returns the size of the file to be uploaded.
// The raw size is the size of the file as reported by the header X-TFS-FileLength.
func getUploadFileSize(ctx *ArtifactContext) (int64, int64, error) {
contentLength := ctx.Req.ContentLength
xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
if xTfsLength > 0 {
return xTfsLength, contentLength, nil
}
return contentLength, contentLength, nil
}

View File

@ -386,7 +386,7 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
func reqRepoBranchWriter(ctx *context.APIContext) {
options, ok := web.GetForm(ctx).(api.FileOptionInterface)
if !ok || (!ctx.Repo.CanWriteToBranch(ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch")
return
}

View File

@ -644,7 +644,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
}
if isBranchExist {
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, ruleName); err != nil {
if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, ruleName); err != nil {
ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
return
}
@ -669,7 +669,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
}
for _, branchName := range matchedBranches {
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil {
if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
return
}
@ -914,7 +914,7 @@ func EditBranchProtection(ctx *context.APIContext) {
}
if isBranchExist {
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, bpName); err != nil {
if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, bpName); err != nil {
ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
return
}
@ -940,7 +940,7 @@ func EditBranchProtection(ctx *context.APIContext) {
}
for _, branchName := range matchedBranches {
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil {
if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
return
}

View File

@ -398,7 +398,7 @@ func GetEditorconfig(ctx *context.APIContext) {
// canWriteFiles returns true if repository is editable and user has proper access level.
func canWriteFiles(ctx *context.APIContext, branch string) bool {
return ctx.Repo.CanWriteToBranch(ctx.Doer, branch) &&
return ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, branch) &&
!ctx.Repo.Repository.IsMirror &&
!ctx.Repo.Repository.IsArchived
}

View File

@ -552,7 +552,7 @@ func GetIssue(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -661,7 +661,7 @@ func CreateIssue(ctx *context.APIContext) {
}
if form.Closed {
if err := issue_service.ChangeStatus(issue, ctx.Doer, "", true); err != nil {
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", true); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
@ -721,7 +721,7 @@ func EditIssue(ctx *context.APIContext) {
// "$ref": "#/responses/error"
form := web.GetForm(ctx).(*api.EditIssueOption)
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -877,7 +877,7 @@ func DeleteIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@ -933,7 +933,7 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditDeadlineOption)
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()

View File

@ -307,7 +307,7 @@ func DeleteIssueAttachment(ctx *context.APIContext) {
}
func getIssueFromContext(ctx *context.APIContext) *issues_model.Issue {
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64("index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64("index"))
if err != nil {
ctx.NotFoundOrServerError("GetIssueByIndex", issues_model.IsErrIssueNotExist, err)
return nil

View File

@ -64,7 +64,7 @@ func ListIssueComments(ctx *context.APIContext) {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return
}
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return
@ -161,7 +161,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return
}
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return
@ -351,7 +351,7 @@ func CreateIssueComment(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
return

View File

@ -59,7 +59,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
return
}
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IsErrIssueNotExist", err)
@ -484,7 +484,7 @@ func RemoveIssueBlocking(ctx *context.APIContext) {
}
func getParamsIssue(ctx *context.APIContext) *issues_model.Issue {
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IsErrIssueNotExist", err)
@ -518,7 +518,7 @@ func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Is
repo = ctx.Repo.Repository
}
issue, err := issues_model.GetIssueByIndex(repo.ID, form.Index)
issue, err := issues_model.GetIssueByIndex(ctx, repo.ID, form.Index)
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IsErrIssueNotExist", err)

View File

@ -45,7 +45,7 @@ func ListIssueLabels(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -157,7 +157,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -275,7 +275,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -299,7 +299,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
}
func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (*issues_model.Issue, []*issues_model.Label, error) {
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()

View File

@ -41,7 +41,7 @@ func PinIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -98,7 +98,7 @@ func UnpinIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -159,7 +159,7 @@ func MoveIssuePin(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()

View File

@ -269,7 +269,7 @@ func GetIssueReactions(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -389,7 +389,7 @@ func DeleteIssueReaction(ctx *context.APIContext) {
}
func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()

View File

@ -161,7 +161,7 @@ func DeleteIssueStopwatch(ctx *context.APIContext) {
}
func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_model.Issue, error) {
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()

View File

@ -104,7 +104,7 @@ func DelIssueSubscription(ctx *context.APIContext) {
}
func setIssueSubscription(ctx *context.APIContext, watch bool) {
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -185,7 +185,7 @@ func CheckIssueSubscription(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@ -251,7 +251,7 @@ func GetIssueSubscribers(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()

View File

@ -75,7 +75,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
ctx.NotFound("Timetracker is disabled")
return
}
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@ -121,7 +121,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
}
}
count, err := issues_model.CountTrackedTimes(opts)
count, err := issues_model.CountTrackedTimes(ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
@ -132,7 +132,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err)
return
}
if err = trackedTimes.LoadAttributes(); err != nil {
if err = trackedTimes.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
@ -179,7 +179,7 @@ func AddTime(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.AddTimeOption)
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@ -214,12 +214,12 @@ func AddTime(ctx *context.APIContext) {
created = form.Created
}
trackedTime, err := issues_model.AddTime(user, issue, form.Time, created)
trackedTime, err := issues_model.AddTime(ctx, user, issue, form.Time, created)
if err != nil {
ctx.Error(http.StatusInternalServerError, "AddTime", err)
return
}
if err = trackedTime.LoadAttributes(); err != nil {
if err = trackedTime.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
@ -260,7 +260,7 @@ func ResetIssueTime(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@ -331,7 +331,7 @@ func DeleteTime(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@ -443,7 +443,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err)
return
}
if err = trackedTimes.LoadAttributes(); err != nil {
if err = trackedTimes.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
@ -540,7 +540,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
}
}
count, err := issues_model.CountTrackedTimes(opts)
count, err := issues_model.CountTrackedTimes(ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
@ -551,7 +551,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err)
return
}
if err = trackedTimes.LoadAttributes(); err != nil {
if err = trackedTimes.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
@ -601,7 +601,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
return
}
count, err := issues_model.CountTrackedTimes(opts)
count, err := issues_model.CountTrackedTimes(ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
@ -613,7 +613,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
return
}
if err = trackedTimes.LoadAttributes(); err != nil {
if err = trackedTimes.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}

View File

@ -355,16 +355,17 @@ func Generate(ctx *context.APIContext) {
}
opts := repo_module.GenerateRepoOptions{
Name: form.Name,
DefaultBranch: form.DefaultBranch,
Description: form.Description,
Private: form.Private,
GitContent: form.GitContent,
Topics: form.Topics,
GitHooks: form.GitHooks,
Webhooks: form.Webhooks,
Avatar: form.Avatar,
IssueLabels: form.Labels,
Name: form.Name,
DefaultBranch: form.DefaultBranch,
Description: form.Description,
Private: form.Private,
GitContent: form.GitContent,
Topics: form.Topics,
GitHooks: form.GitHooks,
Webhooks: form.Webhooks,
Avatar: form.Avatar,
IssueLabels: form.Labels,
ProtectedBranch: form.ProtectedBranch,
}
if !opts.IsValid() {

View File

@ -20,7 +20,7 @@ import (
func Routes() *web.Route {
base := web.NewRoute()
base.Use(common.ProtocolMiddlewares()...)
base.Methods("GET, HEAD", "/assets/*", public.AssetsHandlerFunc("/assets/"))
base.Methods("GET, HEAD", "/assets/*", public.FileHandlerFunc())
r := web.NewRoute()
r.Use(common.Sessioner(), Contexter())

View File

@ -55,7 +55,7 @@ func (ctx *preReceiveContext) CanWriteCode() bool {
if !ctx.loadPusherAndPermission() {
return false
}
ctx.canWriteCode = issues_model.CanMaintainerWriteToBranch(ctx.userPerm, ctx.branchName, ctx.user) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
ctx.canWriteCode = issues_model.CanMaintainerWriteToBranch(ctx, ctx.userPerm, ctx.branchName, ctx.user) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
ctx.checkedCanWriteCode = true
}
return ctx.canWriteCode

View File

@ -220,7 +220,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest:
desc = strings.Join(act.GetIssueInfos(), "#")
content = renderMarkdown(ctx, act, act.GetIssueContent())
content = renderMarkdown(ctx, act, act.GetIssueContent(ctx))
case activities_model.ActionCommentIssue, activities_model.ActionApprovePullRequest, activities_model.ActionRejectPullRequest, activities_model.ActionCommentPull:
desc = act.GetIssueTitle()
comment := act.GetIssueInfos()[1]

View File

@ -34,9 +34,12 @@ func DummyOK(w http.ResponseWriter, req *http.Request) {
}
func RobotsTxt(w http.ResponseWriter, req *http.Request) {
filePath := util.FilePathJoinAbs(setting.CustomPath, "robots.txt")
robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt")
if ok, _ := util.IsExist(robotsTxt); !ok {
robotsTxt = util.FilePathJoinAbs(setting.CustomPath, "robots.txt") // the legacy "robots.txt"
}
httpcache.SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
http.ServeFile(w, req, filePath)
http.ServeFile(w, req, robotsTxt)
}
func StaticRedirect(target string) func(w http.ResponseWriter, req *http.Request) {

View File

@ -4,10 +4,14 @@
package actions
import (
"archive/zip"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
@ -479,7 +483,6 @@ type ArtifactsViewResponse struct {
type ArtifactsViewItem struct {
Name string `json:"name"`
Size int64 `json:"size"`
ID int64 `json:"id"`
}
func ArtifactsView(ctx *context_module.Context) {
@ -493,7 +496,7 @@ func ArtifactsView(ctx *context_module.Context) {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
artifacts, err := actions_model.ListUploadedArtifactsByRunID(ctx, run.ID)
artifacts, err := actions_model.ListUploadedArtifactsMeta(ctx, run.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
@ -505,7 +508,6 @@ func ArtifactsView(ctx *context_module.Context) {
artifactsResponse.Artifacts = append(artifactsResponse.Artifacts, &ArtifactsViewItem{
Name: art.ArtifactName,
Size: art.FileSize,
ID: art.ID,
})
}
ctx.JSON(http.StatusOK, artifactsResponse)
@ -513,15 +515,8 @@ func ArtifactsView(ctx *context_module.Context) {
func ArtifactsDownloadView(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")
artifactID := ctx.ParamsInt64("id")
artifactName := ctx.Params("artifact_name")
artifact, err := actions_model.GetArtifactByID(ctx, artifactID)
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, err.Error())
} else if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
@ -531,20 +526,49 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
if artifact.RunID != run.ID {
ctx.Error(http.StatusNotFound, "artifact not found")
return
}
f, err := storage.ActionsArtifacts.Open(artifact.StoragePath)
artifacts, err := actions_model.ListArtifactsByRunIDAndName(ctx, run.ID, artifactName)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
defer f.Close()
if len(artifacts) == 0 {
ctx.Error(http.StatusNotFound, "artifact not found")
return
}
ctx.ServeContent(f, &context_module.ServeHeaderOptions{
Filename: artifact.ArtifactName,
LastModified: artifact.CreatedUnix.AsLocalTime(),
})
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(artifactName), artifactName))
writer := zip.NewWriter(ctx.Resp)
defer writer.Close()
for _, art := range artifacts {
f, err := storage.ActionsArtifacts.Open(art.StoragePath)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
var r io.ReadCloser
if art.ContentEncoding == "gzip" {
r, err = gzip.NewReader(f)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
} else {
r = f
}
defer r.Close()
w, err := writer.Create(art.ArtifactPath)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
if _, err := io.Copy(w, r); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
}
}

View File

@ -754,6 +754,12 @@ func CompareDiff(ctx *context.Context) {
}
ctx.Data["HeadBranches"] = headBranches
// For compare repo branches
PrepareBranchList(ctx)
if ctx.Written() {
return
}
headTags, err := repo_model.GetTagNamesByRepoID(ctx, ci.HeadRepo.ID)
if err != nil {
ctx.ServerError("GetTagNamesByRepoID", err)

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