Compare commits

...

44 Commits

Author SHA1 Message Date
xxq250 127fb3b294 build.sh arm 改x86 2026-07-29 09:46:37 +08:00
xxq250 95e7597ad2 app.ini 重要参数增加 2026-07-29 00:31:32 +08:00
xxq250 5281667f34 Merge remote-tracking branch 'origin/feature_v262' into feature_v262 2026-07-28 23:51:08 +08:00
xxq250 dc5acb4bdf 登录才可读仓库 2026-07-28 23:50:58 +08:00
yystopf c13c3e269a 修复:compare diff 为空 2026-07-28 11:23:04 +08:00
yystopf d9432b0b5a Merge remote-tracking branch 'refs/remotes/origin/feature_v262' into feature_v262 2026-07-28 10:29:47 +08:00
yystopf 8232eae5b2 修复:compare file报错 2026-07-28 10:28:46 +08:00
xxq250 2d43a3bb57 bin file delete 2026-07-07 16:01:08 +08:00
yystopf 2c302528e2 更改:chroma版本 2026-07-07 15:50:34 +08:00
yystopf a81cbe29eb 更改:action查不到流水线 2026-07-07 10:52:59 +08:00
yystopf d86464a70d 修复:已知问题修复 2026-07-01 08:56:26 +08:00
yystopf 8eded066ea 修复:invalid memory address or nil pointer dereference 2026-06-12 15:26:49 +08:00
yystopf 973b96914c 更改:go-chi版本 2026-06-12 09:04:57 +08:00
yystopf 270d2c643e 更改:v1.62.0基本样式 2026-06-08 17:38:36 +08:00
xxq250 43db48c1ae fixed yaml/v4版本 2026-06-05 09:36:05 +08:00
yystopf 292463a3b9 更改:v1.26.2版本更新 2026-06-05 09:08:06 +08:00
yystopf b2d0e593d3 修复:fork仓库创建pr无法获取对比结果 2026-05-09 17:18:22 +08:00
yystopf b18d89928d 修复:创建组织修复 2026-05-09 16:26:58 +08:00
yystopf c9e6203d5e 更改:删除分支更新 2026-05-09 15:41:58 +08:00
yystopf fa05b27afa 修复:diff适配旧版 2026-05-07 09:56:11 +08:00
yystopf 11249b31b1 新增:样式文件 2026-04-28 17:06:50 +08:00
xxq250 c7be4d0f9a fixed 打包 2026-04-28 15:46:13 +08:00
yystopf 0272990cbe 新增:更新文件 2026-04-27 09:14:40 +08:00
virus 84e69abe4f 更改:部分文件更改 2025-11-20 17:33:36 +08:00
yystopf b3de9f9ae4 更改:更新至v1.25.1 2025-11-20 11:32:43 +08:00
yystopf 1b7a0ffb36 更改:版本号 2025-11-11 14:10:00 +08:00
yystopf 684b2733a0 新增:倉庫密鈅列表接口 2025-11-11 14:01:23 +08:00
yystopf f091079312 新增:仓库组织内代码搜索接口 2025-09-03 09:15:06 +08:00
yystopf 4c65b6777a 新增:detail_status在runs接口 2025-09-01 17:38:59 +08:00
yystopf db3a241c92 新增:detail_status在runs接口 2025-09-01 17:35:45 +08:00
xxq250 868d41e749 默认开启登录访问,与叁数RequireSignInView无关,版本号 2025-08-12 10:52:35 +08:00
xxq250 902314a413 默认开启登录访问,与叁数RequireSignInView无关 2025-08-12 10:44:56 +08:00
xxq250 7d71cba643 调整版本号,增加日期 2025-07-29 11:24:17 +08:00
xxq250 6f0092b14c fixed action run stop api 2025-07-28 15:54:07 +08:00
xxq250 31c339fd1d add action run stop api 2025-07-28 15:41:45 +08:00
xxq250 713af786c1 add action run stop api 2025-07-28 15:10:55 +08:00
yystopf 37f8da019d 更改:代码搜索版本号 2025-07-24 17:14:27 +08:00
yystopf dfe0ce8de0 新增:代码搜索接口 2025-07-24 08:30:51 +08:00
yystopf c954b4799e fix:获取冲突文件变更 2025-06-24 14:52:46 +08:00
yystopf 8910972c65 新增:重新打开pr接口 2025-06-23 19:26:16 +08:00
yystopf b015baf624 新增:wiki版本库相关接口 2025-05-12 13:12:59 +08:00
xxq250 4c2cc2a658 fixed 支持自定义仓库大小限制,bug 2025-04-28 10:47:27 +08:00
xxq250 89f6f640e1 fixed 支持自定义仓库大小限制 2025-04-18 16:09:39 +08:00
xxq250 ca5c03bfbf fixed 1、增加仓库大小限制;2、arm架构下hook事件问题;3、更新仓库限制大小 2025-04-11 14:35:39 +08:00
2250 changed files with 195578 additions and 138860 deletions

View File

@ -66,6 +66,7 @@ SSH_LISTEN_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = n2kib4qdArULO57JW0jD2Ygm3z1ehzI8Y4zVfbxouyY
OFFLINE_MODE = false
PUBLIC_URL_DETECTION = never
[mailer]
ENABLED = false
@ -83,6 +84,9 @@ DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.example.org
SHOW_REGISTRATION_BUTTON = false
[service.explore]
REQUIRE_SIGNIN_VIEW = true
[webhook]
ALLOWED_HOST_LIST = *
@ -119,12 +123,12 @@ DEFAULT_PAGING_NUM = 200
MAX_RESPONSE_ITEMS = 200
[git]
PATH =
PATH =
DISABLE_DIFF_HIGHLIGHT = false
MAX_GIT_DIFF_LINES = 1000
MAX_GIT_DIFF_LINE_CHARACTERS = 5000
MAX_GIT_DIFF_FILES = 100
GC_ARGS =
GC_ARGS =
EnableAutoGitWireProtocol = true
COMMITS_RANGE_SIZE = 50
@ -172,3 +176,7 @@ ENABLED = true
[ssh.minimum_key_sizes]
RSA = 2048
[actions]
ENABLED = true
WORKFLOW_DIRS = .gitlink/workflows,.ci/workflows

View File

@ -11,5 +11,10 @@ go run build/generate-bindata.go options options vendor/code.gitea.io/gitea/modu
go run build/generate-bindata.go public public vendor/code.gitea.io/gitea/modules/public/bindata.go
go run build/generate-bindata.go templates templates vendor/code.gitea.io/gitea/modules/templates/bindata.go
go run build/generate-bindata-new.go modules/schemas vendor/code.gitea.io/gitea/modules/migration/bindata.dat
go run build/generate-bindata-new.go options vendor/code.gitea.io/gitea/modules/options/bindata.dat
go run build/generate-bindata-new.go public vendor/code.gitea.io/gitea/modules/public/bindata.dat
go run build/generate-bindata-new.go templates vendor/code.gitea.io/gitea/modules/templates/bindata.dat
echo "go build -tags 'bindata' -o gitea_hat main.go..."
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags 'bindata' -o gitea_hat main.go

View File

@ -0,0 +1,27 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build ignore
package main
import (
"fmt"
"os"
"code.gitea.io/gitea/modules/assetfs"
)
func main() {
if len(os.Args) != 3 {
fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}")
os.Exit(1)
}
dir, filename := os.Args[1], os.Args[2]
fmt.Printf("generating bindata for %s to %s\n", dir, filename)
if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil {
fmt.Printf("failed: %s\n", err.Error())
os.Exit(1)
}
}

54
cmd/actions.go Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func newActionsCommand() *cli.Command {
return &cli.Command{
Name: "actions",
Usage: "Manage Gitea Actions",
Commands: []*cli.Command{
newActionsGenerateRunnerTokenCommand(),
},
}
}
func newActionsGenerateRunnerTokenCommand() *cli.Command {
return &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",
Aliases: []string{"s"},
Value: "",
Usage: "{owner}[/{repo}] - leave empty for a global runner",
},
},
}
}
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
setting.MustInstalled()
scope := c.String("scope")
respText, extra := private.GenerateActionsRunnerToken(ctx, scope)
if extra.HasError() {
return handleCliResponseExtra(extra)
}
_, _ = fmt.Printf("%s\n", respText.Text)
return nil
}

174
cmd/admin.go Normal file
View File

@ -0,0 +1,174 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"github.com/urfave/cli/v3"
)
func newAdminCommand() *cli.Command {
return &cli.Command{
Name: "admin",
Usage: "Perform common administrative operations",
Commands: []*cli.Command{
newUserCommand(),
newRepoSyncReleasesCommand(),
newRegenerateCommand(),
newAuthCommand(),
newSendMailCommand(),
},
}
}
func newRepoSyncReleasesCommand() *cli.Command {
return &cli.Command{
Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases,
}
}
func newRegenerateCommand() *cli.Command {
return &cli.Command{
Name: "regenerate",
Usage: "Regenerate specific files",
Commands: []*cli.Command{
newRegenerateHooksCommand(),
newRegenerateKeysCommand(),
},
}
}
func newAuthCommand() *cli.Command {
return &cli.Command{
Name: "auth",
Usage: "Modify external auth providers",
Commands: []*cli.Command{
microcmdAuthAddOauth(),
microcmdAuthUpdateOauth(),
microcmdAuthAddLdapBindDn(),
microcmdAuthUpdateLdapBindDn(),
microcmdAuthAddLdapSimpleAuth(),
microcmdAuthUpdateLdapSimpleAuth(),
microcmdAuthAddSMTP(),
microcmdAuthUpdateSMTP(),
newAuthListCommand(),
newAuthDeleteCommand(),
},
}
}
func newSendMailCommand() *cli.Command {
return &cli.Command{
Name: "sendmail",
Usage: "Send a message to all users",
Action: runSendMail,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "title",
Usage: "a title of a message",
Required: true,
},
&cli.StringFlag{
Name: "content",
Usage: "a content of a message",
Value: "",
},
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "A flag to bypass a confirmation step",
},
},
}
}
func idFlag() *cli.Int64Flag {
return &cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}
}
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
if err := git.InitSimple(); err != nil {
return err
}
log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ {
repos, count, err := repo_model.SearchRepositoryByName(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: repo_model.RepositoryListDefaultPageSize,
Page: page,
},
Private: true,
})
if err != nil {
return fmt.Errorf("SearchRepositoryByName: %w", err)
}
if len(repos) == 0 {
break
}
log.Trace("Processing next %d repos of %d", len(repos), count)
for _, repo := range repos {
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RelativePath())
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
log.Warn("OpenRepository: %v", err)
continue
}
oldnum, err := getReleaseCount(ctx, repo.ID)
if err != nil {
log.Warn(" GetReleaseCountByRepoID: %v", err)
}
log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
if _, err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
log.Warn(" SyncReleasesWithTags: %v", err)
gitRepo.Close()
continue
}
count, err = getReleaseCount(ctx, repo.ID)
if err != nil {
log.Warn(" GetReleaseCountByRepoID: %v", err)
gitRepo.Close()
continue
}
log.Trace("repo %s releases synchronized to tags: from %d to %d",
repo.FullName(), oldnum, count)
gitRepo.Close()
}
}
return nil
}
func getReleaseCount(ctx context.Context, id int64) (int64, error) {
return db.Count[repo_model.Release](
ctx,
repo_model.FindReleasesOptions{
RepoID: id,
IncludeTags: true,
},
)
}

109
cmd/admin_auth.go Normal file
View File

@ -0,0 +1,109 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"text/tabwriter"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
auth_service "code.gitea.io/gitea/services/auth"
"github.com/urfave/cli/v3"
)
func newAuthDeleteCommand() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag()},
Action: runDeleteAuth,
}
}
func newAuthListCommand() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "min-width",
Usage: "Minimal cell width including any padding for the formatted table",
Value: 0,
},
&cli.IntFlag{
Name: "tab-width",
Usage: "width of tab characters in formatted table (equivalent number of spaces)",
Value: 8,
},
&cli.IntFlag{
Name: "padding",
Usage: "padding added to a cell before computing its width",
Value: 1,
},
&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{
Name: "vertical-bars",
Usage: "Set to true to print vertical bars between columns",
},
},
}
}
func runListAuth(ctx context.Context, c *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
authSources, err := db.Find[auth_model.Source](ctx, auth_model.FindSourcesOptions{})
if err != nil {
return err
}
flags := tabwriter.AlignRight
if c.Bool("vertical-bars") {
flags |= tabwriter.Debug
}
padChar := byte('\t')
if len(c.String("pad-char")) > 0 {
padChar = c.String("pad-char")[0]
}
// loop through each source and print
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
for _, source := range authSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
}
w.Flush()
return nil
}
func runDeleteAuth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
if err := initDB(ctx); err != nil {
return err
}
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
return auth_service.DeleteSource(ctx, source)
}

462
cmd/admin_auth_ldap.go Normal file
View File

@ -0,0 +1,462 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli/v3"
)
type (
authService struct {
initDB func(ctx context.Context) error
createAuthSource func(context.Context, *auth.Source) error
updateAuthSource func(context.Context, *auth.Source) error
getAuthSourceByID func(ctx context.Context, id int64) (*auth.Source, error)
}
)
func commonLdapCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "Authentication name.",
},
&cli.BoolFlag{
Name: "not-active",
Usage: "Deactivate the authentication source.",
},
&cli.BoolFlag{
Name: "active",
Usage: "Activate the authentication source.",
},
&cli.StringFlag{
Name: "security-protocol",
Usage: "Security protocol name.",
},
&cli.BoolFlag{
Name: "skip-tls-verify",
Usage: "Disable TLS verification.",
},
&cli.StringFlag{
Name: "host",
Usage: "The address where the LDAP server can be reached.",
},
&cli.IntFlag{
Name: "port",
Usage: "The port to use when connecting to the LDAP server.",
},
&cli.StringFlag{
Name: "user-search-base",
Usage: "The LDAP base at which user accounts will be searched for.",
},
&cli.StringFlag{
Name: "user-filter",
Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.",
},
&cli.StringFlag{
Name: "admin-filter",
Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
},
&cli.StringFlag{
Name: "restricted-filter",
Usage: "An LDAP filter specifying if a user should be given restricted status.",
},
&cli.BoolFlag{
Name: "allow-deactivate-all",
Usage: "Allow empty search results to deactivate all users.",
},
&cli.StringFlag{
Name: "username-attribute",
Usage: "The attribute of the users LDAP record containing the user name.",
},
&cli.StringFlag{
Name: "firstname-attribute",
Usage: "The attribute of the users LDAP record containing the users first name.",
},
&cli.StringFlag{
Name: "surname-attribute",
Usage: "The attribute of the users LDAP record containing the users surname.",
},
&cli.StringFlag{
Name: "email-attribute",
Usage: "The attribute of the users LDAP record containing the users email address.",
},
&cli.StringFlag{
Name: "public-ssh-key-attribute",
Usage: "The attribute of the users LDAP record containing the users public ssh key.",
},
&cli.BoolFlag{
Name: "ssh-keys-are-verified",
Usage: "Set to true to automatically flag SSH keys in LDAP as verified.",
},
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
&cli.StringFlag{
Name: "avatar-attribute",
Usage: "The attribute of the users LDAP record containing the users avatar.",
},
}
}
func ldapBindDnCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.",
},
&cli.StringFlag{
Name: "bind-password",
Usage: "The password for the Bind DN, if any.",
},
&cli.BoolFlag{
Name: "attributes-in-bind",
Usage: "Fetch attributes in bind DN context.",
},
&cli.BoolFlag{
Name: "synchronize-users",
Usage: "Enable user synchronization.",
},
&cli.BoolFlag{
Name: "disable-synchronize-users",
Usage: "Disable user synchronization.",
},
&cli.UintFlag{
Name: "page-size",
Usage: "Search page size.",
},
&cli.BoolFlag{
Name: "enable-groups",
Usage: "Enable LDAP groups",
},
&cli.StringFlag{
Name: "group-search-base-dn",
Usage: "The LDAP base DN at which group accounts will be searched for",
},
&cli.StringFlag{
Name: "group-member-attribute",
Usage: "Group attribute containing list of users",
},
&cli.StringFlag{
Name: "group-user-attribute",
Usage: "User attribute listed in group",
},
&cli.StringFlag{
Name: "group-filter",
Usage: "Verify group membership in LDAP",
},
&cli.StringFlag{
Name: "group-team-map",
Usage: "Map LDAP groups to Organization teams",
},
&cli.BoolFlag{
Name: "group-team-map-removal",
Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
})
}
func ldapSimpleAuthCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "user-dn",
Usage: "The user's DN.",
})
}
func microcmdAuthAddLdapBindDn() *cli.Command {
return &cli.Command{
Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().addLdapBindDn(ctx, cmd)
},
Flags: ldapBindDnCLIFlags(),
}
}
func microcmdAuthUpdateLdapBindDn() *cli.Command {
return &cli.Command{
Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().updateLdapBindDn(ctx, cmd)
},
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),
}
}
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
return &cli.Command{
Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().addLdapSimpleAuth(ctx, cmd)
},
Flags: ldapSimpleAuthCLIFlags(),
}
}
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
return &cli.Command{
Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().updateLdapSimpleAuth(ctx, cmd)
},
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
}
}
// newAuthService creates a service with default functions.
func newAuthService() *authService {
return &authService{
initDB: initDB,
createAuthSource: auth.CreateSource,
updateAuthSource: auth.UpdateSource,
getAuthSourceByID: auth.GetSourceByID,
}
}
// parseAuthSourceLdap assigns values on authSource according to command line flags.
func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) {
if c.IsSet("name") {
authSource.Name = c.String("name")
}
if c.IsSet("not-active") {
authSource.IsActive = !c.Bool("not-active")
}
if c.IsSet("active") {
authSource.IsActive = c.Bool("active")
}
if c.IsSet("synchronize-users") {
authSource.IsSyncEnabled = c.Bool("synchronize-users")
}
if c.IsSet("disable-synchronize-users") {
authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
}
authSource.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
}
// parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("name") {
config.Name = c.String("name")
}
if c.IsSet("host") {
config.Host = c.String("host")
}
if c.IsSet("port") {
config.Port = c.Int("port")
}
if c.IsSet("security-protocol") {
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
if !ok {
return fmt.Errorf("unknown security protocol name: %s", c.String("security-protocol"))
}
config.SecurityProtocol = p
}
if c.IsSet("skip-tls-verify") {
config.SkipVerify = c.Bool("skip-tls-verify")
}
if c.IsSet("bind-dn") {
config.BindDN = c.String("bind-dn")
}
if c.IsSet("user-dn") {
config.UserDN = c.String("user-dn")
}
if c.IsSet("bind-password") {
config.BindPassword = c.String("bind-password")
}
if c.IsSet("user-search-base") {
config.UserBase = c.String("user-search-base")
}
if c.IsSet("username-attribute") {
config.AttributeUsername = c.String("username-attribute")
}
if c.IsSet("firstname-attribute") {
config.AttributeName = c.String("firstname-attribute")
}
if c.IsSet("surname-attribute") {
config.AttributeSurname = c.String("surname-attribute")
}
if c.IsSet("email-attribute") {
config.AttributeMail = c.String("email-attribute")
}
if c.IsSet("attributes-in-bind") {
config.AttributesInBind = c.Bool("attributes-in-bind")
}
if c.IsSet("public-ssh-key-attribute") {
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
}
if c.IsSet("ssh-keys-are-verified") {
config.SSHKeysAreVerified = c.Bool("ssh-keys-are-verified")
}
if c.IsSet("avatar-attribute") {
config.AttributeAvatar = c.String("avatar-attribute")
}
if c.IsSet("page-size") {
config.SearchPageSize = uint32(c.Uint("page-size"))
}
if c.IsSet("user-filter") {
config.Filter = c.String("user-filter")
}
if c.IsSet("admin-filter") {
config.AdminFilter = c.String("admin-filter")
}
if c.IsSet("restricted-filter") {
config.RestrictedFilter = c.String("restricted-filter")
}
if c.IsSet("allow-deactivate-all") {
config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
}
if c.IsSet("enable-groups") {
config.GroupsEnabled = c.Bool("enable-groups")
}
if c.IsSet("group-search-base-dn") {
config.GroupDN = c.String("group-search-base-dn")
}
if c.IsSet("group-member-attribute") {
config.GroupMemberUID = c.String("group-member-attribute")
}
if c.IsSet("group-user-attribute") {
config.UserUID = c.String("group-user-attribute")
}
if c.IsSet("group-filter") {
config.GroupFilter = c.String("group-filter")
}
if c.IsSet("group-team-map") {
config.GroupTeamMap = c.String("group-team-map")
}
if c.IsSet("group-team-map-removal") {
config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
}
return nil
}
// findLdapSecurityProtocolByName finds security protocol by its name ignoring case.
// It returns the value of the security protocol and if it was found.
func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
for i, n := range ldap.SecurityProtocolNames {
if strings.EqualFold(name, n) {
return i, true
}
}
return 0, false
}
// getAuthSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type.
func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) {
if err := argsSet(c, "id"); err != nil {
return nil, err
}
authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return nil, err
}
if authSource.Type != authType {
return nil, fmt.Errorf("invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
}
return authSource, nil
}
// addLdapBindDn adds a new LDAP via Bind DN authentication source.
func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err
}
if err := a.initDB(ctx); err != nil {
return err
}
authSource := &auth.Source{
Type: auth.LDAP,
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
},
}
parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
return a.createAuthSource(ctx, authSource)
}
// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
authSource, err := a.getAuthSource(ctx, c, auth.LDAP)
if err != nil {
return err
}
parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
return a.updateAuthSource(ctx, authSource)
}
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err
}
if err := a.initDB(ctx); err != nil {
return err
}
authSource := &auth.Source{
Type: auth.DLDAP,
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
},
}
parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
return a.createAuthSource(ctx, authSource)
}
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
authSource, err := a.getAuthSource(ctx, c, auth.DLDAP)
if err != nil {
return err
}
parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
return a.updateAuthSource(ctx, authSource)
}

322
cmd/admin_auth_oauth.go Normal file
View File

@ -0,0 +1,322 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"net/url"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli/v3"
)
func oauthCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
&cli.StringFlag{
Name: "provider",
Value: "",
Usage: "OAuth2 Provider",
},
&cli.StringFlag{
Name: "key",
Value: "",
Usage: "Client ID (Key)",
},
&cli.StringFlag{
Name: "secret",
Value: "",
Usage: "Client Secret",
},
&cli.StringFlag{
Name: "auto-discover-url",
Value: "",
Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
},
&cli.StringFlag{
Name: "use-custom-urls",
Value: "false",
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
},
&cli.StringFlag{
Name: "custom-tenant-id",
Value: "",
Usage: "Use custom Tenant ID for OAuth endpoints",
},
&cli.StringFlag{
Name: "custom-auth-url",
Value: "",
Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-token-url",
Value: "",
Usage: "Use a custom Token URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-profile-url",
Value: "",
Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-email-url",
Value: "",
Usage: "Use a custom Email URL (option for GitHub)",
},
&cli.StringFlag{
Name: "icon-url",
Value: "",
Usage: "Custom icon URL for OAuth2 login source",
},
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
&cli.StringSliceFlag{
Name: "scopes",
Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source",
},
&cli.StringFlag{
Name: "ssh-public-key-claim-name",
Usage: "Claim name that provides SSH public keys",
},
&cli.StringFlag{
Name: "full-name-claim-name",
Usage: "Claim name that provides user's full name",
},
&cli.StringFlag{
Name: "required-claim-name",
Value: "",
Usage: "Claim name that has to be set to allow users to login with this source",
},
&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{
Name: "group-claim-name",
Value: "",
Usage: "Claim name providing group names for this source",
},
&cli.StringFlag{
Name: "admin-group",
Value: "",
Usage: "Group Claim value for administrator users",
},
&cli.StringFlag{
Name: "restricted-group",
Value: "",
Usage: "Group Claim value for restricted users",
},
&cli.StringFlag{
Name: "group-team-map",
Value: "",
Usage: "JSON mapping between groups and org teams",
},
&cli.BoolFlag{
Name: "group-team-map-removal",
Usage: "Activate automatic team membership removal depending on groups",
},
}
}
func microcmdAuthAddOauth() *cli.Command {
return &cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().runAddOauth(ctx, cmd)
},
Flags: oauthCLIFlags(),
}
}
func microcmdAuthUpdateOauth() *cli.Command {
return &cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().runUpdateOauth(ctx, cmd)
},
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}}, oauthCLIFlags()[1:]...)...),
}
}
func parseOAuth2Config(c *cli.Command) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
TokenURL: c.String("custom-token-url"),
AuthURL: c.String("custom-auth-url"),
ProfileURL: c.String("custom-profile-url"),
EmailURL: c.String("custom-email-url"),
Tenant: c.String("custom-tenant-id"),
}
} else {
customURLMapping = nil
}
return &oauth2.Source{
Provider: c.String("provider"),
ClientID: c.String("key"),
ClientSecret: c.String("secret"),
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping,
IconURL: c.String("icon-url"),
Scopes: c.StringSlice("scopes"),
RequiredClaimName: c.String("required-claim-name"),
RequiredClaimValue: c.String("required-claim-value"),
GroupClaimName: c.String("group-claim-name"),
AdminGroup: c.String("admin-group"),
RestrictedGroup: c.String("restricted-group"),
GroupTeamMap: c.String("group-team-map"),
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
SSHPublicKeyClaimName: c.String("ssh-public-key-claim-name"),
FullNameClaimName: c.String("full-name-claim-name"),
}
}
func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
config := parseOAuth2Config(c)
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL)
}
}
return a.createAuthSource(ctx, &auth_model.Source{
Type: auth_model.OAuth2,
Name: c.String("name"),
IsActive: true,
Cfg: config,
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
})
}
func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
if err := a.initDB(ctx); err != nil {
return err
}
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
oAuth2Config := source.Cfg.(*oauth2.Source)
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("provider") {
oAuth2Config.Provider = c.String("provider")
}
if c.IsSet("key") {
oAuth2Config.ClientID = c.String("key")
}
if c.IsSet("secret") {
oAuth2Config.ClientSecret = c.String("secret")
}
if c.IsSet("auto-discover-url") {
oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
}
if c.IsSet("icon-url") {
oAuth2Config.IconURL = c.String("icon-url")
}
if c.IsSet("scopes") {
oAuth2Config.Scopes = c.StringSlice("scopes")
}
if c.IsSet("required-claim-name") {
oAuth2Config.RequiredClaimName = c.String("required-claim-name")
}
if c.IsSet("required-claim-value") {
oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
}
if c.IsSet("group-claim-name") {
oAuth2Config.GroupClaimName = c.String("group-claim-name")
}
if c.IsSet("admin-group") {
oAuth2Config.AdminGroup = c.String("admin-group")
}
if c.IsSet("restricted-group") {
oAuth2Config.RestrictedGroup = c.String("restricted-group")
}
if c.IsSet("group-team-map") {
oAuth2Config.GroupTeamMap = c.String("group-team-map")
}
if c.IsSet("group-team-map-removal") {
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
}
if c.IsSet("ssh-public-key-claim-name") {
oAuth2Config.SSHPublicKeyClaimName = c.String("ssh-public-key-claim-name")
}
if c.IsSet("full-name-claim-name") {
oAuth2Config.FullNameClaimName = c.String("full-name-claim-name")
}
// update custom URL mapping
customURLMapping := &oauth2.CustomURLMapping{}
if oAuth2Config.CustomURLMapping != nil {
customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
customURLMapping.TokenURL = c.String("custom-token-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") {
customURLMapping.AuthURL = c.String("custom-auth-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") {
customURLMapping.ProfileURL = c.String("custom-profile-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") {
customURLMapping.EmailURL = c.String("custom-email-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") {
customURLMapping.Tenant = c.String("custom-tenant-id")
}
oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(ctx, source)
}

200
cmd/admin_auth_smtp.go Normal file
View File

@ -0,0 +1,200 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"strings"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/smtp"
"github.com/urfave/cli/v3"
)
func smtpCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
&cli.StringFlag{
Name: "auth-type",
Value: "PLAIN",
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
},
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "SMTP Host",
},
&cli.IntFlag{
Name: "port",
Usage: "SMTP Port",
},
&cli.BoolFlag{
Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
},
&cli.BoolFlag{
Name: "skip-verify",
Usage: "Skip TLS verify.",
},
&cli.StringFlag{
Name: "helo-hostname",
Value: "",
Usage: "Hostname sent with HELO. Leave blank to send current hostname",
},
&cli.BoolFlag{
Name: "disable-helo",
Usage: "Disable SMTP helo.",
},
&cli.StringFlag{
Name: "allowed-domains",
Value: "",
Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
},
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.",
},
&cli.BoolFlag{
Name: "active",
Usage: "This Authentication Source is Activated.",
Value: true,
},
}
}
func microcmdAuthUpdateSMTP() *cli.Command {
return &cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().runUpdateSMTP(ctx, cmd)
},
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}}, smtpCLIFlags()[1:]...)...),
}
}
func microcmdAuthAddSMTP() *cli.Command {
return &cli.Command{
Name: "add-smtp",
Usage: "Add new SMTP authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().runAddSMTP(ctx, cmd)
},
Flags: smtpCLIFlags(),
}
}
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) {
return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5")
}
conf.Auth = c.String("auth-type")
}
if c.IsSet("host") {
conf.Host = c.String("host")
}
if c.IsSet("port") {
conf.Port = c.Int("port")
}
if c.IsSet("allowed-domains") {
conf.AllowedDomains = c.String("allowed-domains")
}
if c.IsSet("force-smtps") {
conf.ForceSMTPS = c.Bool("force-smtps")
}
if c.IsSet("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.Bool("disable-helo")
}
return nil
}
func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
if !c.IsSet("name") || len(c.String("name")) == 0 {
return errors.New("name must be set")
}
if !c.IsSet("host") || len(c.String("host")) == 0 {
return errors.New("host must be set")
}
if !c.IsSet("port") {
return errors.New("port must be set")
}
active := true
if c.IsSet("active") {
active = c.Bool("active")
}
var smtpConfig smtp.Source
if err := parseSMTPConfig(c, &smtpConfig); err != nil {
return err
}
// If not set default to PLAIN
if len(smtpConfig.Auth) == 0 {
smtpConfig.Auth = "PLAIN"
}
return a.createAuthSource(ctx, &auth_model.Source{
Type: auth_model.SMTP,
Name: c.String("name"),
IsActive: active,
Cfg: &smtpConfig,
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
})
}
func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
if err := a.initDB(ctx); err != nil {
return err
}
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
smtpConfig := source.Cfg.(*smtp.Source)
if err := parseSMTPConfig(c, smtpConfig); err != nil {
return err
}
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("active") {
source.IsActive = c.Bool("active")
}
source.Cfg = smtpConfig
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(ctx, source)
}

44
cmd/admin_regenerate.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"code.gitea.io/gitea/modules/graceful"
asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/urfave/cli/v3"
)
func newRegenerateHooksCommand() *cli.Command {
return &cli.Command{
Name: "hooks",
Usage: "Regenerate git-hooks",
Action: runRegenerateHooks,
}
}
func newRegenerateKeysCommand() *cli.Command {
return &cli.Command{
Name: "keys",
Usage: "Regenerate authorized_keys file",
Action: runRegenerateKeys,
}
}
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
}
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
return asymkey_service.RewriteAllPublicKeys(ctx)
}

23
cmd/admin_user.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"github.com/urfave/cli/v3"
)
func newUserCommand() *cli.Command {
return &cli.Command{
Name: "user",
Usage: "Modify users",
Commands: []*cli.Command{
microcmdUserCreate(),
newUserListCommand(),
microcmdUserChangePassword(),
microcmdUserDelete(),
newUserGenerateAccessTokenCommand(),
microcmdUserMustChangePassword(),
},
}
}

View File

@ -0,0 +1,78 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3"
)
func microcmdUserChangePassword() *cli.Command {
return &cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "The user to change password for",
Required: true,
},
&cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Usage: "New password to set for user",
Required: true,
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password (can be disabled by --must-change-password=false)",
Value: true,
},
},
}
}
func runChangePassword(ctx context.Context, c *cli.Command) error {
if !setting.IsInTesting {
if err := initDB(ctx); err != nil {
return err
}
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
opts := &user_service.UpdateAuthOptions{
Password: optional.Some(c.String("password")),
MustChangePassword: optional.Some(c.Bool("must-change-password")),
}
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
switch {
case errors.Is(err, password.ErrMinLength):
return fmt.Errorf("password is not long enough, needs to be at least %d characters", setting.MinPasswordLength)
case errors.Is(err, password.ErrComplexity):
return errors.New("password does not meet complexity requirements")
case errors.Is(err, password.ErrIsPwned):
return errors.New("the password is in a list of stolen passwords previously exposed in public data breaches, please try again with a different password, to see more details: https://haveibeenpwned.com/Passwords")
default:
return err
}
}
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
return nil
}

241
cmd/admin_user_create.go Normal file
View File

@ -0,0 +1,241 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"strings"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func microcmdUserCreate() *cli.Command {
return &cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{
{
Flags: [][]cli.Flag{
{
&cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
},
&cli.StringFlag{
Name: "username",
Usage: "Username",
},
},
},
Required: true,
},
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "user-type",
Usage: "Set user's type: individual or bot",
Value: "individual",
},
&cli.StringFlag{
Name: "password",
Usage: "User password",
},
&cli.StringFlag{
Name: "email",
Usage: "User email address",
Required: true,
},
&cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
&cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
HideDefault: true,
},
&cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
&cli.BoolFlag{
Name: "access-token",
Usage: "Generate access token for the user",
},
&cli.StringFlag{
Name: "access-token-name",
Usage: `Name of the generated access token`,
Value: "gitea-admin",
},
&cli.StringFlag{
Name: "access-token-scopes",
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
Value: "all",
},
&cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},
&cli.StringFlag{
Name: "fullname",
Usage: `The full, human-readable name of the user`,
},
},
}
}
func runCreateUser(ctx context.Context, c *cli.Command) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings()
userTypes := map[string]user_model.UserType{
"individual": user_model.UserTypeIndividual,
"bot": user_model.UserTypeBot,
}
userType, ok := userTypes[c.String("user-type")]
if !ok {
return fmt.Errorf("invalid user type: %s", c.String("user-type"))
}
if userType != user_model.UserTypeIndividual {
// Some other commands like "change-password" also only support individual users.
// It needs to clarify the "password" behavior for bot users in the future.
// At the moment, we do not allow setting password for bot users.
if c.IsSet("password") || c.IsSet("random-password") {
return errors.New("password can only be set for individual users")
}
}
if c.IsSet("password") && c.IsSet("random-password") {
return errors.New("cannot set both -random-password and -password flags")
}
var username string
if c.IsSet("username") {
username = c.String("username")
} else {
username = c.String("name")
_, _ = fmt.Fprintf(c.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
}
if !setting.IsInTesting {
// FIXME: need to refactor the "initDB" related code later
// it doesn't make sense to call it in (almost) every command action function
if err := initDB(ctx); err != nil {
return err
}
}
var password string
if c.IsSet("password") {
password = c.String("password")
} else if c.IsSet("random-password") {
var err error
password, err = pwd.Generate(c.Int("random-password-length"))
if err != nil {
return err
}
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("generated random password is '%s'\n", password)
} else if userType == user_model.UserTypeIndividual {
return errors.New("must set either password or random-password flag")
}
isAdmin := c.Bool("admin")
mustChangePassword := true // always default to true
if c.IsSet("must-change-password") {
if userType != user_model.UserTypeIndividual {
return errors.New("must-change-password flag can only be set for individual users")
}
// if the flag is set, use the value provided by the user
mustChangePassword = c.Bool("must-change-password")
} else if userType == user_model.UserTypeIndividual {
// check whether there are users in the database
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
if err != nil {
return fmt.Errorf("IsTableNotEmpty: %w", err)
}
if !hasUserRecord {
// if this is the first one being created, don't force to change password (keep the old behavior)
mustChangePassword = false
}
}
restricted := optional.None[bool]()
if c.IsSet("restricted") {
restricted = optional.Some(c.Bool("restricted"))
}
// default user visibility in app.ini
visibility := setting.Service.DefaultUserVisibilityMode
u := &user_model.User{
Name: username,
Email: c.String("email"),
IsAdmin: isAdmin,
Type: userType,
Passwd: password,
MustChangePassword: mustChangePassword,
Visibility: visibility,
FullName: c.String("fullname"),
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: optional.Some(true),
IsRestricted: restricted,
}
var accessTokenName string
var accessTokenScope auth_model.AccessTokenScope
if c.IsSet("access-token") {
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
if accessTokenName == "" {
return errors.New("access-token-name cannot be empty")
}
var err error
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
}
// arguments should be prepared before creating the user & access token, in case there is anything wrong
// create the user
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err)
}
fmt.Printf("New user '%s' has been successfully created!\n", username)
// create the access token
if accessTokenScope != "" {
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
if err := auth_model.NewAccessToken(ctx, t); err != nil {
return err
}
fmt.Printf("Access token was successfully created... %s\n", t.Token)
}
return nil
}

84
cmd/admin_user_delete.go Normal file
View File

@ -0,0 +1,84 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3"
)
func microcmdUserDelete() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
&cli.Int64Flag{
Name: "id",
Usage: "ID of user of the user to delete",
},
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username of the user to delete",
},
&cli.StringFlag{
Name: "email",
Aliases: []string{"e"},
Usage: "Email of the user to delete",
},
&cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
},
Action: runDeleteUser,
}
}
func runDeleteUser(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return errors.New("You must provide the id, username or email of a user to delete")
}
if !setting.IsInTesting {
if err := initDB(ctx); err != nil {
return err
}
}
if err := storage.Init(); err != nil {
return err
}
var err error
var user *user_model.User
if c.IsSet("email") {
user, err = user_model.GetUserByEmail(ctx, c.String("email"))
} else if c.IsSet("username") {
user, err = user_model.GetUserByName(ctx, c.String("username"))
} else {
user, err = user_model.GetUserByID(ctx, c.Int64("id"))
}
if err != nil {
return err
}
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
return fmt.Errorf("the user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
}
if c.IsSet("id") && user.ID != c.Int64("id") {
return fmt.Errorf("the user %s does not match the provided id %d", user.Name, c.Int64("id"))
}
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
}

View File

@ -0,0 +1,97 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3"
)
func newUserGenerateAccessTokenCommand() *cli.Command {
return &cli.Command{
Name: "generate-access-token",
Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username",
},
&cli.StringFlag{
Name: "token-name",
Aliases: []string{"t"},
Usage: "Token name",
Value: "gitea-admin",
},
&cli.BoolFlag{
Name: "raw",
Usage: "Display only the token value",
},
&cli.StringFlag{
Name: "scopes",
Value: "all",
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
},
},
Action: runGenerateAccessToken,
}
}
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("you must provide a username to generate a token for")
}
if err := initDB(ctx); err != nil {
return err
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
// construct token with name and user so we can make sure it is unique
t := &auth_model.AccessToken{
Name: c.String("token-name"),
UID: user.ID,
}
exist, err := auth_model.AccessTokenByNameExists(ctx, t)
if err != nil {
return err
}
if exist {
return errors.New("access token name has been used already")
}
// make sure the scopes are valid
accessTokenScope, err := auth_model.AccessTokenScope(c.String("scopes")).Normalize()
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
t.Scope = accessTokenScope
// create the token
if err := auth_model.NewAccessToken(ctx, t); err != nil {
return err
}
if c.Bool("raw") {
fmt.Printf("%s\n", t.Token)
} else {
fmt.Printf("Access token was successfully created: %s\n", t.Token)
}
return nil
}

60
cmd/admin_user_list.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"os"
"text/tabwriter"
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3"
)
func newUserListCommand() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "admin",
Usage: "List only admin users",
},
},
}
}
func runListUsers(ctx context.Context, c *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
users, err := user_model.GetAllUsers(ctx)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
if c.IsSet("admin") {
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
for _, u := range users {
if u.IsAdmin {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
}
}
} else {
twofa := user_model.UserList(users).GetTwoFaStatus(ctx)
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
for _, u := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
}
}
w.Flush()
return nil
}

View File

@ -0,0 +1,64 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func microcmdUserMustChangePassword() *cli.Command {
return &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",
Aliases: []string{"A"},
Usage: "All users must change password, except those explicitly excluded with --exclude",
},
&cli.StringSliceFlag{
Name: "exclude",
Aliases: []string{"e"},
Usage: "Do not change the must-change-password flag for these users",
},
&cli.BoolFlag{
Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it",
},
},
}
}
func runMustChangePassword(ctx context.Context, c *cli.Command) error {
if c.NArg() == 0 && !c.IsSet("all") {
return errors.New("either usernames or --all must be provided")
}
mustChangePassword := !c.Bool("unset")
all := c.Bool("all")
exclude := c.StringSlice("exclude")
if !setting.IsInTesting {
if err := initDB(ctx); err != nil {
return err
}
}
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)
if err != nil {
return err
}
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
return nil
}

206
cmd/cert.go Normal file
View File

@ -0,0 +1,206 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"os"
"strings"
"time"
"github.com/urfave/cli/v3"
)
// cmdCert represents the available cert sub-command.
func cmdCert() *cli.Command {
return &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{
Name: "host",
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
Required: true,
},
&cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
&cli.IntFlag{
Name: "rsa-bits",
Value: 3072,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},
&cli.StringFlag{
Name: "out",
Value: "cert.pem",
Usage: "Path to the file where there certificate will be saved",
},
&cli.StringFlag{
Name: "keyout",
Value: "key.pem",
Usage: "Path to the file where there certificate key will be saved",
},
},
}
}
func publicKey(priv any) any {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
func pemBlockForKey(priv any) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
log.Fatalf("Unable to marshal ECDSA private key: %v", err)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
func runCert(_ context.Context, c *cli.Command) error {
var priv any
var err error
switch c.String("ecdsa-curve") {
case "":
priv, err = rsa.GenerateKey(rand.Reader, c.Int("rsa-bits"))
case "P224":
priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case "P256":
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case "P384":
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case "P521":
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
err = fmt.Errorf("unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
}
if err != nil {
return fmt.Errorf("failed to generate private key: %w", err)
}
var notBefore time.Time
if startDate := c.String("start-date"); startDate != "" {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
if err != nil {
return fmt.Errorf("failed to parse creation date %w", err)
}
} else {
notBefore = time.Now()
}
notAfter := notBefore.Add(c.Duration("duration"))
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return fmt.Errorf("failed to generate serial number: %w", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
CommonName: "Gitea",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
hosts := strings.SplitSeq(c.String("host"), ",")
for h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
if c.Bool("ca") {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}
certOut, err := os.Create(c.String("out"))
if err != nil {
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err)
}
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
return fmt.Errorf("failed to encode certificate: %w", err)
}
err = certOut.Close()
if err != nil {
return fmt.Errorf("failed to write cert: %w", err)
}
fmt.Fprintf(c.Writer, "Written cert to %s\n", c.String("out"))
keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err)
}
err = pem.Encode(keyOut, pemBlockForKey(priv))
if err != nil {
return fmt.Errorf("failed to encode key: %w", err)
}
err = keyOut.Close()
if err != nil {
return fmt.Errorf("failed to write key: %w", err)
}
fmt.Fprintf(c.Writer, "Written key to %s\n", c.String("keyout"))
return nil
}

150
cmd/cmd.go Normal file
View File

@ -0,0 +1,150 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Package cmd provides subcommands to the gitea binary - such as "web" or
// "admin".
package cmd
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/signal"
"strings"
"syscall"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
// argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context.
func argsSet(c *cli.Command, args ...string) error {
for _, a := range args {
if !c.IsSet(a) {
return errors.New(a + " is not set")
}
if c.Value(a) == nil {
return errors.New(a + " is required")
}
}
return nil
}
// confirm waits for user input which confirms an action
func confirm() (bool, error) {
var response string
_, err := fmt.Scanln(&response)
if err != nil {
return false, err
}
switch strings.ToLower(response) {
case "y", "yes":
return true, nil
case "n", "no":
return false, nil
default:
return false, errors.New(response + " isn't a correct confirmation string")
}
}
func initDB(ctx context.Context) error {
setting.MustInstalled()
setting.LoadDBSetting()
setting.InitSQLLoggersForCli(log.INFO)
if setting.Database.Type == "" {
log.Fatal(`Database settings are missing from the configuration file: %q.
Ensure you are running in the correct environment or set the correct configuration file with -c.
If this is the intended configuration file complete the [database] section.`, setting.CustomConf)
}
if err := db.InitEngine(ctx); err != nil {
return fmt.Errorf("unable to initialize the database using the configuration in %q. Error: %w", setting.CustomConf, err)
}
return nil
}
func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// install notify
signalChannel := make(chan os.Signal, 1)
signal.Notify(
signalChannel,
syscall.SIGINT,
syscall.SIGTERM,
)
select {
case <-signalChannel:
case <-ctx.Done():
}
cancel()
signal.Reset()
}()
return ctx, cancel
}
func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
if out != os.Stdout && out != os.Stderr {
panic("setupConsoleLogger can only be used with os.Stdout or os.Stderr")
}
writeMode := log.WriterMode{
Level: level,
Colorize: colorize,
WriterOption: log.WriterConsoleOption{Stderr: out == os.Stderr},
}
writer := log.NewEventWriterConsole("console-default", writeMode)
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
func globalBool(c *cli.Command, 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(context.Context, *cli.Command) (context.Context, error) {
return func(ctx context.Context, c *cli.Command) (context.Context, error) {
if setting.InstallLock {
// During config loading, there might also be logs (for example: deprecation warnings).
// It must make sure that console logger is set up before config is loaded.
log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it.")
return nil, errors.New("console logger must be setup before config is loaded")
}
level := defaultLevel
if globalBool(c, "quiet") {
level = log.FATAL
}
if globalBool(c, "debug") || globalBool(c, "verbose") {
level = log.TRACE
}
log.SetConsoleLogger(log.DEFAULT, "console-default", level)
return ctx, nil
}
}
func isValidDefaultSubCommand(cmd *cli.Command) (string, bool) {
// Dirty patch for urfave/cli's strange design.
// "./gitea bad-cmd" should not start the web server.
rootArgs := cmd.Root().Args().Slice()
if len(rootArgs) != 0 && rootArgs[0] != cmd.Name {
return rootArgs[0], false
}
return "", true
}

156
cmd/config.go Normal file
View File

@ -0,0 +1,156 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func cmdConfig() *cli.Command {
subcmdConfigEditIni := &cli.Command{
Name: "edit-ini",
Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.",
Description: `
Help users to edit the Gitea configuration INI file.
# Keep Specified Keys
If you need to re-create the configuration file with only a subset of keys,
you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag.
For example, if a helm chart needs to reset the settings and only keep SECRET_KEY,
it can use a template file (only keys take effect, values are ignored):
[security]
SECRET_KEY=
$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini
# Map Environment Variables to INI Configuration
Environment variables of the form "GITEA__section_name__KEY_NAME"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value as provided.
Environment variables of the form "GITEA__section_name__KEY_NAME__FILE"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value loaded from the specified file.
Environment variable keys can only contain characters "0-9A-Z_",
if a section or key name contains dot ".", it needs to be escaped as _0x2E_.
For example, to apply this config:
[git.config]
foo.bar=val
$ export GITEA__git_0x2E_config__foo_0x2E_bar=val
# Put All Together
$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini}
`,
Flags: []cli.Flag{
// "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper
// "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file
&cli.BoolFlag{
Name: "in-place",
Usage: "Output to the same config file as input. This flag will be ignored if --out is set.",
},
&cli.StringFlag{
Name: "config-keep-keys",
Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.",
},
&cli.BoolFlag{
Name: "apply-env",
Usage: "Apply all GITEA__* variables from the environment to the config.",
},
&cli.StringFlag{
Name: "out",
Usage: "Destination config file to write to.",
},
},
Action: runConfigEditIni,
}
return &cli.Command{
Name: "config",
Usage: "Manage Gitea configuration",
Commands: []*cli.Command{
subcmdConfigEditIni,
},
}
}
func runConfigEditIni(_ context.Context, c *cli.Command) error {
// the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...)
// don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly
if !c.IsSet("config") {
return errors.New("flag is required but not set: --config")
}
configFileIn := c.String("config")
cfgIn, err := setting.NewConfigProviderFromFile(configFileIn)
if err != nil {
return fmt.Errorf("failed to load config file %q: %v", configFileIn, err)
}
// determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file
inPlace := c.Bool("in-place")
configFileOut := c.String("out")
if configFileOut == "" {
if !inPlace {
return errors.New("either --in-place or --out must be specified")
}
configFileOut = configFileIn // in-place edit
}
needWriteOut := configFileOut != configFileIn
cfgOut := cfgIn
configKeepKeys := c.String("config-keep-keys")
if configKeepKeys != "" {
needWriteOut = true
cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys)
if err != nil {
return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err)
}
for _, secOut := range cfgOut.Sections() {
for _, keyOut := range secOut.Keys() {
secIn := cfgIn.Section(secOut.Name())
keyIn := setting.ConfigSectionKey(secIn, keyOut.Name())
if keyIn != nil {
keyOut.SetValue(keyIn.String())
} else {
secOut.DeleteKey(keyOut.Name())
}
}
if len(secOut.Keys()) == 0 {
cfgOut.DeleteSection(secOut.Name())
}
}
}
if c.Bool("apply-env") {
if setting.EnvironmentToConfig(cfgOut, env) {
needWriteOut = true
}
}
if needWriteOut {
err = cfgOut.SaveTo(configFileOut)
if err != nil {
return err
}
}
return nil
}

68
cmd/docs.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"os"
"strings"
cli_docs "github.com/urfave/cli-docs/v3"
"github.com/urfave/cli/v3"
)
func newDocsCommand() *cli.Command {
return &cli.Command{
Name: "docs",
Usage: "Output CLI documentation",
Description: "A command to output Gitea's CLI documentation, optionally to a file.",
Action: runDocs,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "man",
Usage: "Output man pages instead",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Path to output to instead of stdout (will overwrite if exists)",
},
},
}
}
func runDocs(_ context.Context, cmd *cli.Command) error {
docs, err := cli_docs.ToMarkdown(cmd.Root())
if cmd.Bool("man") {
docs, err = cli_docs.ToMan(cmd.Root())
}
if err != nil {
return err
}
if !cmd.Bool("man") {
// Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040
firstHashtagIndex := strings.Index(docs, "#")
if firstHashtagIndex > 0 {
docs = docs[firstHashtagIndex:]
}
}
out := os.Stdout
if cmd.String("output") != "" {
fi, err := os.Create(cmd.String("output"))
if err != nil {
return err
}
defer fi.Close()
out = fi
}
_, err = fmt.Fprintln(out, docs)
return err
}

219
cmd/doctor.go Normal file
View File

@ -0,0 +1,219 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
golog "log"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/migrations"
migrate_base "code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/doctor"
"github.com/urfave/cli/v3"
"xorm.io/xorm"
)
func newDoctorCommand() *cli.Command {
return &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
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.",
Commands: []*cli.Command{
newDoctorCheckCommand(),
newRecreateTableCommand(),
newDoctorConvertCommand(),
},
}
}
func newDoctorCheckCommand() *cli.Command {
return &cli.Command{
Name: "check",
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: runDoctorCheck,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "list",
Usage: "List the available checks",
},
&cli.BoolFlag{
Name: "default",
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
},
&cli.StringSliceFlag{
Name: "run",
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
},
&cli.BoolFlag{
Name: "all",
Usage: "Run all the available checks",
},
&cli.BoolFlag{
Name: "fix",
Usage: "Automatically fix what we can",
},
&cli.StringFlag{
Name: "log-file",
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
},
&cli.BoolFlag{
Name: "color",
Aliases: []string{"H"},
Usage: "Use color for outputted information",
},
},
}
}
func newRecreateTableCommand() *cli.Command {
return &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{
Name: "debug",
Usage: "Print SQL commands sent",
},
},
Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns.
This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
Action: runRecreateTable,
}
}
func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
// Redirect the default golog to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
debug := cmd.Bool("debug")
setting.MustInstalled()
setting.LoadDBSetting()
if debug {
setting.InitSQLLoggersForCli(log.DEBUG)
} else {
setting.InitSQLLoggersForCli(log.INFO)
}
setting.Database.LogSQL = debug
if err := db.InitEngine(ctx); err != nil {
fmt.Println(err)
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil
}
args := cmd.Args()
names := make([]string, 0, cmd.NArg())
for i := 0; i < cmd.NArg(); i++ {
names = append(names, args.Get(i))
}
beans, err := db.NamesToBean(names...)
if err != nil {
return err
}
recreateTables := migrate_base.RecreateTables(beans...)
return db.InitEngineWithMigration(context.Background(), func(ctx context.Context, x *xorm.Engine) error {
if err := migrations.EnsureUpToDate(ctx, x); err != nil {
return err
}
return recreateTables(x)
})
}
func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) {
// Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
logFile := cmd.String("log-file")
switch logFile {
case "":
return // if no doctor log-file is set, do not show any log from default logger
case "-":
setupConsoleLogger(log.TRACE, colorize, os.Stdout)
default:
logFile, _ = filepath.Abs(logFile)
writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}}
writer, err := log.NewEventWriter("console-to-file", "file", writeMode)
if err != nil {
log.FallbackErrorf("unable to create file log writer: %v", err)
return
}
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
}
func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
colorize := log.CanColorStdout
if cmd.IsSet("color") {
colorize = cmd.Bool("color")
}
setupDoctorDefaultLogger(cmd, colorize)
// Finally redirect the default golang's log to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
if cmd.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
doctor.SortChecks(doctor.Checks)
for _, check := range doctor.Checks {
if check.IsDefault {
_, _ = w.Write([]byte{'*'})
}
_, _ = w.Write([]byte{'\t'})
_, _ = w.Write([]byte(check.Name))
_, _ = w.Write([]byte{'\t'})
_, _ = w.Write([]byte(check.Title))
_, _ = w.Write([]byte{'\n'})
}
return w.Flush()
}
var checks []*doctor.Check
if cmd.Bool("all") {
checks = make([]*doctor.Check, len(doctor.Checks))
copy(checks, doctor.Checks)
} else if cmd.IsSet("run") {
addDefault := cmd.Bool("default")
runNamesSet := container.SetOf(cmd.StringSlice("run")...)
for _, check := range doctor.Checks {
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check)
runNamesSet.Remove(check.Name)
}
}
if len(runNamesSet) > 0 {
return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
}
} else {
for _, check := range doctor.Checks {
if check.IsDefault {
checks = append(checks, check)
}
}
}
return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks)
}

View File

@ -4,28 +4,27 @@
package cmd
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// cmdDoctorConvert represents the available convert sub-command.
var cmdDoctorConvert = &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",
Action: runDoctorConvert,
func newDoctorConvertCommand() *cli.Command {
return &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",
Action: runDoctorConvert,
}
}
func runDoctorConvert(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
func runDoctorConvert(ctx context.Context, cmd *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
@ -37,8 +36,8 @@ func runDoctorConvert(ctx *cli.Context) error {
switch {
case setting.Database.Type.IsMySQL():
if err := db.ConvertUtf8ToUtf8mb4(); err != nil {
log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err)
if err := db.ConvertDatabaseTable(); err != nil {
log.Fatal("Failed to convert database & table: %v", err)
return err
}
fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4")

328
cmd/dump.go Normal file
View File

@ -0,0 +1,328 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"os"
"path"
"path/filepath"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/dump"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"gitea.com/go-chi/session"
"github.com/urfave/cli/v3"
)
func newDumpCommand() *cli.Command {
return &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",
Aliases: []string{"f"},
Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`,
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"V"},
Usage: "Show process details",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display warnings and errors",
},
&cli.StringFlag{
Name: "tempdir",
Aliases: []string{"t"},
Value: os.TempDir(),
Usage: "Temporary dir path",
},
&cli.StringFlag{
Name: "database",
Aliases: []string{"d"},
Usage: "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres",
},
&cli.BoolFlag{
Name: "skip-repository",
Aliases: []string{"R"},
Usage: "Skip the repository dumping",
},
&cli.BoolFlag{
Name: "skip-log",
Aliases: []string{"L"},
Usage: "Skip the log dumping",
},
&cli.BoolFlag{
Name: "skip-custom-dir",
Usage: "Skip custom directory",
},
&cli.BoolFlag{
Name: "skip-lfs-data",
Usage: "Skip LFS data",
},
&cli.BoolFlag{
Name: "skip-attachment-data",
Usage: "Skip attachment data",
},
&cli.BoolFlag{
Name: "skip-package-data",
Usage: "Skip package data",
},
&cli.BoolFlag{
Name: "skip-index",
Usage: "Skip bleve index data",
},
&cli.BoolFlag{
Name: "skip-db",
Usage: "Skip database",
},
&cli.StringFlag{
Name: "type",
Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "),
},
},
}
}
func fatal(format string, args ...any) {
log.Fatal(format, args...)
}
func runDump(ctx context.Context, cmd *cli.Command) error {
setting.MustInstalled()
quite := cmd.Bool("quiet")
verbose := cmd.Bool("verbose")
if verbose && quite {
fatal("Option --quiet and --verbose cannot both be set")
}
// outFileName is either "-" or a file name (will be made absolute)
outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type"))
if outType == "" {
fatal("Invalid output type")
}
outFile := os.Stdout
if outFileName != "-" {
var err error
if outFileName, err = filepath.Abs(outFileName); err != nil {
fatal("Unable to get absolute path of dump file: %v", err)
}
if exist, _ := util.IsExist(outFileName); exist {
fatal("Dump file %q exists", outFileName)
}
if outFile, err = os.Create(outFileName); err != nil {
fatal("Unable to create dump file %q: %v", outFileName, err)
}
defer outFile.Close()
}
setupConsoleLogger(util.Iif(quite, log.WARN, log.INFO), log.CanColorStderr, os.Stderr)
setting.DisableLoggerInit()
setting.LoadSettings() // cannot access session settings otherwise
err := db.InitEngine(ctx)
if err != nil {
return err
}
if err = storage.Init(); err != nil {
return err
}
dumper, err := dump.NewDumper(ctx, outType, outFile)
if err != nil {
fatal("Failed to create archive %q: %v", outFile, err)
return err
}
dumper.Verbose = verbose
dumper.GlobalExcludeAbsPath(outFileName)
defer func() {
if err := dumper.Close(); err != nil {
fatal("Failed to save archive %q: %v", outFileName, err)
}
}()
if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") {
log.Info("Skip dumping local repositories")
} else {
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
if err := dumper.AddRecursiveExclude("repos", setting.RepoRootPath, nil); err != nil {
fatal("Failed to include repositories: %v", err)
}
if cmd.IsSet("skip-lfs-data") && cmd.Bool("skip-lfs-data") {
log.Info("Skip dumping LFS data")
} else if !setting.LFS.StartServer {
log.Info("LFS isn't enabled. Skip dumping LFS data")
} else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat()
if err != nil {
return err
}
return dumper.AddFileByReader(object, info, path.Join("data", "lfs", objPath))
}); err != nil {
fatal("Failed to dump LFS objects: %v", err)
}
}
if cmd.Bool("skip-db") {
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
dumper.GlobalExcludeAbsPath(setting.Database.Path)
log.Info("Skipping database")
} else {
tmpDir := cmd.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
fatal("Path does not exist: %s", tmpDir)
}
dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql")
if err != nil {
fatal("Failed to create tmp file: %v", err)
}
defer func() {
_ = dbDump.Close()
if err := util.Remove(dbDump.Name()); err != nil {
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
}
}()
targetDBType := cmd.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else {
log.Info("Dumping database...")
}
if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
fatal("Failed to dump database: %v", err)
}
if err = dumper.AddFileByPath("gitea-db.sql", dbDump.Name()); err != nil {
fatal("Failed to include gitea-db.sql: %v", err)
}
}
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err = dumper.AddFileByPath("app.ini", setting.CustomConf); err != nil {
fatal("Failed to include specified app.ini: %v", err)
}
if cmd.IsSet("skip-custom-dir") && cmd.Bool("skip-custom-dir") {
log.Info("Skipping custom directory")
} else {
customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() {
if is, _ := dump.IsSubdir(setting.AppDataPath, setting.CustomPath); !is {
if err := dumper.AddRecursiveExclude("custom", setting.CustomPath, nil); err != nil {
fatal("Failed to include custom: %v", err)
}
} else {
log.Info("Custom dir %s is inside data dir %s, skipped", setting.CustomPath, setting.AppDataPath)
}
} else {
log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
}
}
isExist, err := util.IsExist(setting.AppDataPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", setting.AppDataPath, err)
}
if isExist {
log.Info("Packing data directory...%s", setting.AppDataPath)
var excludes []string
if setting.SessionConfig.OriginalProvider == "file" {
var opts session.Options
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
return err
}
excludes = append(excludes, opts.ProviderConfig)
}
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
excludes = append(excludes, setting.Indexer.RepoPath)
excludes = append(excludes, setting.Indexer.IssuePath)
}
excludes = append(excludes, setting.RepoRootPath)
excludes = append(excludes, setting.LFS.Storage.Path)
excludes = append(excludes, setting.Attachment.Storage.Path)
excludes = append(excludes, setting.Packages.Storage.Path)
excludes = append(excludes, setting.RepoArchive.Storage.Path)
excludes = append(excludes, setting.Log.RootPath)
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
fatal("Failed to include data directory: %v", err)
}
}
if cmd.IsSet("skip-attachment-data") && cmd.Bool("skip-attachment-data") {
log.Info("Skip dumping attachment data")
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat()
if err != nil {
return err
}
return dumper.AddFileByReader(object, info, path.Join("data", "attachments", objPath))
}); err != nil {
fatal("Failed to dump attachments: %v", err)
}
if cmd.IsSet("skip-package-data") && cmd.Bool("skip-package-data") {
log.Info("Skip dumping package data")
} else if !setting.Packages.Enabled {
log.Info("Packages isn't enabled. Skip dumping package data")
} else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat()
if err != nil {
return err
}
return dumper.AddFileByReader(object, info, path.Join("data", "packages", objPath))
}); err != nil {
fatal("Failed to dump packages: %v", err)
}
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
// ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not.
if cmd.IsSet("skip-log") && cmd.Bool("skip-log") {
log.Info("Skip dumping log files")
} else {
isExist, err := util.IsExist(setting.Log.RootPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err)
}
if isExist {
if err := dumper.AddRecursiveExclude("log", setting.Log.RootPath, nil); err != nil {
fatal("Failed to include log: %v", err)
}
}
}
if outFileName == "-" {
log.Info("Finish dumping to stdout")
} else {
if err = os.Chmod(outFileName, 0o600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err)
}
log.Info("Finish dumping in file %s", outFileName)
}
return nil
}

195
cmd/dump_repo.go Normal file
View File

@ -0,0 +1,195 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/migrations"
"github.com/urfave/cli/v3"
)
func newDumpRepositoryCommand() *cli.Command {
return &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{
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",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to store the data",
},
&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{
Name: "auth_username",
Value: "",
Usage: "The username to visit the clone_addr",
},
&cli.StringFlag{
Name: "auth_password",
Value: "",
Usage: "The password to visit the clone_addr",
},
&cli.StringFlag{
Name: "auth_token",
Value: "",
Usage: "The personal token to visit the clone_addr",
},
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "The data will be stored on a directory with owner name if not empty",
},
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "The data will be stored on a directory with repository name if not empty",
},
&cli.StringFlag{
Name: "units",
Value: "",
Usage: `Which items will be migrated, 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.`,
},
},
}
}
func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
setting.DisableLoggerInit()
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
if err := initDB(ctx); err != nil {
return err
}
// migrations.GiteaLocalUploader depends on git module
if err := git.InitSimple(); err != nil {
return err
}
log.Info("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf)
var (
serviceType structs.GitServiceType
cloneAddr = cmd.String("clone_addr")
serviceStr = cmd.String("git_service")
)
if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
serviceStr = "github"
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitlab.com/") {
serviceStr = "gitlab"
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitea.com/") {
serviceStr = "gitea"
}
if serviceStr == "" {
return errors.New("git_service missed or clone_addr cannot be recognized")
}
serviceType = convert.ToGitServiceType(serviceStr)
opts := base.MigrateOptions{
GitServiceType: serviceType,
CloneAddr: cloneAddr,
AuthUsername: cmd.String("auth_username"),
AuthPassword: cmd.String("auth_password"),
AuthToken: cmd.String("auth_token"),
RepoName: cmd.String("repo_name"),
}
if len(cmd.String("units")) == 0 {
opts.Wiki = true
opts.Issues = true
opts.Milestones = true
opts.Labels = true
opts.Releases = true
opts.Comments = true
opts.PullRequests = true
opts.ReleaseAssets = true
} else {
units := strings.SplitSeq(cmd.String("units"), ",")
for unit := range units {
switch strings.ToLower(strings.TrimSpace(unit)) {
case "":
continue
case "wiki":
opts.Wiki = true
case "issues":
opts.Issues = true
case "milestones":
opts.Milestones = true
case "labels":
opts.Labels = true
case "releases":
opts.Releases = true
case "release_assets":
opts.ReleaseAssets = true
case "comments":
opts.Comments = true
case "pull_requests":
opts.PullRequests = true
default:
return errors.New("invalid unit: " + unit)
}
}
}
// the repo_dir will be removed if error occurs in DumpRepository
// make sure the directory doesn't exist or is empty, prevent from deleting user files
repoDir := cmd.String("repo_dir")
if exists, err := util.IsExist(repoDir); err != nil {
return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
} else if exists {
if isDir, _ := util.IsDir(repoDir); !isDir {
return fmt.Errorf("repo_dir %q already exists but it's not a directory", repoDir)
}
if dir, _ := os.ReadDir(repoDir); len(dir) > 0 {
return fmt.Errorf("repo_dir %q is not empty", repoDir)
}
}
if err := migrations.DumpRepository(
ctx,
repoDir,
cmd.String("owner_name"),
opts,
); err != nil {
log.Fatal("Failed to dump repository: %v", err)
return err
}
log.Trace("Dump finished!!!")
return nil
}

314
cmd/embedded.go Normal file
View File

@ -0,0 +1,314 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/assetfs"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v3"
)
var matchedAssetFiles []assetFile
func newEmbeddedCommand() *cli.Command {
return &cli.Command{
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",
Commands: []*cli.Command{
newEmbeddedListCommand(),
newEmbeddedViewCommand(),
newEmbeddedExtractCommand(),
},
}
}
func newEmbeddedListCommand() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List files matching the given pattern",
Action: runList,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "include-vendored",
Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
},
},
}
}
func newEmbeddedViewCommand() *cli.Command {
return &cli.Command{
Name: "view",
Usage: "View a file matching the given pattern",
Action: runView,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "include-vendored",
Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
},
},
}
}
func newEmbeddedExtractCommand() *cli.Command {
return &cli.Command{
Name: "extract",
Usage: "Extract resources",
Action: runExtract,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "include-vendored",
Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
},
&cli.BoolFlag{
Name: "overwrite",
Usage: "Overwrite files if they already exist",
},
&cli.BoolFlag{
Name: "rename",
Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)",
},
&cli.BoolFlag{
Name: "custom",
Usage: "Extract to the 'custom' directory as per app.ini",
},
&cli.StringFlag{
Name: "destination",
Aliases: []string{"dest-dir"},
Usage: "Extract to the specified directory",
},
},
}
}
type assetFile struct {
fs *assetfs.LayeredFS
name string
path string
}
func initEmbeddedExtractor(c *cli.Command) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args().Slice())
if err != nil {
return err
}
collectAssetFilesByPattern(c, patterns, "options", options.BuiltinAssets())
collectAssetFilesByPattern(c, patterns, "public", public.BuiltinAssets())
collectAssetFilesByPattern(c, patterns, "templates", templates.BuiltinAssets())
return nil
}
func runList(_ context.Context, c *cli.Command) error {
if err := runListDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runView(_ context.Context, c *cli.Command) error {
if err := runViewDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runExtract(_ context.Context, c *cli.Command) error {
if err := runExtractDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runListDo(c *cli.Command) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
for _, a := range matchedAssetFiles {
fmt.Println(a.path)
}
return nil
}
func runViewDo(c *cli.Command) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
if len(matchedAssetFiles) == 0 {
return errors.New("no files matched the given pattern")
} else if len(matchedAssetFiles) > 1 {
return errors.New("too many files matched the given pattern, try to be more specific")
}
data, err := matchedAssetFiles[0].fs.ReadFile(matchedAssetFiles[0].name)
if err != nil {
return fmt.Errorf("%s: %w", matchedAssetFiles[0].path, err)
}
if _, err = os.Stdout.Write(data); err != nil {
return fmt.Errorf("%s: %w", matchedAssetFiles[0].path, err)
}
return nil
}
func runExtractDo(c *cli.Command) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
if c.NArg() == 0 {
return errors.New("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
}
destdir := "."
if c.IsSet("destination") {
destdir = c.String("destination")
} else if c.Bool("custom") {
destdir = setting.CustomPath
fmt.Println("Using app.ini at", setting.CustomConf)
}
fi, err := os.Stat(destdir)
if errors.Is(err, os.ErrNotExist) {
// In case Windows users attempt to provide a forward-slash path
wdestdir := filepath.FromSlash(destdir)
if wfi, werr := os.Stat(wdestdir); werr == nil {
destdir = wdestdir
fi = wfi
err = nil
}
}
if err != nil {
return fmt.Errorf("%s: %s", destdir, err)
} else if !fi.IsDir() {
return fmt.Errorf("destination %q is not a directory", destdir)
}
fmt.Printf("Extracting to %s:\n", destdir)
overwrite := c.Bool("overwrite")
rename := c.Bool("rename")
for _, a := range matchedAssetFiles {
if err := extractAsset(destdir, a, overwrite, rename); err != nil {
// Non-fatal error
_, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", a.path, err)
}
}
return nil
}
func extractAsset(d string, a assetFile, overwrite, rename bool) error {
dest := filepath.Join(d, filepath.FromSlash(a.path))
dir := filepath.Dir(dest)
data, err := a.fs.ReadFile(a.name)
if err != nil {
return fmt.Errorf("%s: %w", a.path, err)
}
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return fmt.Errorf("%s: %w", dir, err)
}
perms := os.ModePerm & 0o666
fi, err := os.Lstat(dest)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%s: %w", dest, err)
}
} else if !overwrite && !rename {
fmt.Printf("%s already exists; skipped.\n", dest)
return nil
} else if !fi.Mode().IsRegular() {
return fmt.Errorf("%s already exists, but it's not a regular file", dest)
} else if rename {
if err := util.Rename(dest, dest+".bak"); err != nil {
return fmt.Errorf("error creating backup for %s: %w", dest, err)
}
// Attempt to respect file permissions mask (even if user:group will be set anew)
perms = fi.Mode()
}
file, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms)
if err != nil {
return fmt.Errorf("%s: %w", dest, err)
}
defer file.Close()
if _, err = file.Write(data); err != nil {
return fmt.Errorf("%s: %w", dest, err)
}
fmt.Println(dest)
return nil
}
func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) {
fs := assetfs.Layered(layer)
files, err := fs.ListAllFiles(".", true)
if err != nil {
log.Error("Error listing files in %q: %v", path, err)
return
}
for _, name := range files {
if path == "public" &&
strings.HasPrefix(name, "vendor/") &&
!c.Bool("include-vendored") {
continue
}
matchName := path + "/" + name
for _, g := range globs {
if g.Match(matchName) {
matchedAssetFiles = append(matchedAssetFiles, assetFile{fs: fs, name: name, path: path + "/" + name})
break
}
}
}
}
func compileCollectPatterns(args []string) (_ []glob.Glob, err error) {
if len(args) == 0 {
args = []string{"**"}
}
pat := make([]glob.Glob, len(args))
for i := range args {
if pat[i], err = glob.Compile(args[i], '/'); err != nil {
return nil, fmt.Errorf("invalid glob patterh %q: %w", args[i], err)
}
}
return pat, nil
}

105
cmd/generate.go Normal file
View File

@ -0,0 +1,105 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"os"
"code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v3"
)
func newGenerateCommand() *cli.Command {
return &cli.Command{
Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens",
Commands: []*cli.Command{
newGenerateSecretCommand(),
},
}
}
func newGenerateSecretCommand() *cli.Command {
return &cli.Command{
Name: "secret",
Usage: "Generate a secret token",
Commands: []*cli.Command{
newGenerateInternalTokenCommand(),
newGenerateLfsJWTSecretCommand(),
newGenerateSecretKeyCommand(),
},
}
}
func newGenerateInternalTokenCommand() *cli.Command {
return &cli.Command{
Name: "INTERNAL_TOKEN",
Usage: "Generate a new INTERNAL_TOKEN",
Action: runGenerateInternalToken,
}
}
func newGenerateLfsJWTSecretCommand() *cli.Command {
return &cli.Command{
Name: "JWT_SECRET",
Aliases: []string{"LFS_JWT_SECRET"},
Usage: "Generate a new JWT_SECRET",
Action: runGenerateLfsJwtSecret,
}
}
func newGenerateSecretKeyCommand() *cli.Command {
return &cli.Command{
Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey,
}
}
func runGenerateInternalToken(_ context.Context, c *cli.Command) error {
internalToken, err := generate.NewInternalToken()
if err != nil {
return err
}
fmt.Printf("%s", internalToken)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
}
return nil
}
func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error {
_, jwtSecretBase64 := generate.NewJwtSecretWithBase64()
fmt.Printf("%s", jwtSecretBase64)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
}
return nil
}
func runGenerateSecretKey(_ context.Context, c *cli.Command) error {
secretKey, err := generate.NewSecretKey()
if err != nil {
return err
}
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("%s", secretKey)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
}
return nil
}

831
cmd/hook.go Normal file
View File

@ -0,0 +1,831 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
const (
hookBatchSize = 500
)
func newHookCommand() *cli.Command {
return &cli.Command{
Name: "hook",
Usage: "(internal) Should only be called by Git",
Hidden: true, // internal commands shouldn't be visible
Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Commands: []*cli.Command{
newHookPreReceiveCommand(),
newHookUpdateCommand(),
newHookPostReceiveCommand(),
newHookProcReceiveCommand(),
},
}
}
func newHookPreReceiveCommand() *cli.Command {
return &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{
Name: "debug",
},
},
}
}
func newHookUpdateCommand() *cli.Command {
return &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{
Name: "debug",
},
},
}
}
func newHookPostReceiveCommand() *cli.Command {
return &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{
Name: "debug",
},
},
}
}
// Note: new hook since git 2.29
func newHookProcReceiveCommand() *cli.Command {
return &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{
Name: "debug",
},
},
}
}
type delayWriter struct {
internal io.Writer
buf *bytes.Buffer
timer *time.Timer
}
func newDelayWriter(internal io.Writer, delay time.Duration) *delayWriter {
timer := time.NewTimer(delay)
return &delayWriter{
internal: internal,
buf: &bytes.Buffer{},
timer: timer,
}
}
func (d *delayWriter) Write(p []byte) (n int, err error) {
if d.buf != nil {
select {
case <-d.timer.C:
_, err := d.internal.Write(d.buf.Bytes())
if err != nil {
return 0, err
}
d.buf = nil
return d.internal.Write(p)
default:
return d.buf.Write(p)
}
}
return d.internal.Write(p)
}
func (d *delayWriter) WriteString(s string) (n int, err error) {
if d.buf != nil {
select {
case <-d.timer.C:
_, err := d.internal.Write(d.buf.Bytes())
if err != nil {
return 0, err
}
d.buf = nil
return d.internal.Write([]byte(s))
default:
return d.buf.WriteString(s)
}
}
return d.internal.Write([]byte(s))
}
func (d *delayWriter) Close() error {
if d == nil {
return nil
}
stopped := d.timer.Stop()
if stopped || d.buf == nil {
return nil
}
_, err := d.internal.Write(d.buf.Bytes())
d.buf = nil
return err
}
type nilWriter struct{}
func (n *nilWriter) Write(p []byte) (int, error) {
return len(p), nil
}
func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil
}
func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, refFullName git.RefName, ok bool) {
fields := strings.Split(line, " ")
if len(fields) != 3 {
return "", "", "", false
}
return fields[0], fields[1], git.RefName(fields[2]), true
}
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(ctx, `Rejecting changes as Gitea environment not set.
If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "")
}
return nil
}
// the environment is set by serv command
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
username := os.Getenv(repo_module.EnvRepoUsername)
reponame := os.Getenv(repo_module.EnvRepoName)
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
actionsTaskID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionsTaskID), 10, 64)
hookOptions := private.HookOptions{
UserID: userID,
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
GitPushOptions: pushOptions(),
PullRequestID: prID,
DeployKeyID: deployKeyID,
ActionsTaskID: actionsTaskID,
IsWiki: isWiki,
}
scanner := bufio.NewScanner(os.Stdin)
oldCommitIDs := make([]string, hookBatchSize)
newCommitIDs := make([]string, hookBatchSize)
refFullNames := make([]git.RefName, hookBatchSize)
count := 0
total := 0
lastline := 0
var out io.Writer
out = &nilWriter{}
if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 {
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close()
out = dWriter
} else {
out = os.Stdout
}
}
supportProcReceive := git.DefaultFeatures().SupportProcReceive
for scanner.Scan() {
// TODO: support news feeds for wiki
if isWiki {
continue
}
oldCommitID, newCommitID, refFullName, ok := parseGitHookCommitRefLine(scanner.Text())
if !ok {
continue
}
total++
lastline++
// If the ref is a branch or tag, check if it's protected
// if supportProcReceive all ref should be checked because
// permission check was delayed
if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() {
oldCommitIDs[count] = oldCommitID
newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName
count++
fmt.Fprintf(out, "*")
if count >= hookBatchSize {
fmt.Fprintf(out, " Checking %d references\n", count)
hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
if extra.HasError() {
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
}
count = 0
lastline = 0
}
} else {
fmt.Fprintf(out, ".")
}
if lastline >= hookBatchSize {
fmt.Fprintf(out, "\n")
lastline = 0
}
}
if err := scanner.Err(); err != nil {
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
}
if count > 0 {
hookOptions.OldCommitIDs = oldCommitIDs[:count]
hookOptions.NewCommitIDs = newCommitIDs[:count]
hookOptions.RefFullNames = refFullNames[:count]
fmt.Fprintf(out, " Checking %d references\n", count)
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
if extra.HasError() {
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
}
} else if lastline > 0 {
fmt.Fprintf(out, "\n")
}
fmt.Fprintf(out, "Checked %d references in total\n", total)
return nil
}
// runHookUpdate avoid to do heavy operations on update hook because it will be
// invoked for every ref update which does not like pre-receive and post-receive
func runHookUpdate(_ context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
// Update is empty and is kept only for backwards compatibility
if len(os.Args) < 3 {
return nil
}
refName := git.RefName(os.Args[len(os.Args)-3])
if refName.IsPull() {
// ignore update to refs/pull/xxx/head, so we don't need to output any information
os.Exit(1)
}
return nil
}
func runHookPostReceive(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what
if err := gitcmd.NewCommand("update-server-info").RunWithStderr(ctx); err != nil {
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
}
// Now if we're an internal don't do anything else
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(ctx, `Rejecting changes as Gitea environment not set.
If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "")
}
return nil
}
var out io.Writer
var dWriter *delayWriter
out = &nilWriter{}
if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 {
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close()
out = dWriter
} else {
out = os.Stdout
}
}
// the environment is set by serv command
repoUser := os.Getenv(repo_module.EnvRepoUsername)
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
repoName := os.Getenv(repo_module.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
pusherName := os.Getenv(repo_module.EnvPusherName)
hookOptions := private.HookOptions{
UserName: pusherName,
UserID: pusherID,
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
GitPushOptions: pushOptions(),
PullRequestID: prID,
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
IsWiki: isWiki,
}
oldCommitIDs := make([]string, hookBatchSize)
newCommitIDs := make([]string, hookBatchSize)
refFullNames := make([]git.RefName, hookBatchSize)
count := 0
total := 0
wasEmpty := false
masterPushed := false
results := make([]private.HookPostReceiveBranchResult, 0)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
// TODO: support news feeds for wiki
if isWiki {
continue
}
var ok bool
oldCommitIDs[count], newCommitIDs[count], refFullNames[count], ok = parseGitHookCommitRefLine(scanner.Text())
if !ok {
continue
}
fmt.Fprintf(out, ".")
commitID, _ := git.NewIDFromString(newCommitIDs[count])
if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
masterPushed = true
}
count++
total++
if count >= hookBatchSize {
fmt.Fprintf(out, " Processing %d references\n", count)
hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if extra.HasError() {
_ = dWriter.Close()
hookPrintResults(results)
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
}
wasEmpty = wasEmpty || resp.RepoWasEmpty
results = append(results, resp.Results...)
count = 0
}
}
if err := scanner.Err(); err != nil {
_ = dWriter.Close()
hookPrintResults(results)
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
}
if count == 0 {
if wasEmpty && masterPushed {
// We need to tell the repo to reset the default branch to master
extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
if extra.HasError() {
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
}
}
fmt.Fprintf(out, "Processed %d references in total\n", total)
_ = dWriter.Close()
hookPrintResults(results)
return nil
}
hookOptions.OldCommitIDs = oldCommitIDs[:count]
hookOptions.NewCommitIDs = newCommitIDs[:count]
hookOptions.RefFullNames = refFullNames[:count]
fmt.Fprintf(out, " Processing %d references\n", count)
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
if resp == nil {
_ = dWriter.Close()
hookPrintResults(results)
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
}
wasEmpty = wasEmpty || resp.RepoWasEmpty
results = append(results, resp.Results...)
fmt.Fprintf(out, "Processed %d references in total\n", total)
if wasEmpty && masterPushed {
// We need to tell the repo to reset the default branch to master
extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
if extra.HasError() {
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
}
}
_ = dWriter.Close()
hookPrintResults(results)
return nil
}
func hookPrintResults(results []private.HookPostReceiveBranchResult) {
for _, res := range results {
hookPrintResult(res.Message, res.Create, res.Branch, res.URL)
}
}
func hookPrintResult(output, isCreate bool, branch, url string) {
if !output {
return
}
fmt.Fprintln(os.Stderr, "")
if isCreate {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
fmt.Fprintf(os.Stderr, " %s\n", url)
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", url)
}
fmt.Fprintln(os.Stderr, "")
_ = os.Stderr.Sync()
}
func pushOptions() map[string]string {
opts := make(map[string]string)
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
for idx := range pushCount {
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
kv := strings.SplitN(opt, "=", 2)
if len(kv) == 2 {
opts[kv[0]] = kv[1]
}
}
}
return opts
}
func runHookProcReceive(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(ctx, `Rejecting changes as Gitea environment not set.
If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "")
}
return nil
}
if !git.DefaultFeatures().SupportProcReceive {
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
}
reader := bufio.NewReader(os.Stdin)
repoUser := os.Getenv(repo_module.EnvRepoUsername)
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
repoName := os.Getenv(repo_module.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
pusherName := os.Getenv(repo_module.EnvPusherName)
// 1. Version and features negotiation.
// S: PKT-LINE(version=1\0push-options atomic...) / PKT-LINE(version=1\n)
// S: flush-pkt
// H: PKT-LINE(version=1\0push-options...)
// H: flush-pkt
rs, err := readPktLine(ctx, reader, pktLineTypeData)
if err != nil {
return err
}
const VersionHead string = "version=1"
var (
hasPushOptions bool
response = []byte(VersionHead)
requestOptions []string
)
index := bytes.IndexByte(rs.Data, byte(0))
if index >= len(rs.Data) {
return fail(ctx, "Protocol: format error", "pkt-line: format error %s", rs.Data)
}
if index < 0 {
if len(rs.Data) == 10 && rs.Data[9] == '\n' {
index = 9
} else {
return fail(ctx, "Protocol: format error", "pkt-line: format error %s", rs.Data)
}
}
if string(rs.Data[0:index]) != VersionHead {
return fail(ctx, "Protocol: version error", "Received unsupported version: %s", string(rs.Data[0:index]))
}
requestOptions = strings.Split(string(rs.Data[index+1:]), " ")
for _, option := range requestOptions {
if strings.HasPrefix(option, "push-options") {
response = append(response, byte(0))
response = append(response, []byte("push-options")...)
hasPushOptions = true
}
}
response = append(response, '\n')
_, err = readPktLine(ctx, reader, pktLineTypeFlush)
if err != nil {
return err
}
err = writeDataPktLine(ctx, os.Stdout, response)
if err != nil {
return err
}
err = writeFlushPktLine(ctx, os.Stdout)
if err != nil {
return err
}
// 2. receive commands from server.
// S: PKT-LINE(<old-oid> <new-oid> <ref>)
// S: ... ...
// S: flush-pkt
// # [receive push-options]
// S: PKT-LINE(push-option)
// S: ... ...
// S: flush-pkt
hookOptions := private.HookOptions{
UserName: pusherName,
UserID: pusherID,
GitPushOptions: make(map[string]string),
IsWiki: isWiki,
}
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.RefFullNames = make([]git.RefName, 0, hookBatchSize)
for {
// note: pktLineTypeUnknown means pktLineTypeFlush and pktLineTypeData all allowed
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
if rs.Type == pktLineTypeFlush {
break
}
t := strings.SplitN(string(rs.Data), " ", 3)
if len(t) != 3 {
continue
}
hookOptions.OldCommitIDs = append(hookOptions.OldCommitIDs, t[0])
hookOptions.NewCommitIDs = append(hookOptions.NewCommitIDs, t[1])
hookOptions.RefFullNames = append(hookOptions.RefFullNames, git.RefName(t[2]))
}
if hasPushOptions {
for {
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil {
return err
}
if rs.Type == pktLineTypeFlush {
break
}
hookOptions.GitPushOptions.AddFromKeyValue(string(rs.Data))
}
}
// 3. run hook
resp, extra := private.HookProcReceive(ctx, repoUser, repoName, hookOptions)
if extra.HasError() {
return fail(ctx, extra.UserMsg, "HookProcReceive failed: %v", extra.Error)
}
// 4. response result to service
// # a. OK, but has an alternate reference. The alternate reference name
// # and other status can be given in option directives.
// H: PKT-LINE(ok <ref>)
// H: PKT-LINE(option refname <refname>)
// H: PKT-LINE(option old-oid <old-oid>)
// H: PKT-LINE(option new-oid <new-oid>)
// H: PKT-LINE(option forced-update)
// H: ... ...
// H: flush-pkt
// # b. NO, I reject it.
// H: PKT-LINE(ng <ref> <reason>)
// # c. Fall through, let 'receive-pack' to execute it.
// H: PKT-LINE(ok <ref>)
// H: PKT-LINE(option fall-through)
for _, rs := range resp.Results {
if len(rs.Err) > 0 {
err = writeDataPktLine(ctx, os.Stdout, []byte("ng "+rs.OriginalRef.String()+" "+rs.Err))
if err != nil {
return err
}
continue
}
if rs.IsNotMatched {
err = writeDataPktLine(ctx, os.Stdout, []byte("ok "+rs.OriginalRef.String()))
if err != nil {
return err
}
err = writeDataPktLine(ctx, os.Stdout, []byte("option fall-through"))
if err != nil {
return err
}
continue
}
err = writeDataPktLine(ctx, os.Stdout, []byte("ok "+rs.OriginalRef))
if err != nil {
return err
}
err = writeDataPktLine(ctx, os.Stdout, []byte("option refname "+rs.Ref))
if err != nil {
return err
}
commitID, _ := git.NewIDFromString(rs.OldOID)
if !commitID.IsZero() {
err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID))
if err != nil {
return err
}
}
err = writeDataPktLine(ctx, os.Stdout, []byte("option new-oid "+rs.NewOID))
if err != nil {
return err
}
if rs.IsForcePush {
err = writeDataPktLine(ctx, os.Stdout, []byte("option forced-update"))
if err != nil {
return err
}
}
}
err = writeFlushPktLine(ctx, os.Stdout)
if err == nil {
for _, res := range resp.Results {
hookPrintResult(res.ShouldShowMessage, res.IsCreatePR, res.HeadBranch, res.URL)
}
}
return err
}
// git PKT-Line api
// pktLineType message type of pkt-line
type pktLineType int64
const (
// Unknown type
pktLineTypeUnknown pktLineType = 0
// flush-pkt "0000"
pktLineTypeFlush pktLineType = iota
// data line
pktLineTypeData
)
// gitPktLine pkt-line api
type gitPktLine struct {
Type pktLineType
Length uint64
Data []byte
}
func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
var (
err error
r *gitPktLine
)
// read prefix
lengthBytes := make([]byte, 4)
for i := range 4 {
lengthBytes[i], err = in.ReadByte()
if err != nil {
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
}
}
r = new(gitPktLine)
r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
if err != nil {
return nil, fail(ctx, "Protocol: format parse error", "Pkt-Line format is wrong :%v", err)
}
if r.Length == 0 {
if requestType == pktLineTypeData {
return nil, fail(ctx, "Protocol: format data error", "Pkt-Line format is wrong")
}
r.Type = pktLineTypeFlush
return r, nil
}
if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush {
return nil, fail(ctx, "Protocol: format length error", "Pkt-Line format is wrong")
}
r.Data = make([]byte, r.Length-4)
for i := range r.Data {
r.Data[i], err = in.ReadByte()
if err != nil {
return nil, fail(ctx, "Protocol: data error", "Pkt-Line: read stdin failed : %v", err)
}
}
r.Type = pktLineTypeData
return r, nil
}
func writeFlushPktLine(ctx context.Context, out io.Writer) error {
l, err := out.Write([]byte("0000"))
if err != nil || l != 4 {
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
}
return nil
}
func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error {
hexchar := []byte("0123456789abcdef")
hex := func(n uint64) byte {
return hexchar[(n)&15]
}
length := uint64(len(data) + 4)
tmp := make([]byte, 4)
tmp[0] = hex(length >> 12)
tmp[1] = hex(length >> 8)
tmp[2] = hex(length >> 4)
tmp[3] = hex(length)
lr, err := out.Write(tmp)
if err != nil || lr != 4 {
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
}
lr, err = out.Write(data)
if err != nil || int(length-4) != lr {
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
}
return nil
}

44
cmd/mailer.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func runSendMail(ctx context.Context, c *cli.Command) error {
setting.MustInstalled()
subject := c.String("title")
confirmSkipped := c.Bool("force")
body := c.String("content")
if !confirmSkipped {
if len(body) == 0 {
fmt.Print("warning: Content is empty")
}
fmt.Print("Proceed with sending email? [Y/n] ")
isConfirmed, err := confirm()
if err != nil {
return err
} else if !isConfirmed {
fmt.Println("The mail was not sent")
return nil
}
}
respText, extra := private.SendEmail(ctx, subject, body, nil)
if extra.HasError() {
return handleCliResponseExtra(extra)
}
_, _ = fmt.Printf("Sent %s email(s) to all users\n", respText.Text)
return nil
}

View File

@ -4,7 +4,9 @@
package cmd
import (
"context"
"fmt"
"io"
"os"
"strings"
@ -12,30 +14,33 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var cliHelpPrinterOld = cli.HelpPrinter
func init() {
cli.HelpPrinter = cliHelpPrinterNew
}
// cliHelpPrinterNew helps to print "DEFAULT CONFIGURATION" for the following cases ( "-c" can apper in any position):
// * ./gitea -c /dev/null -h
// * ./gitea -c help /dev/null help
// * ./gitea help -c /dev/null
// * ./gitea help -c /dev/null web
// * ./gitea help web -c /dev/null
// * ./gitea web help -c /dev/null
// * ./gitea web -h -c /dev/null
// 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) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
targetCmdIdx := 0
if c.Command.Name == "help" {
targetCmdIdx = 1
}
if lineage[targetCmdIdx+1].Command != nil {
err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
func cliHelpPrinterNew(out io.Writer, templ string, data any) {
cmd, _ := data.(*cli.Command)
if cmd != nil {
prepareWorkPathAndCustomConf(cmd)
}
cliHelpPrinterOld(out, templ, data)
if setting.CustomConf != "" {
_, _ = fmt.Fprintf(out, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
@ -43,10 +48,7 @@ DEFAULT CONFIGURATION:
ConfigFile: %s
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
return err
},
}
return c
}
var helpFlag = cli.HelpFlag
@ -82,100 +84,108 @@ func appGlobalFlags() []cli.Flag {
}
}
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)
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
originBefore := originCmd.Before
originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) {
ctx = ctxOrig
if originBefore != nil {
ctx, err = originBefore(ctx, cmd)
if err != nil {
return ctx, err
}
}
prepareWorkPathAndCustomConf(cmd)
return ctx, nil
}
}
// 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
// from children to parent, check the global flags
for _, curCtx := range ctx.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
args.CustomPath = curCtx.String("custom-path")
}
if curCtx.IsSet("config") && args.CustomConf == "" {
args.CustomConf = curCtx.String("config")
}
}
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)
// prepareWorkPathAndCustomConf tries to prepare the work path, custom path and custom config from various inputs:
// command line flags, environment variables, config file
func prepareWorkPathAndCustomConf(cmd *cli.Command) {
var args setting.ArgWorkPathAndCustomConf
if cmd.IsSet("work-path") {
args.WorkPath = cmd.String("work-path")
}
if cmd.IsSet("custom-path") {
args.CustomPath = cmd.String("custom-path")
}
if cmd.IsSet("config") {
args.CustomConf = cmd.String("config")
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
}
func NewMainApp(version, versionExtra string) *cli.App {
app := cli.NewApp()
func NewMainApp(version, versionExtra string) *cli.Command {
app := &cli.Command{}
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 + versionExtra
app.EnableBashCompletion = true
app.EnableShellCompletion = true
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "work-path",
Aliases: []string{"w"},
TakesFile: true,
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
TakesFile: true,
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
&cli.StringFlag{
Name: "custom-path",
Aliases: []string{"C"},
TakesFile: true,
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
}
// these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{
CmdWeb,
gitea_cmd.CmdServ,
gitea_cmd.CmdHook,
gitea_cmd.CmdDump,
gitea_cmd.CmdAdmin,
gitea_cmd.CmdMigrate,
gitea_cmd.CmdKeys,
gitea_cmd.CmdDoctor,
gitea_cmd.CmdManager,
gitea_cmd.CmdEmbedded,
gitea_cmd.CmdMigrateStorage,
gitea_cmd.CmdDumpRepository,
gitea_cmd.CmdRestoreRepository,
gitea_cmd.CmdActions,
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
newServCommand(),
newHookCommand(),
gitea_cmd.NewKeysCommand(),
newDumpCommand(),
newAdminCommand(),
newMigrateCommand(),
newDoctorCommand(),
newManagerCommand(),
newEmbeddedCommand(),
newMigrateStorageCommand(),
newDumpRepositoryCommand(),
newRestoreRepositoryCommand(),
newActionsCommand(),
}
cmdConvert := util.ToPointer(*cmdDoctorConvert)
cmdConvert.Hidden = true // still support the legacy "./gitea doctor" by the hidden sub-command, remove it in next release
subCmdWithConfig = append(subCmdWithConfig, cmdConvert)
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
gitea_cmd.CmdCert,
gitea_cmd.CmdGenerate,
gitea_cmd.CmdDocs,
cmdCert(),
cmdConfig(),
newGenerateCommand(),
newDocsCommand(),
}
// TODO: we should eventually drop the default command,
// but not sure whether it would break Windows users who used to double-click the EXE to run.
app.DefaultCommand = CmdWeb.Name
globalFlags := appGlobalFlags()
app.Flags = append(app.Flags, cli.VersionFlag)
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 = gitea_cmd.PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig {
prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
prepareSubcommandWithGlobalFlags(subCmdWithConfig[i])
}
app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...)
setting.UnsetUnnecessaryEnvVars()
return app
}
func RunMainApp(app *cli.App, args ...string) error {
err := app.Run(args)
func RunMainApp(app *cli.Command, args ...string) error {
ctx, cancel := installSignals()
defer cancel()
err := app.Run(ctx, args)
if err == nil {
return nil
}

154
cmd/manager.go Normal file
View File

@ -0,0 +1,154 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"os"
"time"
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3"
)
func newManagerCommand() *cli.Command {
return &cli.Command{
Name: "manager",
Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process",
Commands: []*cli.Command{
newShutdownCommand(),
newRestartCommand(),
newReloadTemplatesCommand(),
newFlushQueuesCommand(),
newLoggingCommand(),
newProcessesCommand(),
},
}
}
func newShutdownCommand() *cli.Command {
return &cli.Command{
Name: "shutdown",
Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runShutdown,
}
}
func newRestartCommand() *cli.Command {
return &cli.Command{
Name: "restart",
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runRestart,
}
}
func newReloadTemplatesCommand() *cli.Command {
return &cli.Command{
Name: "reload-templates",
Usage: "Reload template files in the running process",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runReloadTemplates,
}
}
func newFlushQueuesCommand() *cli.Command {
return &cli.Command{
Name: "flush-queues",
Usage: "Flush queues in the running process",
Action: runFlushQueues,
Flags: []cli.Flag{
&cli.DurationFlag{
Name: "timeout",
Value: 60 * time.Second,
Usage: "Timeout for the flushing process",
},
&cli.BoolFlag{
Name: "non-blocking",
Usage: "Set to true to not wait for flush to complete before returning",
},
&cli.BoolFlag{
Name: "debug",
},
},
}
}
func newProcessesCommand() *cli.Command {
return &cli.Command{
Name: "processes",
Usage: "Display running processes within the current process",
Action: runProcesses,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
},
&cli.BoolFlag{
Name: "flat",
Usage: "Show processes as flat table rather than as tree",
},
&cli.BoolFlag{
Name: "no-system",
Usage: "Do not show system processes",
},
&cli.BoolFlag{
Name: "stacktraces",
Usage: "Show stacktraces",
},
&cli.BoolFlag{
Name: "json",
Usage: "Output as json",
},
&cli.StringFlag{
Name: "cancel",
Usage: "Process PID to cancel. (Only available for non-system processes.)",
},
},
}
}
func runShutdown(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.Shutdown(ctx)
return handleCliResponseExtra(extra)
}
func runRestart(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.Restart(ctx)
return handleCliResponseExtra(extra)
}
func runReloadTemplates(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.ReloadTemplates(ctx)
return handleCliResponseExtra(extra)
}
func runFlushQueues(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
return handleCliResponseExtra(extra)
}
func runProcesses(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
return handleCliResponseExtra(extra)
}

325
cmd/manager_logging.go Normal file
View File

@ -0,0 +1,325 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3"
)
func defaultLoggingFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "logger",
Usage: `Logger name - will default to "default"`,
},
&cli.StringFlag{
Name: "writer",
Usage: "Name of the log writer - will default to mode",
},
&cli.StringFlag{
Name: "level",
Usage: "Logging level for the new logger",
},
&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{
Name: "debug",
},
}
}
func newLoggingCommand() *cli.Command {
return &cli.Command{
Name: "logging",
Usage: "Adjust logging commands",
Commands: []*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{
Name: "debug",
},
},
Action: runPauseLogging,
}, {
Name: "resume",
Usage: "Resume logging",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runResumeLogging,
}, {
Name: "release-and-reopen",
Usage: "Cause Gitea to release and re-open files used for logging",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
},
},
Action: runReleaseReopenLogging,
}, {
Name: "remove",
Usage: "Remove a logger",
ArgsUsage: "[name] Name of logger to remove",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
}, &cli.StringFlag{
Name: "logger",
Usage: `Logger name - will default to "default"`,
},
},
Action: runRemoveLogger,
}, {
Name: "add",
Usage: "Add a logger",
Commands: []*cli.Command{
{
Name: "file",
Usage: "Add a file logger",
Flags: append(defaultLoggingFlags(), []cli.Flag{
&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,
}, {
Name: "conn",
Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags(), []cli.Flag{
&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,
},
},
}, {
Name: "log-sql",
Usage: "Set LogSQL",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
},
&cli.BoolFlag{
Name: "off",
Usage: "Switch off SQL logging",
},
},
Action: runSetLogSQL,
},
},
}
}
func runRemoveLogger(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
logger := c.String("logger")
if len(logger) == 0 {
logger = log.DEFAULT
}
writer := c.Args().First()
extra := private.RemoveLogger(ctx, logger, writer)
return handleCliResponseExtra(extra)
}
func runAddConnLogger(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
vals := map[string]any{}
mode := "conn"
vals["net"] = "tcp"
if c.IsSet("protocol") {
switch c.String("protocol") {
case "udp":
vals["net"] = "udp"
case "unix":
vals["net"] = "unix"
}
}
if c.IsSet("address") {
vals["address"] = c.String("address")
} else {
vals["address"] = ":7020"
}
if c.IsSet("reconnect") {
vals["reconnect"] = c.Bool("reconnect")
}
if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
}
return commonAddLogger(ctx, c, mode, vals)
}
func runAddFileLogger(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
vals := map[string]any{}
mode := "file"
if c.IsSet("filename") {
vals["filename"] = c.String("filename")
} else {
return errors.New("filename must be set when creating a file logger")
}
if c.IsSet("rotate") {
vals["rotate"] = c.Bool("rotate")
}
if c.IsSet("max-size") {
vals["maxsize"] = c.Int64("max-size")
}
if c.IsSet("daily") {
vals["daily"] = c.Bool("daily")
}
if c.IsSet("max-days") {
vals["maxdays"] = c.Int("max-days")
}
if c.IsSet("compress") {
vals["compress"] = c.Bool("compress")
}
if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level")
}
return commonAddLogger(ctx, c, mode, vals)
}
func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error {
if len(c.String("level")) > 0 {
vals["level"] = log.LevelFromString(c.String("level")).String()
}
if len(c.String("stacktrace-level")) > 0 {
vals["stacktraceLevel"] = log.LevelFromString(c.String("stacktrace-level")).String()
}
if len(c.String("expression")) > 0 {
vals["expression"] = c.String("expression")
}
if len(c.String("prefix")) > 0 {
vals["prefix"] = c.String("prefix")
}
if len(c.String("flags")) > 0 {
vals["flags"] = log.FlagsFromString(c.String("flags"))
}
if c.IsSet("color") {
vals["colorize"] = c.Bool("color")
}
logger := log.DEFAULT
if c.IsSet("logger") {
logger = c.String("logger")
}
writer := mode
if c.IsSet("writer") {
writer = c.String("writer")
}
extra := private.AddLogger(ctx, logger, writer, mode, vals)
return handleCliResponseExtra(extra)
}
func runPauseLogging(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
userMsg := private.PauseLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil
}
func runResumeLogging(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
userMsg := private.ResumeLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil
}
func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
userMsg := private.ReleaseReopenLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil
}
func runSetLogSQL(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.SetLogSQL(ctx, !c.Bool("off"))
return handleCliResponseExtra(extra)
}

View File

@ -2,64 +2,22 @@ package cmd
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// install notify
signalChannel := make(chan os.Signal, 1)
signal.Notify(
signalChannel,
syscall.SIGINT,
syscall.SIGTERM,
)
select {
case <-signalChannel:
case <-ctx.Done():
}
cancel()
signal.Reset()
}()
return ctx, cancel
}
func initDB(ctx context.Context) error {
setting.LoadCommonSettings()
setting.LoadDBSetting()
setting.InitSQLLoggersForCli(log.INFO)
if setting.Database.Type == "" {
log.Fatal(`Database settings are missing from the configuration file: %q.
Ensure you are running in the correct environment or set the correct configuration file with -c.
If this is the intended configuration file complete the [database] section.`, setting.CustomConf)
func newMigrateCommand() *cli.Command {
return &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.`,
Action: runMigrate,
}
if err := db.InitEngine(ctx); err != nil {
return fmt.Errorf("unable to initialize the database using the configuration in %q. Error: %v", setting.CustomConf, err)
}
return nil
}
// CmdMigrate represents the available migrate sub-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.",
Action: runMigrate,
}
func runMigrate(ctx *cli.Context) error {
func runMigrate(ctx context.Context, c *cli.Command) error {
stdCtx, cancel := installSignals()
defer cancel()

310
cmd/migrate_storage.go Normal file
View File

@ -0,0 +1,310 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"io/fs"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/versioned_migration"
"github.com/urfave/cli/v3"
)
func newMigrateStorageCommand() *cli.Command {
return &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",
Aliases: []string{"t"},
Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'",
},
&cli.StringFlag{
Name: "storage",
Aliases: []string{"s"},
Value: "",
Usage: "New storage type: local (default), minio or azureblob",
},
&cli.StringFlag{
Name: "path",
Aliases: []string{"p"},
Value: "",
Usage: "New storage placement if store is local (leave blank for default)",
},
// Minio Storage special configurations
&cli.StringFlag{
Name: "minio-endpoint",
Value: "",
Usage: "Minio storage endpoint",
},
&cli.StringFlag{
Name: "minio-access-key-id",
Value: "",
Usage: "Minio storage accessKeyID",
},
&cli.StringFlag{
Name: "minio-secret-access-key",
Value: "",
Usage: "Minio storage secretAccessKey",
},
&cli.StringFlag{
Name: "minio-bucket",
Value: "",
Usage: "Minio storage bucket",
},
&cli.StringFlag{
Name: "minio-location",
Value: "",
Usage: "Minio storage location to create bucket",
},
&cli.StringFlag{
Name: "minio-base-path",
Value: "",
Usage: "Minio storage base path on the bucket",
},
&cli.BoolFlag{
Name: "minio-use-ssl",
Usage: "Enable SSL for minio",
},
&cli.BoolFlag{
Name: "minio-insecure-skip-verify",
Usage: "Skip SSL verification",
},
&cli.StringFlag{
Name: "minio-checksum-algorithm",
Value: "",
Usage: "Minio checksum algorithm (default/md5)",
},
&cli.StringFlag{
Name: "minio-bucket-lookup-type",
Value: "",
Usage: "Minio bucket lookup type",
},
// Azure Blob Storage special configurations
&cli.StringFlag{
Name: "azureblob-endpoint",
Value: "",
Usage: "Azure Blob storage endpoint",
},
&cli.StringFlag{
Name: "azureblob-account-name",
Value: "",
Usage: "Azure Blob storage account name",
},
&cli.StringFlag{
Name: "azureblob-account-key",
Value: "",
Usage: "Azure Blob storage account key",
},
&cli.StringFlag{
Name: "azureblob-container",
Value: "",
Usage: "Azure Blob storage container",
},
&cli.StringFlag{
Name: "azureblob-base-path",
Value: "",
Usage: "Azure Blob storage base path",
},
},
}
}
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, attach *repo_model.Attachment) error {
_, err := storage.Copy(dstStorage, attach.RelativePath(), storage.Attachments, attach.RelativePath())
return err
})
}
func migrateLFS(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, mo *git_model.LFSMetaObject) error {
_, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath())
return err
})
}
func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error {
if user.CustomAvatarRelativePath() == "" {
return nil
}
_, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath())
return err
})
}
func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error {
if repo.CustomAvatarRelativePath() == "" {
return nil
}
_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
return err
})
}
func migrateRepoArchivers(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, archiver *repo_model.RepoArchiver) error {
p := archiver.RelativePath()
_, err := storage.Copy(dstStorage, p, storage.RepoArchives, p)
return err
})
}
func migratePackages(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, pb *packages_model.PackageBlob) error {
p := packages_module.KeyToRelativePath(packages_module.BlobHash256Key(pb.HashSHA256))
_, err := storage.Copy(dstStorage, p, storage.Packages, p)
return err
})
}
func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, task *actions_model.ActionTask) error {
if task.LogExpired {
// the log has been cleared
return nil
}
if !task.LogInStorage {
// running tasks store logs in DBFS
return nil
}
p := task.LogFilename
_, err := storage.Copy(dstStorage, p, storage.Actions, p)
return err
})
}
func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
if artifact.Status == actions_model.ArtifactStatusExpired {
return nil
}
_, err := storage.Copy(dstStorage, artifact.StoragePath, storage.ActionsArtifacts, artifact.StoragePath)
if err != nil {
// ignore files that do not exist
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
return nil
})
}
func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
log.Info("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf)
if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err)
return err
}
if err := storage.Init(); err != nil {
return err
}
var dstStorage storage.ObjectStorage
var err error
switch strings.ToLower(cmd.String("storage")) {
case "":
fallthrough
case string(setting.LocalStorageType):
p := cmd.String("path")
if p == "" {
log.Fatal("Path must be given when storage is local")
return nil
}
dstStorage, err = storage.NewLocalStorage(
ctx,
&setting.Storage{
Path: p,
})
case string(setting.MinioStorageType):
dstStorage, err = storage.NewMinioStorage(
ctx,
&setting.Storage{
MinioConfig: setting.MinioStorageConfig{
Endpoint: cmd.String("minio-endpoint"),
AccessKeyID: cmd.String("minio-access-key-id"),
SecretAccessKey: cmd.String("minio-secret-access-key"),
Bucket: cmd.String("minio-bucket"),
Location: cmd.String("minio-location"),
BasePath: cmd.String("minio-base-path"),
UseSSL: cmd.Bool("minio-use-ssl"),
InsecureSkipVerify: cmd.Bool("minio-insecure-skip-verify"),
ChecksumAlgorithm: cmd.String("minio-checksum-algorithm"),
BucketLookUpType: cmd.String("minio-bucket-lookup-type"),
},
})
case string(setting.AzureBlobStorageType):
dstStorage, err = storage.NewAzureBlobStorage(
ctx,
&setting.Storage{
AzureBlobConfig: setting.AzureBlobStorageConfig{
Endpoint: cmd.String("azureblob-endpoint"),
AccountName: cmd.String("azureblob-account-name"),
AccountKey: cmd.String("azureblob-account-key"),
Container: cmd.String("azureblob-container"),
BasePath: cmd.String("azureblob-base-path"),
},
})
default:
return fmt.Errorf("unsupported storage type: %s", cmd.String("storage"))
}
if err != nil {
return err
}
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
"attachments": migrateAttachments,
"lfs": migrateLFS,
"avatars": migrateAvatars,
"repo-avatars": migrateRepoAvatars,
"repo-archivers": migrateRepoArchivers,
"packages": migratePackages,
"actions-log": migrateActionsLog,
"actions-artifacts": migrateActionsArtifacts,
}
tp := strings.ToLower(cmd.String("type"))
if m, ok := migratedMethods[tp]; ok {
if err := m(ctx, dstStorage); err != nil {
return err
}
log.Info("%s files have successfully been copied to the new storage.", tp)
return nil
}
return fmt.Errorf("unsupported storage: %s", cmd.String("type"))
}

68
cmd/restore_repo.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"strings"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func newRestoreRepositoryCommand() *cli.Command {
return &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",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to restore from",
},
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "Restore destination owner name",
},
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "Restore destination repository name",
},
&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{
Name: "validation",
Usage: "Sanity check the content of the files before trying to load them",
},
},
}
}
func runRestoreRepository(ctx context.Context, c *cli.Command) error {
setting.MustInstalled()
var units []string
if s := c.String("units"); s != "" {
units = strings.Split(s, ",")
}
extra := private.RestoreRepo(
ctx,
c.String("repo_dir"),
c.String("owner_name"),
c.String("repo_name"),
units,
c.Bool("validation"),
)
return handleCliResponseExtra(extra)
}

348
cmd/serv.go Normal file
View File

@ -0,0 +1,348 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"unicode"
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/pprof"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/process"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/lfs"
"github.com/kballard/go-shellquote"
"github.com/urfave/cli/v3"
)
func newServCommand() *cli.Command {
return &cli.Command{
Name: "serv",
Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
Hidden: true, // Internal commands shouldn't be visible in help
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "enable-pprof",
},
&cli.BoolFlag{
Name: "debug",
},
},
}
}
func setup(ctx context.Context, debug bool) {
if debug {
setupConsoleLogger(log.TRACE, false, os.Stderr)
} else {
setupConsoleLogger(log.FATAL, false, os.Stderr)
}
setting.MustInstalled()
if _, err := os.Stat(setting.RepoRootPath); err != nil {
_ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
return
}
if err := git.InitSimple(); err != nil {
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
}
}
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
// The output will be passed to git client and shown to user.
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
if userMessage == "" {
userMessage = "Internal Server Error (no specific error)"
}
// There appears to be a chance to cause a zombie process and failure to read the Exit status
// if nothing is outputted on stdout.
_, _ = fmt.Fprintln(os.Stdout, "")
// add extra empty lines to separate our message from other git errors to get more attention
_, _ = fmt.Fprintln(os.Stderr, "error:")
_, _ = fmt.Fprintln(os.Stderr, "error:", userMessage)
_, _ = fmt.Fprintln(os.Stderr, "error:")
if logMsgFmt != "" {
logMsg := fmt.Sprintf(logMsgFmt, args...)
if !setting.IsProd {
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", logMsg)
}
if unicode.IsPunct(rune(userMessage[len(userMessage)-1])) {
logMsg = userMessage + " " + logMsg
} else {
logMsg = userMessage + ". " + logMsg
}
_ = private.SSHLog(ctx, true, logMsg)
}
return cli.Exit("", 1)
}
// handleCliResponseExtra handles the extra response from the cli sub-commands
// If there is a user message it will be printed to stdout
// If the command failed it will return an error (the error will be printed by cli framework)
func handleCliResponseExtra(extra private.ResponseExtra) error {
if extra.UserMsg != "" {
_, _ = fmt.Fprintln(os.Stdout, extra.UserMsg)
}
if extra.HasError() {
return cli.Exit(extra.Error, 1)
}
return nil
}
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
switch verb {
case git.CmdVerbUploadPack, git.CmdVerbUploadArchive:
return perm.AccessModeRead
case git.CmdVerbReceivePack:
return perm.AccessModeWrite
case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer:
switch lfsVerb {
case git.CmdSubVerbLfsUpload:
return perm.AccessModeWrite
case git.CmdSubVerbLfsDownload:
return perm.AccessModeRead
}
}
// should be unreachable
setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
return perm.AccessModeNone
}
func runServ(ctx context.Context, c *cli.Command) error {
// FIXME: This needs to internationalised
setup(ctx, c.Bool("debug"))
if setting.SSH.Disabled {
println("Gitea: SSH has been disabled")
return nil
}
if c.NArg() < 1 {
if err := cli.ShowSubcommandHelp(c); err != nil {
fmt.Printf("error showing subcommand help: %v\n", err)
}
return nil
}
defer func() {
if err := recover(); err != nil {
_ = fail(ctx, "Internal Server Error", "Panic: %v\n%s", err, log.Stack(2))
}
}()
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().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().Get(1))
}
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
if len(cmd) == 0 {
key, user, err := private.ServNoCommand(ctx, keyID)
if err != nil {
return fail(ctx, "Key check failed", "Failed to check provided key: %v", err)
}
switch key.Type {
case asymkey_model.KeyTypeDeploy:
println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.")
case asymkey_model.KeyTypePrincipal:
println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Gitea does not provide shell access.")
default:
println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.")
}
println("If this is unexpected, please log in with password and setup Gitea under another user.")
return nil
} else if c.Bool("debug") {
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
}
sshCmdArgs, err := shellquote.Split(cmd)
if err != nil {
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
}
if len(sshCmdArgs) < 2 {
if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow
if cmd == "ssh_info" {
fmt.Print(`{"type":"agit","version":1}`)
return nil
}
}
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
}
repoPath := strings.TrimPrefix(sshCmdArgs[1], "/")
repoPathFields := strings.SplitN(repoPath, "/", 2)
if len(repoPathFields) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
}
username := repoPathFields[0]
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
if !repo_model.IsValidSSHAccessRepoName(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
}
if c.Bool("enable-pprof") {
if err := os.MkdirAll(setting.PprofDataPath, os.ModePerm); err != nil {
return fail(ctx, "Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err)
}
stopCPUProfiler, err := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username)
if err != nil {
return fail(ctx, "Unable to start CPU profiler", "Unable to start CPU profile: %v", err)
}
defer func() {
stopCPUProfiler()
err := pprof.DumpMemProfileForUsername(setting.PprofDataPath, username)
if err != nil {
_ = fail(ctx, "Unable to dump Mem profile", "Unable to dump Mem Profile: %v", err)
}
}()
}
verb, lfsVerb := sshCmdArgs[0], ""
if !git.IsAllowedVerbForServe(verb) {
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
}
if git.IsAllowedVerbForServeLfs(verb) {
if !setting.LFS.StartServer {
return fail(ctx, "LFS Server is not enabled", "")
}
if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
return fail(ctx, "LFS SSH transfer is not enabled", "")
}
if len(sshCmdArgs) > 2 {
lfsVerb = sshCmdArgs[2]
}
}
requestedMode := getAccessMode(verb, lfsVerb)
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
if extra.HasError() {
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
}
// because the original repoPath maybe redirected, we need to use the returned actual repository information
if results.IsWiki {
repoPath = repo_model.RelativeWikiPath(results.OwnerName, results.RepoName)
} else {
repoPath = repo_model.RelativePath(results.OwnerName, results.RepoName)
}
// LFS SSH protocol
if verb == git.CmdVerbLfsTransfer {
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
if err != nil {
return err
}
return lfstransfer.Main(ctx, repoPath, lfsVerb, token)
}
// LFS token authentication
if verb == git.CmdVerbLfsAuthenticate {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
if err != nil {
return err
}
tokenAuthentication := &git_model.LFSTokenResponse{
Header: make(map[string]string),
Href: url,
}
tokenAuthentication.Header["Authorization"] = token
enc := json.NewEncoder(os.Stdout)
err = enc.Encode(tokenAuthentication)
if err != nil {
return fail(ctx, "Failed to encode LFS json response", "Failed to encode LFS json response: %v", err)
}
return nil
}
var command *exec.Cmd
gitBinPath := filepath.Dir(gitcmd.GitExecutable) // e.g. /usr/bin
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
if _, err := os.Stat(gitBinVerb); err != nil {
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
verbFields := strings.SplitN(verb, "-", 2)
if len(verbFields) == 2 {
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
command = exec.CommandContext(ctx, gitcmd.GitExecutable, verbFields[1], repoPath)
}
}
if command == nil {
// by default, use the verb (it has been checked above by allowedCommands)
command = exec.CommandContext(ctx, gitBinVerb, repoPath)
}
process.SetSysProcAttribute(command)
command.Dir = setting.RepoRootPath
command.Stdout = os.Stdout
command.Stdin = os.Stdin
command.Stderr = os.Stderr
command.Env = append(command.Env, os.Environ()...)
command.Env = append(command.Env,
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
repo_module.EnvRepoName+"="+results.RepoName,
repo_module.EnvRepoUsername+"="+results.OwnerName,
repo_module.EnvPusherName+"="+results.UserName,
repo_module.EnvPusherEmail+"="+results.UserEmail,
repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10),
repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10),
repo_module.EnvPRID+"="+strconv.Itoa(0),
repo_module.EnvDeployKeyID+"="+strconv.FormatInt(results.DeployKeyID, 10),
repo_module.EnvKeyID+"="+strconv.FormatInt(results.KeyID, 10),
repo_module.EnvAppURL+"="+setting.AppURL,
)
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
command.Env = append(command.Env, gitcmd.CommonCmdServEnvs()...)
if err = command.Run(); err != nil {
return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err)
}
// Update user key activity.
if results.KeyID > 0 {
if err = private.UpdatePublicKeyInRepo(ctx, results.KeyID, results.RepoID); err != nil {
return fail(ctx, "Failed to update public key", "UpdatePublicKeyInRepo: %v", err)
}
}
return nil
}

View File

@ -13,16 +13,19 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
gitea_cmd "code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/gtprof"
"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/modules/util"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/install"
hat_routers "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers"
@ -30,7 +33,7 @@ import (
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var PIDFile = "/run/gitea.pid"
@ -119,9 +122,19 @@ func showWebStartupMessage(msg string) {
log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg)
if setting.CORSConfig.Enabled {
log.Info("CORS Service Enabled")
}
if setting.DefaultUILocation != time.Local {
log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
}
if setting.MailService != nil {
log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
}
}
func serveInstall(ctx *cli.Context) error {
func serveInstall(ctx *cli.Command) error {
showWebStartupMessage("Prepare to run install page")
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
@ -154,7 +167,7 @@ func serveInstall(ctx *cli.Context) error {
return nil
}
func serveInstalled(ctx *cli.Context) error {
func serveInstalled(ctx *cli.Command) error {
setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings()
setting.MustInstalled()
@ -205,6 +218,10 @@ func serveInstalled(ctx *cli.Context) error {
log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath)
}
// the AppDataTempDir is fully managed by us with a safe sub-path
// so it's safe to automatically remove the outdated files
setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour)
// Override the provided port number within the configuration
if ctx.IsSet("port") {
if err := setPort(ctx.String("port")); err != nil {
@ -212,6 +229,8 @@ func serveInstalled(ctx *cli.Context) error {
}
}
gtprof.EnableBuiltinTracer(util.Iif(setting.IsProd, 2000*time.Millisecond, 100*time.Millisecond))
// Set up Chi routes
webRoutes := hat_routers.NormalRoutes()
@ -231,7 +250,7 @@ func servePprof() {
finished()
}
func runWeb(ctx *cli.Context) error {
func runWeb(ctx context.Context, cmd *cli.Command) error {
defer func() {
if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
@ -287,6 +306,10 @@ func runWeb(ctx *cli.Context) error {
log.Info("Nacos: Register Gitea Web Finished")
}
if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid {
return fmt.Errorf("unknown command: %s", subCmdName)
}
managerCtx, cancel := context.WithCancel(context.Background())
graceful.InitManager(managerCtx)
defer cancel()
@ -298,12 +321,12 @@ func runWeb(ctx *cli.Context) error {
}
// Set pid file setting
if ctx.IsSet("pid") {
createPIDFile(ctx.String("pid"))
if cmd.IsSet("pid") {
createPIDFile(cmd.String("pid"))
}
if !setting.InstallLock {
if err := serveInstall(ctx); err != nil {
if err := serveInstall(cmd); err != nil {
return err
}
} else {
@ -314,7 +337,7 @@ func runWeb(ctx *cli.Context) error {
go servePprof()
}
return serveInstalled(ctx)
return serveInstalled(cmd)
}
func setPort(port string) error {

431
go.mod
View File

@ -1,284 +1,329 @@
module code.gitlink.org.cn/Gitlink/gitea_hat.git
go 1.21
toolchain go1.21.4
go 1.26.3
require (
code.gitea.io/gitea v1.21.0
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384
github.com/NYTimes/gziphandler v1.1.1
github.com/caddyserver/certmagic v0.19.2
github.com/felixge/fgprof v0.9.3
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1
code.gitea.io/gitea v1.26.2
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e
github.com/caddyserver/certmagic v0.25.3
github.com/felixge/fgprof v0.9.5
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2
github.com/gobwas/glob v0.2.3
github.com/json-iterator/go v1.1.12
github.com/klauspost/cpuid/v2 v2.2.6
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7
github.com/nektos/act v0.2.52
github.com/prometheus/client_golang v1.17.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.6
github.com/klauspost/cpuid/v2 v2.3.0
github.com/mattn/go-isatty v0.0.22
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5
github.com/nektos/act v0.2.63
github.com/prometheus/client_golang v1.23.2
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/urfave/cli/v2 v2.25.7
golang.org/x/net v0.23.0
golang.org/x/text v0.14.0
github.com/unknwon/com v1.0.1
github.com/urfave/cli-docs/v3 v3.1.0
github.com/urfave/cli/v3 v3.9.0
golang.org/x/net v0.55.0
golang.org/x/text v0.37.0
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.4
xorm.io/xorm v1.3.11
)
require (
code.gitea.io/actions-proto-go v0.3.1 // indirect
github.com/bufbuild/connect-go v1.10.0 // indirect
code.gitea.io/actions-proto-go v0.4.1 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 // indirect
github.com/emersion/go-imap v1.2.1 // indirect
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
github.com/go-testfixtures/testfixtures/v3 v3.9.0 // indirect
github.com/go-webauthn/webauthn v0.9.1 // indirect
github.com/google/go-tpm v0.9.0 // indirect
github.com/jhillyerd/enmime v1.0.1 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/go-webauthn/webauthn v0.17.4 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rhysd/actionlint v1.6.26 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/sync v0.5.0 // indirect
google.golang.org/grpc v1.59.0 // indirect
github.com/rhysd/actionlint v1.7.12 // indirect
github.com/stretchr/testify v1.11.1 // indirect
golang.org/x/sync v0.20.0 // indirect
google.golang.org/grpc v1.81.1 // indirect
)
require (
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
code.gitea.io/sdk/gitea v0.16.0 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
code.gitea.io/sdk/gitea v0.25.1 // indirect
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 // indirect
dario.cat/mergo v1.0.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
gitea.com/go-chi/cache v0.2.0 // indirect
gitea.com/go-chi/session v0.0.0-20230613035928-39541325faa3 // indirect
connectrpc.com/connect v1.20.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
gitea.com/go-chi/cache v0.2.1 // indirect
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 // indirect
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 // indirect
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/ClickHouse/ch-go v0.58.2 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.15.0 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/RoaringBitmap/roaring v1.6.0 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/alecthomas/chroma/v2 v2.11.1 // indirect
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
github.com/alibabacloud-go/tea v1.1.17 // indirect
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 // indirect
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/42wim/httpsig v1.2.4 // indirect
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.7.0 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.4.1 // indirect
github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.9.0 // indirect
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.1.3 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/darabonba-array v0.1.0 // indirect
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 // indirect
github.com/alibabacloud-go/darabonba-map v0.0.2 // indirect
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.1 // indirect
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 // indirect
github.com/alibabacloud-go/darabonba-string v1.0.2 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect
github.com/alibabacloud-go/kms-20160120/v3 v3.4.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.2 // indirect
github.com/alibabacloud-go/tea v1.5.0 // indirect
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 // indirect
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 // indirect
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.9 // indirect
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5 // indirect
github.com/aliyun/credentials-go v1.4.12 // indirect
github.com/andybalholm/brotli v1.2.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.41.11 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.27 // indirect
github.com/aws/aws-sdk-go-v2/service/codecommit v1.34.2 // indirect
github.com/aws/smithy-go v1.27.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.11.0 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blevesearch/bleve/v2 v2.3.10 // indirect
github.com/blevesearch/bleve_index_api v1.1.3 // indirect
github.com/blevesearch/geo v0.1.18 // indirect
github.com/blevesearch/bleve/v2 v2.6.0 // indirect
github.com/blevesearch/bleve_index_api v1.3.12 // indirect
github.com/blevesearch/geo v0.2.5 // indirect
github.com/blevesearch/go-faiss v1.1.3 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.2.3 // indirect
github.com/blevesearch/mmap-go v1.2.0 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.0.10 // indirect
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
github.com/blevesearch/zapx/v15 v15.3.13 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/buildkite/terminal-to-html/v3 v3.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/blevesearch/vellum v1.2.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.3 // indirect
github.com/blevesearch/zapx/v12 v12.4.3 // indirect
github.com/blevesearch/zapx/v13 v13.4.3 // indirect
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
github.com/blevesearch/zapx/v17 v17.1.6 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.4 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/bohde/codel v0.2.0 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c // indirect
github.com/buger/jsonparser v1.2.0 // indirect
github.com/buildkite/terminal-to-html/v3 v3.16.8 // indirect
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 // indirect
github.com/chi-middleware/proxy v1.1.1 // indirect
github.com/cloudflare/circl v1.3.6 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.2.1 // indirect
github.com/couchbase/goutils v0.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/couchbase/gomemcached v0.3.4 // indirect
github.com/couchbase/goutils v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/denisenkom/go-mssqldb v0.12.3 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/djherbis/buffer v1.2.0 // indirect
github.com/djherbis/nio/v3 v3.0.1 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/editorconfig/editorconfig-core-go/v2 v2.6.0 // indirect
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/ethantkoenig/rupture v1.0.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
github.com/gliderlabs/ssh v0.3.5 // indirect
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 // indirect
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-co-op/gocron v1.36.0 // indirect
github.com/go-enry/go-enry/v2 v2.8.6 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/gliderlabs/ssh v0.3.8 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-co-op/gocron/v2 v2.21.2 // indirect
github.com/go-enry/go-enry/v2 v2.9.6 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.0 // indirect
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.10.0 // indirect
github.com/go-ldap/ldap/v3 v3.4.6 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-webauthn/x v0.1.4 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-git/go-git/v5 v5.19.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-ldap/ldap/v3 v3.4.13 // indirect
github.com/go-redsync/redsync/v4 v4.16.0 // indirect
github.com/go-sql-driver/mysql v1.10.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-webauthn/x v0.2.6 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-github/v53 v53.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/go-github/v84 v84.0.0 // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/licenseclassifier/v2 v2.0.0 // indirect
github.com/google/pprof v0.0.0-20260604005048-7023385849c0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/feeds v1.1.2 // indirect
github.com/gorilla/feeds v1.2.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-version v1.9.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inbucket/html2text v1.0.0 // indirect
github.com/jaytaylor/html2text v0.0.0-20260303211410-1a4bdc82ecec // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jhillyerd/enmime/v2 v2.4.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 // indirect
github.com/klauspost/compress v1.17.3 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/lib/pq v1.12.3 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/mailru/easyjson v0.9.2 // indirect
github.com/markbates/going v1.0.3 // indirect
github.com/markbates/goth v1.78.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.18 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/meilisearch/meilisearch-go v0.26.0 // indirect
github.com/mholt/acmez v1.2.0 // indirect
github.com/mholt/archiver/v3 v3.5.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/miekg/dns v1.1.57 // indirect
github.com/markbates/goth v1.82.0 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/mattn/go-shellwords v1.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.44 // indirect
github.com/meilisearch/meilisearch-go v0.36.3 // indirect
github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/mholt/archives v0.1.5 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/microsoft/go-mssqldb v1.10.0 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.64 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/minio/minio-go/v7 v7.1.0 // indirect
github.com/minio/minlz v1.1.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/msteinert/pam v1.2.0 // indirect
github.com/niklasfasching/go-org v1.7.0 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/msteinert/pam/v2 v2.1.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/niklasfasching/go-org v1.9.1 // indirect
github.com/nwaples/rardecode/v2 v2.2.3 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.3.0 // indirect
github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/olivere/elastic/v7 v7.0.32 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/paulmach/orb v0.10.0 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/orcaman/concurrent-map v1.0.0 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.27 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/pquerna/otp v1.5.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.68.1 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/quasoft/websspi v1.1.2 // indirect
github.com/redis/go-redis/v9 v9.3.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/redis/go-redis/v9 v9.20.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sassoftware/go-rpmutils v0.2.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sassoftware/go-rpmutils v0.4.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stangelandcl/ppmd v0.1.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tstranex/u2f v1.0.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/wneessen/go-mail v0.7.3 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/go-gitlab v0.94.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yohcop/openid-go v1.0.1 // indirect
github.com/yuin/goldmark v1.6.0 // indirect
github.com/yuin/goldmark v1.8.2 // indirect
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.etcd.io/bbolt v1.3.8 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
gitlab.com/gitlab-org/api/client-go v1.46.0 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.15.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
go.uber.org/zap v1.28.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/image v0.41.0 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.45.0 // indirect
golang.org/x/tools/godoc v0.1.0-deprecated // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mvdan.cc/xurls/v2 v2.5.0 // indirect
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 // indirect
mvdan.cc/xurls/v2 v2.6.0 // indirect
pgregory.net/rapid v1.2.0 // indirect
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab // indirect
)
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/blevesearch/zapx/v15 v15.3.6 => github.com/zeripath/zapx/v15 v15.3.6-alignment-fix
replace github.com/nektos/act => gitea.com/gitea/act v0.243.4
replace github.com/nektos/act => gitea.com/gitea/act v0.261.10 // gitea maintains its own package
exclude github.com/gofrs/uuid v3.2.0+incompatible

1658
go.sum

File diff suppressed because it is too large Load Diff

70
main.go
View File

@ -1,57 +1,57 @@
package main
import (
"os"
"runtime"
"strings"
"time"
"os"
"runtime"
"strings"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
hat_cmd "code.gitlink.org.cn/Gitlink/gitea_hat.git/cmd"
"code.gitea.io/gitea/modules/setting"
hat_cmd "code.gitlink.org.cn/Gitlink/gitea_hat.git/cmd"
// register supported doc types
_ "code.gitea.io/gitea/modules/markup/asciicast"
_ "code.gitea.io/gitea/modules/markup/console"
_ "code.gitea.io/gitea/modules/markup/csv"
_ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode"
// register supported doc types
_ "code.gitea.io/gitea/modules/markup/asciicast"
_ "code.gitea.io/gitea/modules/markup/console"
_ "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/v2"
"github.com/urfave/cli/v3"
)
var (
Version = "v2.7, by v1.21.0 "
Tags = ""
MakeVersion = ""
Version = "v3.1.2, by v1.26.2, 20260729 "
Tags = ""
MakeVersion = ""
)
func init() {
setting.AppVer = Version
setting.AppBuiltWith = formatBuiltWith()
setting.AppStartTime = time.Now().UTC()
// Grab the original help templates
setting.AppVer = Version
setting.AppBuiltWith = formatBuiltWith()
setting.AppStartTime = time.Now().UTC()
// Grab the original help templates
}
func main() {
cli.OsExiter = func(code int) {
cli.OsExiter = func(code int) {
log.GetManager().Close()
os.Exit(code)
}
app := hat_cmd.NewMainApp(Version, formatBuiltWith())
_ = hat_cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
log.GetManager().Close()
os.Exit(code)
}
app := hat_cmd.NewMainApp(Version, formatBuiltWith())
_ = hat_cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
log.GetManager().Close()
}
func formatBuiltWith() string {
version := runtime.Version()
if len(MakeVersion) > 0 {
version = MakeVersion + ", " + runtime.Version()
}
if len(Tags) == 0 {
return " built with " + version
}
version := runtime.Version()
if len(MakeVersion) > 0 {
version = MakeVersion + ", " + runtime.Version()
}
if len(Tags) == 0 {
return " built with " + version
}
return " built with " + version + " : " + strings.ReplaceAll(Tags, " ", ", ")
return " built with " + version + " : " + strings.ReplaceAll(Tags, " ", ", ")
}

View File

@ -6,9 +6,80 @@ import (
gitea_actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
gitea_repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/actions/jobparser"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
func InsertRunSingleWorkflow(ctx context.Context, run *gitea_actions_model.ActionRun, jobs []*jobparser.SingleWorkflow) error {
return db.WithTx(ctx, func(ctx context.Context) error {
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
if err != nil {
return err
}
run.Index = index
run.Title = util.EllipsisDisplayString(run.Title, 255)
if err := db.Insert(ctx, run); err != nil {
return err
}
if run.Repo == nil {
repo, err := gitea_repo_model.GetRepositoryByID(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err
}
runJobs := make([]*gitea_actions_model.ActionRunJob, 0, len(jobs))
var hasWaiting bool
for _, v := range jobs {
id, job := v.Job()
needs := job.Needs()
if err := v.SetJob(id, job.EraseNeeds()); err != nil {
return err
}
payload, _ := v.Marshal()
status := gitea_actions_model.StatusWaiting
if len(needs) > 0 || run.NeedApproval {
status = gitea_actions_model.StatusBlocked
} else {
hasWaiting = true
}
job.Name = util.EllipsisDisplayString(job.Name, 255)
runJobs = append(runJobs, &gitea_actions_model.ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
IsForkPullRequest: run.IsForkPullRequest,
Name: job.Name,
WorkflowPayload: payload,
JobID: id,
Needs: needs,
RunsOn: job.RunsOn(),
Status: status,
})
}
if err := db.Insert(ctx, runJobs); err != nil {
return err
}
// if there is a job in the waiting status, increase tasks version.
if hasWaiting {
if err := gitea_actions_model.IncreaseTaskVersion(ctx, run.OwnerID, run.RepoID); err != nil {
return err
}
}
return nil
})
}
func InsertRun(ctx context.Context, run *gitea_actions_model.ActionRun, jobs []*gitea_actions_model.ActionRunJob) error {
ctx, commiter, err := db.TxContext(ctx)
if err != nil {

View File

@ -1,6 +1,7 @@
package activities
import (
"context"
"fmt"
"time"
@ -23,7 +24,7 @@ type Contributor struct {
Contributions int64 `json:"contributions"`
}
func GetContributors(repoID, userID int64) ([]Contributor, error) {
func GetContributors(ctx context.Context, repoID, userID int64) ([]Contributor, error) {
var result = make([]Contributor, 0)
sql :=
`select a.act_user_id as id ,
@ -35,15 +36,15 @@ func GetContributors(repoID, userID int64) ([]Contributor, error) {
where repo_id=? and user_id=?
group by repo_id,act_user_id `
err := db.GetEngine(db.DefaultContext).SQL(sql, repoID, userID).Find(&result)
err := db.GetEngine(ctx).SQL(sql, repoID, userID).Find(&result)
return result, err
}
func activityQueryCondition(opts gitea_activities_models.GetFeedsOptions) (builder.Cond, error) {
func activityQueryCondition(ctx context.Context,opts gitea_activities_models.GetFeedsOptions) (builder.Cond, error) {
cond := builder.NewCond()
if opts.RequestedTeam != nil && opts.RequestedUser == nil {
org, err := gitea_user_model.GetUserByID(db.DefaultContext, opts.RequestedTeam.OrgID)
org, err := gitea_user_model.GetUserByID(ctx, opts.RequestedTeam.OrgID)
if err != nil {
return nil, err
}
@ -76,8 +77,8 @@ func activityQueryCondition(opts gitea_activities_models.GetFeedsOptions) (build
}
if opts.RequestedTeam != nil {
env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam)
teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos)
env := gitea_repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
teamRepoIDs, err := env.RepoIDs(ctx)
if err != nil {
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
}

View File

@ -1,6 +1,8 @@
package activities
import (
"context"
gitea_activities_models "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
@ -14,7 +16,7 @@ type UserHeatmapData struct {
Contributions int64 `json:"contributions"`
}
func GetUserHeatmapDataByTimestampRange(user *gitea_user_model.User, team *organization.Team, doer *gitea_user_model.User, startAt, endAt timeutil.TimeStamp) ([]*UserHeatmapData, error) {
func GetUserHeatmapDataByTimestampRange(ctx context.Context, user *gitea_user_model.User, team *organization.Team, doer *gitea_user_model.User, startAt, endAt timeutil.TimeStamp) ([]*UserHeatmapData, error) {
hdata := make([]*UserHeatmapData, 0)
if !gitea_activities_models.ActivityReadable(user, doer) {
return hdata, nil
@ -30,7 +32,7 @@ func GetUserHeatmapDataByTimestampRange(user *gitea_user_model.User, team *organ
groupByName = groupBy
}
cond, err := activityQueryCondition(gitea_activities_models.GetFeedsOptions{
cond, err := activityQueryCondition(ctx, gitea_activities_models.GetFeedsOptions{
RequestedUser: user,
RequestedTeam: team,
Actor: doer,
@ -43,7 +45,7 @@ func GetUserHeatmapDataByTimestampRange(user *gitea_user_model.User, team *organ
return nil, err
}
return hdata, db.GetEngine(db.DefaultContext).
return hdata, db.GetEngine(ctx).
Select(groupBy+" AS timestamp, count(user_id) as contributions").
Table("action").
Where(cond).

View File

@ -23,9 +23,9 @@ type PullRequest struct {
LatestPullRequestVersion *pull.PullRequestVersion
}
func GetPullRequestIDsByIssueUpdatedUnix() ([]int64, error) {
func GetPullRequestIDsByIssueUpdatedUnix(ctx context.Context) ([]int64, error) {
prs := make([]int64, 0, 10)
return prs, db.GetEngine(db.DefaultContext).Table("pull_request").
return prs, db.GetEngine(ctx).Table("pull_request").
Where("issue.updated_unix>?", time.Now().Add(-time.Minute*2).Unix()).
Join("INNER", "issue", "issue.id=pull_request.issue_id").
Cols("pull_request.id").

View File

@ -30,7 +30,7 @@ type PullRequestVersion struct {
}
func listPullRequestVersionStatement(ctx context.Context, prID int64, opts *db.ListOptions) (*xorm.Session, error) {
sess := db.GetEngine(db.DefaultContext).Where("pull_request_version.pull_id=?", prID)
sess := db.GetEngine(ctx).Where("pull_request_version.pull_id=?", prID)
sess.Join("INNER", "repository", "pull_request_version.repo_id = repository.id")
return sess, nil

View File

@ -1,13 +1,15 @@
package webhook
import (
"context"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/webhook"
)
func GetHookTasksByHookID(hookID int64, page, limit int) ([]*webhook.HookTask, error) {
func GetHookTasksByHookID(ctx context.Context, hookID int64, page, limit int) ([]*webhook.HookTask, error) {
tasks := make([]*webhook.HookTask, 0, limit)
return tasks, db.GetEngine(db.DefaultContext).
return tasks, db.GetEngine(ctx).
Limit(limit, (page-1)*limit).
Where("hook_id=?", hookID).
Desc("id").

View File

@ -1,6 +1,8 @@
package webhook
import (
"context"
"code.gitea.io/gitea/models/db"
gitea_webhook_model "code.gitea.io/gitea/models/webhook"
)
@ -9,9 +11,9 @@ type Webhook struct {
gitea_webhook_model.Webhook
}
func GetWebhook(bean *Webhook) (*Webhook, error) {
func GetWebhook(ctx context.Context,bean *Webhook) (*Webhook, error) {
webhook := bean.Webhook
has, err := db.GetEngine(db.DefaultContext).Get(&webhook)
has, err := db.GetEngine(ctx).Get(&webhook)
if err != nil {
return nil, err
} else if !has {

View File

@ -3,21 +3,24 @@ package convert
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/repo"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
gitea_convert "code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/gitdiff"
api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)
func ToTag(repo *repo.Repository, gitRepo *git.Repository, t *git.Tag) (tag *api.Tag, err error) {
tarballURL := repo.HTMLURL() + "/archive/" + url.PathEscape(t.Name+".tar.gz")
zipballURL := repo.HTMLURL() + "/archive/" + url.PathEscape(t.Name+".zip")
tagCommit, err := ToTagCommit(repo, gitRepo, t)
if err != nil {
return &api.Tag{}, err
@ -28,13 +31,13 @@ func ToTag(repo *repo.Repository, gitRepo *git.Repository, t *git.Tag) (tag *api
ID: t.ID.String(),
Commit: tagCommit,
Tagger: gitea_convert.ToCommitUser(t.Tagger),
ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),
TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"),
ZipballURL: zipballURL,
TarballURL: tarballURL,
}, nil
}
func ToTagCommit(repo *repo.Repository, gitRepo *git.Repository, t *git.Tag) (result *api.TagCommit, err error) {
commit, err := t.Commit(gitRepo)
commit, err := gitRepo.GetTagCommit(t.Name)
if err != nil {
return &api.TagCommit{}, nil
}
@ -101,7 +104,6 @@ func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit stri
file := &api.ChangedFile{
Filename: f.GetDiffFileName(),
OldName: f.OldName,
Index: f.Index,
Type: f.Type,
IsBin: f.IsBin,
IsCreated: f.IsCreated,

View File

@ -2,10 +2,12 @@ package convert
import (
"context"
"strings"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
gitea_convert "code.gitea.io/gitea/services/convert"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)
@ -16,7 +18,7 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
return nil, err
}
branchName, err := commit.GetBranchName()
branchName, err := GetBranchName(ctx, gitRepo, commit)
if err != nil {
return nil, err
}
@ -26,3 +28,23 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
Branch: branchName,
}, nil
}
func GetBranchName(ctx context.Context, gitRepo *git.Repository, c *git.Commit) (string, error) {
cmd := gitcmd.NewCommand("name-rev")
if git.DefaultFeatures().CheckVersionAtLeast("2.13.0") {
cmd.AddArguments("--exclude", "refs/tags/*")
}
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
data, _, err := cmd.WithDir(gitRepo.Path).RunStdString(ctx)
if err != nil {
// handle special case where git can not describe commit
if strings.Contains(err.Error(), "cannot describe") {
return "", nil
}
return "", err
}
// name-rev commitID output will be "master" or "master~12"
return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil
}

View File

@ -1,19 +1,23 @@
package convert
import (
"context"
"errors"
"strings"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
gitea_convert "code.gitea.io/gitea/services/convert"
git_service "code.gitea.io/gitea/services/git"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)
func ToAPIPullRequest(ctx context.Context, gitRepo *git.Repository, pr *issues.PullRequest, doer *user.User) (*hat_api.PullRequest, error) {
func ToAPIPullRequest(ctx *context.APIContext, gitRepo *git.Repository, pr *issues.PullRequest, doer *user.User) (*hat_api.PullRequest, error) {
giteaAPIPullrequest := gitea_convert.ToAPIPullRequest(ctx, pr, doer)
hatAPIPullrequest := &hat_api.PullRequest{
ID: giteaAPIPullrequest.ID,
@ -51,20 +55,20 @@ func ToAPIPullRequest(ctx context.Context, gitRepo *git.Repository, pr *issues.P
return hatAPIPullrequest, errors.New("pullrequest's issue is blank.")
}
var compareInfo *git.CompareInfo
var compareInfo *git_service.CompareInfo
var err error
if pr.HasMerged {
var baseCommit string
if pr.MergeBase == "" {
var commitSHA, parentCommit string
// If there is a head or a patch file, and it is readable, grab info
commitSHA, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
commitSHA, err := gitRepo.GetRefCommitID(pr.GetGitHeadRefName())
if err != nil {
// Head File does not exist, try the patch
commitSHA, err = gitRepo.ReadPatchCommit(pr.Index)
if err == nil {
// Recreate pull head in files for next time
if err := gitRepo.SetReference(pr.GetGitRefName(), commitSHA); err != nil {
if err := gitrepo.UpdateRef(ctx, ctx.Repo.Repository, pr.GetGitHeadRefName(), commitSHA); err != nil {
log.Error("Could not write head file", err)
}
} else {
@ -74,7 +78,7 @@ func ToAPIPullRequest(ctx context.Context, gitRepo *git.Repository, pr *issues.P
}
if commitSHA != "" {
// Get immediate parent of the first commit in the patch, grab history back
parentCommit, _, err = git.NewCommand(ctx, "rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).RunStdString(&git.RunOpts{Dir: gitRepo.Path})
parentCommit, _, err = gitcmd.NewCommand("rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).WithDir(ctx.Repo.GitRepo.Path).RunStdString(ctx)
if err == nil {
parentCommit = strings.TrimSpace(parentCommit)
}
@ -89,9 +93,9 @@ func ToAPIPullRequest(ctx context.Context, gitRepo *git.Repository, pr *issues.P
} else {
baseCommit = pr.MergeBase
}
compareInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, baseCommit, pr.GetGitRefName(), false, false)
compareInfo, err = git_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, gitRepo, git.RefNameFromCommit(baseCommit), git.RefName(pr.GetGitHeadRefName()), false, false)
} else {
compareInfo, err = gitRepo.GetCompareInfo(gitRepo.Path, pr.MergeBase, pr.GetGitRefName(), false, false)
compareInfo, err = git_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, gitRepo, git.RefName(pr.MergeBase), git.RefName(pr.GetGitHeadRefName()), false, false)
}
if err != nil {

View File

@ -2,8 +2,8 @@ package convert
import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
gitea_api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context"
gitea_convert "code.gitea.io/gitea/services/convert"
api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)

View File

@ -1,16 +1,2 @@
package convert
import "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
func ToBranchType(index int) git.BranchKind {
switch index {
case 1:
return git.DefaultBranch
case 2:
return git.ProtectedBranch
case 3:
return git.OtherBranch
default:
return git.None
}
}

View File

@ -12,6 +12,7 @@ import (
"strings"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/process"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)
@ -133,7 +134,7 @@ func (r *BlameReader) NextApiPart(repo *gitea_git.Repository) (*hat_api.BlamePar
func (r *BlameReader) Close() error {
defer r.finished() // Only remove the process from the process table when the underlying command is closed
r.cancel() // However, first cancel our own context early
// r.cancel() // However, first cancel our own context early
_ = r.output.Close()
@ -145,12 +146,12 @@ func (r *BlameReader) Close() error {
}
func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) {
return createBlameReader(ctx, repoPath, gitea_git.GitExecutable, "blame", commitID, "--porcelain", "--", file)
return createBlameReader(ctx, repoPath, gitcmd.GitExecutable, "blame", commitID, "--porcelain", "--", file)
}
func createBlameReader(ctx context.Context, dir string, command ...string) (*BlameReader, error) {
// Here we use the provided context - this should be tied to the request performing the blame so that it does not hang around.
ctx, cancel, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("GetBlame [repo_path: %s]", dir))
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("GetBlame [repo_path: %s]", dir))
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Dir = dir
@ -172,10 +173,10 @@ func createBlameReader(ctx context.Context, dir string, command ...string) (*Bla
reader := bufio.NewReader(stdout)
return &BlameReader{
cmd: cmd,
output: stdout,
reader: reader,
cancel: cancel,
cmd: cmd,
output: stdout,
reader: reader,
// cancel: cancel,
finished: finished,
}, nil
}

View File

@ -1,83 +0,0 @@
package git
import (
"context"
"strings"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
)
type Branch struct {
*api.Branch
CommitID string `json:"commit_id"` // add configure
CommitTime string `json:"commit_time"` // add configure
DefaultBranch string `json:"default_branch"`
BranchKind int `json:"branch_kind"`
}
type BranchKind int
const (
None BranchKind = iota
DefaultBranch
ProtectedBranch
OtherBranch
)
func (bk BranchKind) Name() string {
return strings.ToLower(bk.Title())
}
func (bk BranchKind) Title() string {
switch bk {
case DefaultBranch:
return "default"
case ProtectedBranch:
return "protected"
case OtherBranch:
return "other"
}
return ""
}
type BranchesSlice struct {
BranchName string `json:"branch_name"`
// BranchKind int `json:"branch_kind"`
Branches []*Branch `json:"branches"`
}
// sort by branchkind
type SortBranch []*Branch
func (s SortBranch) Len() int { return len(s) }
func (s SortBranch) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s SortBranch) Less(i, j int) bool { return s[i].BranchKind < s[j].BranchKind }
// sort by CommiTime of the branch
type SortBranchTime []*Branch
func (s SortBranchTime) Len() int { return len(s) }
func (s SortBranchTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s SortBranchTime) Less(i, j int) bool { return s[i].CommitTime > s[j].CommitTime }
func GetBranchesByPathNoLimit(ctx context.Context, path string) ([]*git.Branch, int, error) {
gitRepo, err := git.OpenRepository(ctx, path)
if err != nil {
return nil, 0, err
}
defer gitRepo.Close()
brs, countAll, err := gitRepo.GetBranchNames(0, 0)
if err != nil {
return nil, 0, err
}
branches := make([]*git.Branch, len(brs))
for i := range brs {
branches[i] = &git.Branch{
Path: gitRepo.Path,
Name: brs[i],
}
}
return branches, countAll, nil
}

View File

@ -1,124 +0,0 @@
package git
import (
"bufio"
"context"
"fmt"
"io"
"strings"
gitea_git "code.gitea.io/gitea/modules/git"
)
func GetSearchBranches(ctx context.Context, repo *gitea_git.Repository, search string, skip, limit int) ([]*gitea_git.Branch, int, error) {
brs, countAll, err := callShowSearchRef(ctx, repo.Path, gitea_git.BranchPrefix, "--heads", search, skip, limit)
if err != nil {
return nil, 0, err
}
branches := make([]*gitea_git.Branch, len(brs))
for i := range brs {
branches[i] = &gitea_git.Branch{
Name: brs[i],
Path: repo.Path,
}
}
return branches, countAll, nil
}
func callShowSearchRef(ctx context.Context, repoPath, prefix, arg, search string, skip, limit int) (branchNames []string, countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
go func() {
stderrBuilder := &strings.Builder{}
err := gitea_git.NewCommand(ctx, "show-ref").AddDynamicArguments(arg).
Run(&gitea_git.RunOpts{
Dir: repoPath,
Stdout: stdoutWriter,
Stderr: stderrBuilder,
})
if err != nil {
if stderrBuilder.Len() == 0 {
_ = stdoutWriter.Close()
return
}
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
} else {
_ = stdoutWriter.Close()
}
}()
i := 0
bufReader := bufio.NewReader(stdoutReader)
for i < skip {
line, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return branchNames, i, nil
}
if err != nil {
return nil, 0, err
}
branchName := strings.TrimPrefix(strings.Split(string(line), " ")[1], prefix)
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
isSeached := strings.Contains(branchName, search)
if !isPrefix && isSeached {
i++
}
}
for limit == 0 || i < skip+limit {
branchName, err := bufReader.ReadString('\n')
if err == io.EOF {
// This shouldn't happen... but we'll tolerate it for the sake of peace
return branchNames, i, nil
}
if err != nil {
return nil, i, err
}
branchName = strings.TrimPrefix(strings.Split(string(branchName), " ")[1], prefix)
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
isSeached := strings.Contains(branchName, search)
if isSeached {
i++
branchNames = append(branchNames, branchName)
}
}
// count all refs
for limit != 0 {
line, isPrefix, err := bufReader.ReadLine()
if err == io.EOF {
return branchNames, i, nil
}
if err != nil {
return nil, 0, err
}
branchName := strings.TrimPrefix(strings.Split(string(line), " ")[1], prefix)
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
isSeached := strings.Contains(branchName, search)
if !isPrefix && isSeached {
i++
}
}
return branchNames, i, nil
}
func ConcatenateError(err error, stderr string) error {
if len(stderr) == 0 {
return err
}
return fmt.Errorf("%w - %s", err, stderr)
}

View File

@ -2,19 +2,20 @@ package git
import (
"bytes"
"encoding/hex"
"fmt"
"errors"
"io"
"strconv"
"strings"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
const prettyLogFormat = `--pretty=format:%H`
func GetFirstAndLastCommitByPath(repo *gitea_git.Repository, revision, relpath string) (*gitea_git.Commit, *gitea_git.Commit, error) {
stdout, _, runErr := gitea_git.NewCommand(repo.Ctx, "log").AddDynamicArguments(revision, prettyLogFormat, "--"+relpath).RunStdBytes(&gitea_git.RunOpts{Dir: repo.Path})
stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(revision, prettyLogFormat, "--"+relpath).WithDir(repo.Path).RunStdBytes(repo.Ctx)
if runErr != nil {
return nil, nil, runErr
}
@ -41,72 +42,126 @@ func GetFirstAndLastCommitByPath(repo *gitea_git.Repository, revision, relpath s
func AllCommitsByFileAndRange(repo *gitea_git.Repository, opts gitea_git.CommitsByFileAndRangeOptions, keyword string, pageSize int) ([]*gitea_git.Commit, error) {
skip := (opts.Page - 1) * pageSize
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
go func() {
stderr := strings.Builder{}
gitCmd := gitea_git.NewCommand(repo.Ctx, "rev-list").
AddOptionFormat("--max-count=%d", pageSize*opts.Page).
AddOptionFormat("--skip=%d", skip).
AddOptionFormat("--grep=%s", keyword)
gitCmd.AddDynamicArguments(opts.Revision)
gitCmd := gitcmd.NewCommand("rev-list").
AddOptionFormat("--max-count=%d", pageSize*opts.Page).
AddOptionFormat("--skip=%d", skip).
AddOptionFormat("--grep=%s", keyword)
gitCmd.AddDynamicArguments(opts.Revision)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
if opts.Since != "" {
gitCmd.AddOptionFormat("--since=%s", opts.Since)
}
if opts.Until != "" {
gitCmd.AddOptionFormat("--until=%s", opts.Until)
}
gitCmd.AddDashesAndList(opts.File)
gitCmd.AddArguments("--all")
// gitCmd.AddDashesAndList(opts.File)
err := gitCmd.Run(&gitea_git.RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
} else {
_ = stdoutWriter.Close()
}
}()
commits := []*gitea_git.Commit{}
shaline := [41]byte{}
var sha1 gitea_git.SHA1
for {
n, err := io.ReadFull(stdoutReader, shaline[:])
if err != nil || n < 40 {
if err == io.EOF {
err = nil
var commits []*gitea_git.Commit
stdoutReader, stdoutReaderClose := gitCmd.MakeStdoutPipe()
defer stdoutReaderClose()
err := gitCmd.WithDir(repo.Path).
WithPipelineFunc(func(context gitcmd.Context) error {
objectFormat, err := repo.GetObjectFormat()
if err != nil {
return err
}
return commits, err
}
n, err = hex.Decode(sha1[:], shaline[0:40])
if n != 20 {
err = fmt.Errorf("invalid sha %q", string(shaline[:40]))
length := objectFormat.FullLength()
shaline := make([]byte, length+1)
for {
n, err := io.ReadFull(stdoutReader, shaline)
if err != nil || n < length {
if err == io.EOF {
err = nil
}
return err
}
objectID, err := gitea_git.NewIDFromString(string(shaline[0:length]))
if err != nil {
return err
}
commit, err := getCommit(repo, objectID)
if err != nil {
return err
}
commits = append(commits, commit)
}
}).
RunWithStderr(repo.Ctx)
return commits, err
}
func getCommit(repo *gitea_git.Repository, id gitea_git.ObjectID) (*gitea_git.Commit, error) {
batch, cancel, err := repo.CatFileBatch(repo.Ctx)
if err != nil {
return nil, err
}
defer cancel()
return getCommitFromBatchReader(repo, batch, id)
}
func getCommitFromBatchReader(repo *gitea_git.Repository, batch gitea_git.CatFileBatch, id gitea_git.ObjectID) (*gitea_git.Commit, error) {
info, rd, err := batch.QueryContent(id.String())
if err != nil {
if errors.Is(err, io.EOF) || gitea_git.IsErrNotExist(err) {
return nil, gitea_git.ErrNotExist{ID: id.String()}
}
return nil, err
}
switch info.Type {
case "missing":
return nil, gitea_git.ErrNotExist{ID: id.String()}
case "tag":
// then we need to parse the tag
// and load the commit
data, err := io.ReadAll(io.LimitReader(rd, info.Size))
if err != nil {
return nil, err
}
commit, err := repo.GetCommit(sha1.String())
_, err = rd.Discard(1)
if err != nil {
return nil, err
}
commits = append(commits, commit)
tag, err := parseTagData(id.Type(), data)
if err != nil {
return nil, err
}
return getCommitFromBatchReader(repo, batch, tag.Object)
case "commit":
commit, err := gitea_git.CommitFromReader(repo, id, io.LimitReader(rd, info.Size))
if err != nil {
return nil, err
}
_, err = rd.Discard(1)
if err != nil {
return nil, err
}
return commit, nil
default:
log.Debug("Unknown cat-file object type: %s", info.Type)
if err := gitea_git.DiscardFull(rd, info.Size+1); err != nil {
return nil, err
}
return nil, gitea_git.ErrNotExist{
ID: id.String(),
}
}
}
func GetAllCommitsCount(repo *gitea_git.Repository, keyword string) (int64, error) {
cmd := gitea_git.NewCommand(repo.Ctx, "rev-list").
cmd := gitcmd.NewCommand("rev-list").
AddOptionFormat("--grep=%s", keyword)
cmd.AddArguments("--exclude=" + "refs/pull/" + "*")
cmd.AddArguments("--all", "--count")
stdout, _, err := cmd.RunStdString(&gitea_git.RunOpts{Dir: repo.Path})
stdout, _, err := cmd.WithDir(repo.Path).RunStdString(repo.Ctx)
if err != nil {
return 0, err
}

View File

@ -1,13 +1,16 @@
package git
import gitea_git "code.gitea.io/gitea/modules/git"
import (
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
func GetDiffFileOnlyName(repo *gitea_git.Repository, base, head string) (string, error) {
stdout, _, err := gitea_git.NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(base, head).RunStdString(&gitea_git.RunOpts{Dir: repo.Path})
stdout, _, err := gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(base, head).WithDir(repo.Path).RunStdString(repo.Ctx)
return stdout, err
}
func GetDiffStringByFilePath(repo *gitea_git.Repository, base, head, filepath string) (string, error) {
stdout, _, err := gitea_git.NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base, head, filepath).RunStdString(&gitea_git.RunOpts{Dir: repo.Path})
stdout, _, err := gitcmd.NewCommand("diff", "-p").AddDynamicArguments(base, head, filepath).WithDir(repo.Path).RunStdString(repo.Ctx)
return stdout, err
}

View File

@ -2,13 +2,12 @@ package git
import (
"bufio"
"context"
"fmt"
"os"
"strconv"
"strings"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
type RepoContributor struct {
@ -18,71 +17,6 @@ type RepoContributor struct {
ContributionPerc string `json:"contribution_perc"`
}
func GetRepoContributors(repo *gitea_git.Repository, page, pageSize int) (int, []*RepoContributor, error) {
var total, totalContributions, skip int
var contributors []*RepoContributor
skip = (page - 1) * pageSize
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return total, nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
cmd := gitea_git.NewCommand(repo.Ctx, "shortlog", "-sne", "--all")
stderr := new(strings.Builder)
err = cmd.Run(&gitea_git.RunOpts{
Env: []string{},
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
var ca int
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
commits := l[0:strings.Index(l, "\t")]
commitsInt, err := strconv.Atoi(commits)
if err != nil {
return err
}
name := l[strings.Index(l, "\t")+1 : strings.Index(l, " <")]
email := l[strings.Index(l, "<")+1 : strings.Index(l, ">")]
totalContributions += commitsInt
total++
if skip > 0 {
skip--
} else {
if ca < pageSize {
contributors = append(contributors, &RepoContributor{
Commits: commitsInt,
Name: name,
Email: email,
})
ca++
}
}
}
_ = stdoutReader.Close()
return nil
},
})
for _, cont := range contributors {
fperc := fmt.Sprintf("%.2f", float64(cont.Commits)*100/float64(totalContributions))
cont.ContributionPerc = fperc + "%"
}
return total, contributors, nil
}
func GetRepoContributorsNew(repo *gitea_git.Repository, page, pageSize int) (int, []*RepoContributor, error) {
var total, totalContributions, skip int
var contributors []*RepoContributor
@ -91,56 +25,45 @@ func GetRepoContributorsNew(repo *gitea_git.Repository, page, pageSize int) (int
skip = (page - 1) * pageSize
stdoutReader, stdoutWriter, err := os.Pipe()
cmd := gitcmd.NewCommand("shortlog", "-sne", "--all")
stdoutReader, stdoutClose := cmd.MakeStdoutPipe()
defer stdoutClose()
err := cmd.WithEnv([]string{}).WithDir(repo.Path).WithPipelineFunc(func(ctx gitcmd.Context) error {
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
commits := l[0:strings.Index(l, "\t")]
commitsInt, err := strconv.Atoi(commits)
if err != nil {
return err
}
name := l[strings.Index(l, "\t")+1 : strings.Index(l, " <")]
email := strings.ToLower(l[strings.Index(l, "<")+1 : strings.Index(l, ">")])
totalContributions += commitsInt
// committer is not system user
if existedContributorInfo, ok := contributorInfoHash[email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.Commits += commitsInt
} else {
var newContributor = &RepoContributor{
Commits: commitsInt,
Name: name,
Email: email,
}
total++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[email] = newContributor
}
}
return nil
}).Run(repo.Ctx)
if err != nil {
return total, nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
cmd := gitea_git.NewCommand(repo.Ctx, "shortlog", "-sne", "--all")
stderr := new(strings.Builder)
err = cmd.Run(&gitea_git.RunOpts{
Env: []string{},
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
commits := l[0:strings.Index(l, "\t")]
commitsInt, err := strconv.Atoi(commits)
if err != nil {
return err
}
name := l[strings.Index(l, "\t")+1 : strings.Index(l, " <")]
email := strings.ToLower(l[strings.Index(l, "<")+1 : strings.Index(l, ">")])
totalContributions += commitsInt
// committer is not system user
if existedContributorInfo, ok := contributorInfoHash[email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.Commits += commitsInt
} else {
var newContributor = &RepoContributor{
Commits: commitsInt,
Name: name,
Email: email,
}
total++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[email] = newContributor
}
}
_ = stdoutReader.Close()
return nil
},
})
var ca int
for _, cont := range contributorInfos {

View File

@ -0,0 +1,17 @@
package git
import "code.gitea.io/gitea/modules/git"
// GetRefType gets the type of the ref based on the string
func GetRefType(repo *git.Repository, ref string) git.ObjectType {
if repo.IsTagExist(ref) {
return git.ObjectTag
} else if repo.IsBranchExist(ref) {
return git.ObjectBranch
} else if repo.IsReferenceExist(ref) {
return git.ObjectCommit
} else if _, err := repo.GetBlob(ref); err == nil {
return git.ObjectBlob
}
return git.ObjectType("invalid")
}

View File

@ -2,9 +2,7 @@ package git
import (
"bufio"
"context"
"fmt"
"os"
"sort"
"strconv"
"strings"
@ -12,6 +10,7 @@ import (
"code.gitea.io/gitea/modules/container"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
type CodeActivityStats struct {
@ -36,7 +35,7 @@ type CodeActivityAuthor struct {
func GetCodeActivityStatsWithoutSince(repo *gitea_git.Repository, branch string) (*CodeActivityStats, error) {
stats := &CodeActivityStats{}
stdout, _, runErr := gitea_git.NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").RunStdString(&gitea_git.RunOpts{Dir: repo.Path})
stdout, _, runErr := gitcmd.NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").WithDir(repo.Path).RunStdString(repo.Ctx)
if runErr != nil {
return nil, runErr
}
@ -47,99 +46,85 @@ func GetCodeActivityStatsWithoutSince(repo *gitea_git.Repository, branch string)
}
stats.CommitCountInAllBranches = c
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
gitCmd := gitea_git.NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso")
gitCmd := gitcmd.NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso")
if len(branch) == 0 {
gitCmd.AddArguments("--branches=*")
} else {
gitCmd.AddArguments("--first-parent").AddDynamicArguments(branch)
}
stderr := new(strings.Builder)
err = gitCmd.Run(&gitea_git.RunOpts{
Env: []string{},
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
stats.CommitCount = 0
stats.Additions = 0
stats.Deletions = 0
authors := make(map[string]*CodeActivityAuthor)
files := make(container.Set[string])
var author string
var currentAuthor *CodeActivityAuthor
p := 0
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
stdoutReader, stdoutClose := gitCmd.MakeStdoutPipe()
defer stdoutClose()
err = gitCmd.WithEnv([]string{}).WithDir(repo.Path).WithPipelineFunc(func(ctx gitcmd.Context) error {
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
stats.CommitCount = 0
stats.Additions = 0
stats.Deletions = 0
authors := make(map[string]*CodeActivityAuthor)
files := make(container.Set[string])
var author string
var currentAuthor *CodeActivityAuthor
p := 0
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Separator
case 2: // Commit sha-1
stats.CommitCount++
case 3: // Author
author = l
case 4: // E-mail
email := strings.ToLower(l)
if _, ok := authors[email+author]; !ok {
authors[email+author] = &CodeActivityAuthor{Name: author, Email: email, Commits: 0}
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Separator
case 2: // Commit sha-1
stats.CommitCount++
case 3: // Author
author = l
case 4: // E-mail
email := strings.ToLower(l)
if _, ok := authors[email+author]; !ok {
authors[email+author] = &CodeActivityAuthor{Name: author, Email: email, Commits: 0}
}
authors[email+author].Commits++
currentAuthor = authors[email+author]
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
stats.Additions += c
currentAuthor.Additions += c
}
authors[email+author].Commits++
currentAuthor = authors[email+author]
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
stats.Additions += c
currentAuthor.Additions += c
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
stats.Deletions += c
currentAuthor.Deletions += c
}
}
files.Add(parts[2])
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
stats.Deletions += c
currentAuthor.Deletions += c
}
}
files.Add(parts[2])
}
}
a := make([]*CodeActivityAuthor, 0, len(authors))
for _, v := range authors {
a = append(a, v)
}
// Sort authors descending depending on commit count
sort.Slice(a, func(i, j int) bool {
return a[i].Commits > a[j].Commits
})
stats.AuthorCount = int64(len(authors))
stats.ChangedFiles = int64(len(files))
stats.Authors = a
_ = stdoutReader.Close()
return nil
},
})
}
a := make([]*CodeActivityAuthor, 0, len(authors))
for _, v := range authors {
a = append(a, v)
}
// Sort authors descending depending on commit count
sort.Slice(a, func(i, j int) bool {
return a[i].Commits > a[j].Commits
})
stats.AuthorCount = int64(len(authors))
stats.ChangedFiles = int64(len(files))
stats.Authors = a
_ = stdoutReader.Close()
return nil
}).Run(repo.Ctx)
if err != nil {
return nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s", err, stderr)
return nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w", err)
}
return stats, nil
@ -151,13 +136,13 @@ func GetPaginateCodeAuthors(repo *gitea_git.Repository, fromTime time.Time, bran
var authors []*CodeActivityAuthor
since := fromTime.Format(time.RFC3339)
authorCmd := gitea_git.NewCommand(repo.Ctx, "log", "--no-merges", "--format=%aN <%aE>", "--date=iso").AddDynamicArguments(fmt.Sprintf("--since='%s'", since))
authorCmd := gitcmd.NewCommand("log", "--no-merges", "--format=%aN <%aE>", "--date=iso").AddDynamicArguments(fmt.Sprintf("--since='%s'", since))
if len(branch) == 0 {
authorCmd.AddArguments("--branches=*")
} else {
authorCmd.AddArguments("--first-parent").AddDynamicArguments(branch)
}
authorStdout, _, runErr := authorCmd.RunStdString(&gitea_git.RunOpts{Dir: repo.Path})
authorStdout, _, runErr := authorCmd.WithDir(repo.Path).RunStdString(repo.Ctx)
if runErr != nil {
return total, nil, runErr
}
@ -193,84 +178,71 @@ func GetPaginateCodeAuthors(repo *gitea_git.Repository, fromTime time.Time, bran
return total, authors, nil
}
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return total, nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
gitCmd := gitea_git.NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddDynamicArguments(fmt.Sprintf("--author=%s", filterAuthor))
gitCmd := gitcmd.NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddDynamicArguments(fmt.Sprintf("--author=%s", filterAuthor))
if len(branch) == 0 {
gitCmd.AddArguments("--branches=*")
} else {
gitCmd.AddArguments("--first-parent").AddDynamicArguments(branch)
}
stderr := new(strings.Builder)
err = gitCmd.Run(&gitea_git.RunOpts{
Env: []string{},
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
authorsMap := make(map[string]*CodeActivityAuthor)
files := make(container.Set[string])
var author string
var currentAuthor *CodeActivityAuthor
p := 0
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Separator
case 2: // Commit sha-1
case 3: // Author
author = l
case 4: // E-mail
email := strings.ToLower(l)
if _, ok := authorsMap[email+author]; !ok {
authorsMap[email+author] = &CodeActivityAuthor{Name: author, Email: email, Commits: 0}
}
authorsMap[email+author].Commits++
currentAuthor = authorsMap[email+author]
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
currentAuthor.Additions += c
}
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
currentAuthor.Deletions += c
}
}
files.Add(parts[2])
}
}
}
for _, v := range authorsMap {
authors = append(authors, v)
}
stdoutReader, stdoutClose := gitCmd.MakeStdoutPipe()
defer stdoutClose()
_ = stdoutReader.Close()
return nil
},
})
stderr := new(strings.Builder)
err := gitCmd.WithEnv([]string{}).WithDir(repo.Path).WithPipelineFunc(func(ctx gitcmd.Context) error {
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
authorsMap := make(map[string]*CodeActivityAuthor)
files := make(container.Set[string])
var author string
var currentAuthor *CodeActivityAuthor
p := 0
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Separator
case 2: // Commit sha-1
case 3: // Author
author = l
case 4: // E-mail
email := strings.ToLower(l)
if _, ok := authorsMap[email+author]; !ok {
authorsMap[email+author] = &CodeActivityAuthor{Name: author, Email: email, Commits: 0}
}
authorsMap[email+author].Commits++
currentAuthor = authorsMap[email+author]
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
currentAuthor.Additions += c
}
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
currentAuthor.Deletions += c
}
}
files.Add(parts[2])
}
}
}
for _, v := range authorsMap {
authors = append(authors, v)
}
return nil
}).Run(repo.Ctx)
if err != nil {
return total, nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s", err, stderr)
}

46
modules/git/signature.go Normal file
View File

@ -0,0 +1,46 @@
package git
import (
"strconv"
"strings"
"time"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
)
// Helper to get a signature from the commit line, which looks like:
//
// full name <user@example.com> 1378823654 +0200
//
// Haven't found the official reference for the standard format yet.
// This function never fails, if the "line" can't be parsed, it returns a default Signature with "zero" time.
func parseSignatureFromCommitLine(line string) *gitea_git.Signature {
sig := &gitea_git.Signature{}
s1, sx, ok1 := strings.Cut(line, " <")
s2, s3, ok2 := strings.Cut(sx, "> ")
if !ok1 || !ok2 {
sig.Name = line
return sig
}
sig.Name, sig.Email = s1, s2
if strings.Count(s3, " ") == 1 {
ts, tz, _ := strings.Cut(s3, " ")
seconds, _ := strconv.ParseInt(ts, 10, 64)
if tzTime, err := time.Parse("-0700", tz); err == nil {
sig.When = time.Unix(seconds, 0).In(tzTime.Location())
}
} else {
// the old gitea code tried to parse the date in a few different formats, but it's not clear why.
// according to public document, only the standard format "timestamp timezone" could be found, so drop other formats.
log.Error("suspicious commit line format: %q", line)
for _, fmt := range []string{ /*"Mon Jan _2 15:04:05 2006 -0700"*/ } {
if t, err := time.Parse(fmt, s3); err == nil {
sig.When = t
break
}
}
}
return sig
}

79
modules/git/tag.go Normal file
View File

@ -0,0 +1,79 @@
package git
import (
"bytes"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
)
func parseTagData(objectFormat gitea_git.ObjectFormat, data []byte) (*gitea_git.Tag, error) {
tag := new(gitea_git.Tag)
tag.ID = objectFormat.EmptyObjectID()
tag.Object = objectFormat.EmptyObjectID()
tag.Tagger = &gitea_git.Signature{}
pos := 0
for {
eol := bytes.IndexByte(data[pos:], '\n')
if eol == -1 {
break // shouldn't happen, but could just tolerate it
}
if eol == 0 {
pos++
break // end of headers
}
line := data[pos : pos+eol]
key, val, _ := bytes.Cut(line, []byte(" "))
switch string(key) {
case "object":
id, err := gitea_git.NewIDFromString(string(val))
if err != nil {
return nil, err
}
tag.Object = id
case "type":
tag.Type = string(val) // A commit can have one or more parents
case "tagger":
tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(val))
}
pos += eol + 1
}
payload, msg, sign := parsePayloadSignature(data, pos)
tag.Message = msg
if len(sign) > 0 {
tag.Signature = &gitea_git.CommitSignature{Signature: sign, Payload: payload}
}
return tag, nil
}
func parsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) {
pos := messageStart
signStart, signEnd := -1, -1
for {
eol := bytes.IndexByte(data[pos:], '\n')
if eol < 0 {
break
}
line := data[pos : pos+eol]
signType, hasPrefix := bytes.CutPrefix(line, []byte("-----BEGIN "))
signType, hasSuffix := bytes.CutSuffix(signType, []byte(" SIGNATURE-----"))
if hasPrefix && hasSuffix {
signEndBytes := append([]byte("\n-----END "), signType...)
signEndBytes = append(signEndBytes, []byte(" SIGNATURE-----")...)
signEnd = bytes.Index(data[pos:], signEndBytes)
if signEnd != -1 {
signStart = pos
signEnd = pos + signEnd + len(signEndBytes)
}
}
pos += eol + 1
}
if signStart != -1 && signEnd != -1 {
msgEnd := max(messageStart, signStart-1)
return string(data[:msgEnd]), string(data[messageStart:msgEnd]), string(data[signStart:signEnd])
}
return string(data), string(data[messageStart:]), ""
}

View File

@ -17,10 +17,11 @@ type CreateHookOption struct {
}
type EditHookOption struct {
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
Active *bool `json:"active"`
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter" binding:"GlobPattern"`
AuthorizationHeader string `json:"authorization_header"`
Active *bool `json:"active"`
}
type HookTask struct {

View File

@ -16,6 +16,7 @@ type Organization struct {
type EditOrgOption struct {
Name string `json:"name"`
Email string `json:"email"`
FullName string `json:"full_name"`
Description string `json:"description" binding:"MaxSize(255)"`
Website string `json:"website" binding:"MaxSize(255)"`

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

44
options/gitignore/Alteryx Normal file
View File

@ -0,0 +1,44 @@
# gitignore template for Alteryx Designer
# website: https://www.alteryx.com/
# website: https://help.alteryx.com/current/designer/alteryx-file-types
# Alteryx Data Files
*.yxdb
*.cydb
*.cyidx
*.rptx
*.vvf
*.aws
# Alteryx Special Files
*.yxwv
*.yxft
*.yxbe
*.bak
*.pcxml
*.log
*.bin
*.yxlang
CASS.ini
# Alteryx License Files
*.yxlc
*.slc
*.cylc
*.alc
*.gzlc
## gitignore reference sites
# https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository#_ignoring
# https://git-scm.com/docs/gitignore
# https://help.github.com/articles/ignoring-files/
## Useful knowledge from stackoverflow
# Even if you haven't tracked the files so far, git seems to be able to "know" about them even after you add them to .gitignore.
# WARNING: First commit your current changes, or you will lose them.
# Then run the following commands from the top folder of your git repo:
# git rm -r --cached .
# git add .
# git commit -m "fixed untracked files"
# author: Kacper Ksieski

View File

@ -14,6 +14,8 @@
*.lzma
*.cab
*.xar
*.zst
*.tzst
# Packing-only formats
*.iso

View File

@ -0,0 +1,31 @@
# gitignore template for B&R Automation Studio (AS) 4
# website: https://www.br-automation.com/en-us/products/software/automation-software/automation-studio/
# AS temporary directories
Binaries/
Diagnosis/
Temp/
TempObjects/
# AS transfer files
*artransfer.br
*arTrsfmode.nv
# 'ignored' directory
ignored/
# ARNC0ext
*arnc0ext.br
# AS File types
*.bak
*.isopen
*.orig
*.log
*.asar
*.csvlog*
*.set
!**/Physical/**/*.set
# RevInfo variables
*RevInfo.var

View File

@ -0,0 +1,11 @@
# generated files
target/
generated/
# dependencies
Dependencies.toml
# config files
Config.toml
# the config files used for testing, Uncomment the following line if you want to commit the test config files
#!**/tests/Config.toml

View File

@ -9,3 +9,4 @@ install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
CMakeUserPresets.json

View File

@ -26,6 +26,18 @@
#*.obj
#
# Default Delphi compiler directories
# Content of this directories are generated with each Compile/Construct of a project.
# Most of the time, files here have not there place in a code repository.
#Win32/
#Win64/
#OSX64/
#OSXARM64/
#Android/
#Android64/
#iOSDevice64/
#Linux64/
# Delphi compiler-generated binaries (safe to delete)
*.exe
*.dll

View File

@ -0,0 +1,62 @@
# gitignore template for ECU-TEST workspaces - by TraceTronic https://tracetronic.com
# website: https://www.ecu-test.com
# * all directories are related to the default directories, please adapt the .gitignore if you use customized
# directories
# Dynamic workspace settings
# * We don't recommend to ignore the .workspace directory, because of important project specific settings
# local user settings
.workspace/ETdrive.xml
.workspace/favorites.xml
.workspace/filters.xml
.workspace/generators.xml
.workspace/history.xml
.workspace/parallelExecution.xml
.workspace/signalviewer.xml
.workspace/signalViewerHistory.json
.workspace/signalviewer2layout.xml
.workspace/testeditor.xml
.workspace/tooladapter.xml
.workspace/view.xml
# optional, if your process depends on this file remove exclusion
.workspace/interactiveexecution.xml
.workspace/pythonlibrary.xml
# deprecated, support for older versions
.workspace/traceexplorer.xml
# Custom file formats and test dependencies
# * you can manage your artifacts also with TEST-GUIDE (https://www.test-guide.info) and reference them via Playbooks
*.arxml
*.a2l
*.dbc
*.hex
*.s19
[tT]estdata
[tT]estdaten
# Test results and test execution related content
# * Git is not intended to store and provide test results for all iterations
# * We recommend to use TEST-GUIDE (https://www.test-guide.info) for the test report management
TestReports
# Report generators and templates
# * if you want to provide (f.e.) your own report generators exclude the directory here and ignore only the
# unnecessary subdirectories
Templates
# Exclude large binary artifacts
# * you can manage your artifacts also with TEST-GUIDE (https://www.test-guide.info) and reference them via Playbooks
Offline-FIUs
Offline-Models
Offline-SGBDs
*.exe
*.msi
*.zip
*.7z
# Exclude default and custom temporary directories
Backup_*
# Python bytecode and cache files
__pycache__/
*.py[cod]

View File

@ -0,0 +1,28 @@
# Firebase build and deployment files
/firebase-debug.log
/firebase-debug.*.log
.firebaserc
# Firebase Hosting
/firebase.json
*.cache
hosting/.cache
# Firebase Functions
/functions/node_modules/
/functions/.env
/functions/package-lock.json
# Firebase Emulators
/firebase-*.zip
/.firebase/
/emulator-ui/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment files (local configs)
/.env.*

119
options/gitignore/Flutter Normal file
View File

@ -0,0 +1,119 @@
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.buildlog/
.history
# Flutter repo-specific
/bin/cache/
/bin/internal/bootstrap.bat
/bin/internal/bootstrap.sh
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/devicelab/ABresults*.json
/dev/docs/doc/
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-preload-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
# Android related
**/android/**/gradle-wrapper.jar
.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/
# Windows
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Linux
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake
# Coverage
coverage/
# Symbols
app.*.symbols
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock

View File

@ -0,0 +1,18 @@
# This .gitignore is appropriate for repositories deployed to GitHub Pages and using
# a Gemfile as specified at https://github.com/github/pages-gem#conventional
# Basic Jekyll gitignores (synchronize to Jekyll.gitignore)
_site/
.sass-cache/
.jekyll-cache/
.jekyll-metadata
# Additional Ruby/bundler ignore for when you run: bundle install
/vendor
# Specific ignore for GitHub Pages
# GitHub Pages will always use its own deployed version of pages-gem
# This means GitHub Pages will NOT use your Gemfile.lock and therefore it is
# counterproductive to check this file into the repository.
# Details at https://github.com/github/pages-gem/issues/768
Gemfile.lock

View File

@ -19,3 +19,7 @@
# Go workspace file
go.work
go.work.sum
# env file
.env

View File

@ -8,7 +8,7 @@ gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
# Avoid ignore Gradle wrapper properties
!gradle-wrapper.properties
# Cache of project

14
options/gitignore/Hexo Normal file
View File

@ -0,0 +1,14 @@
# gitignore template for Hexo sites
# website: https://hexo.io/
# Recommended: Node.gitignore
# Ignore generated directory
public/
# Ignore temp files
tmp/
.tmp*
# additional files
db.json
.deploy*/

47
options/gitignore/IAR Normal file
View File

@ -0,0 +1,47 @@
# Compiled binaries
*.o
*.bin
*.elf
*.hex
*.map
*.out
*.obj
# Trash
*.bak
thumbs.db
*.~*
# IAR Settings
**/settings/*.crun
**/settings/*.dbgdt
**/settings/*.cspy
**/settings/*.cspy.*
**/settings/*.xcl
**/settings/*.dni
**/settings/*.wsdt
**/settings/*.wspos
# IAR Debug Exe
**/Exe/*.sim
# IAR Debug Obj
**/Obj/*.pbd
**/Obj/*.pbd.*
**/Obj/*.pbi
**/Obj/*.pbi.*
# IAR project "Debug" directory
Debug/
# IAR project "Release" directory
Release/
# IAR project settings directory
settings/
# IAR backup files
Backup*
# IAR .dep files
*.dep

View File

@ -16,6 +16,8 @@ _autosave-*
*-save.pro
*-save.kicad_pcb
fp-info-cache
~*.lck
\#auto_saved_files#
# Netlist files (exported from Eeschema)
*.net

View File

@ -22,3 +22,6 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects
.kotlin/

View File

@ -21,3 +21,10 @@ Homestead.yaml
Homestead.json
/.vagrant
.phpunit.result.cache
/public/build
/storage/pail
.env.backup
.env.production
.phpactor.json
auth.json

View File

@ -0,0 +1,42 @@
# Modelica - an object-oriented language for modeling of cyber-physical systems
# https://modelica.org/
# Ignore temporary files, build results, simulation files
## Modelica-specific files
*~
*.bak
*.bak-mo
*.mof
\#*\#
*.moe
*.mol
## Build artefacts
*.exe
*.exp
*.o
*.pyc
## Simulation files
*.mat
## Package files
*.gz
*.rar
*.tar
*.zip
## Dymola-specific files
buildlog.txt
dsfinal.txt
dsin.txt
dslog.txt
dsmodel*
dsres.txt
dymosim*
request
stat
status
stop
success
*.

6
options/gitignore/Move Normal file
View File

@ -0,0 +1,6 @@
# Generated by Move
# will have compiled files
build/
# Remove possibly saving credentials to the git repository
.aptos/

View File

@ -1,3 +1,6 @@
# Ignore build outputs from performing a nix-build or `nix build` command
result
result-*
# Ignore automatically generated direnv output
.direnv

View File

@ -104,6 +104,12 @@ dist
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus

View File

@ -0,0 +1,16 @@
# Excludes Obsidian workspace cache and plugins. All notes and core obsidian
# configuration files are tracked by Git.
# The current application UI state (DOM layout, recently-opened files, etc.) is
# stored in these files (separate for desktop and mobile) so you can resume
# your session seamlessly after a restart. If you want to track UI state, use
# the Workspaces core plugin instead of relying on these files.
.obsidian/workspace.json
.obsidian/workspace-mobile.json
# Obsidian plugins are stored under .obsidian/plugins/$plugin_name. They
# contain metadata (manifest.json), application code (main.js), stylesheets
# (styles.css), and user-configuration data (data.json).
# We want to exclude all plugin-related files, so we can exclude everything
# under this directory.
.obsidian/plugins/**/*

View File

@ -0,0 +1,38 @@
# Excludes Obsidian workspace cache and plugin code, but retains plugin
# configuration. All notes and user-controlled configuration files are tracked
# by Git.
#
# !!! WARNING !!!
#
# Community plugins may store sensitive secrets in their data.json files. By
# including these files, those secrets may be tracked in your Git repository.
#
# To ignore configurations for specific plugins, add a line like this after the
# contents of this file (order is important):
# .obsidian/plugins/{{plugin_name}}/data.json
#
# Alternatively, ensure that you are treating your entire Git repository as
# sensitive data, since it may contain secrets, or may have contained them in
# past commits. Understand your threat profile, and make the decision
# appropriate for yourself. If in doubt, err on the side of not including
# plugin configuration. Use one of the alternative gitignore files instead:
# * NotesOnly.gitignore
# * NotesAndCoreConfiguration.gitignore
# The current application UI state (DOM layout, recently-opened files, etc.) is
# stored in these files (separate for desktop and mobile) so you can resume
# your session seamlessly after a restart. If you want to track UI state, use
# the Workspaces core plugin instead of relying on these files.
.obsidian/workspace.json
.obsidian/workspace-mobile.json
# Obsidian plugins are stored under .obsidian/plugins/$plugin_name. They
# contain metadata (manifest.json), application code (main.js), stylesheets
# (styles.css), and user-configuration data (data.json).
# We only want to track data.json, so we:
# 1. exclude everything under the plugins directory recursively,
# 2. unignore the plugin directories themselves, which then allows us to
# 3. unignore the data.json files
.obsidian/plugins/**/*
!.obsidian/plugins/*/
!.obsidian/plugins/*/data.json

View File

@ -0,0 +1,4 @@
# Excludes all Obsidian-related configuration. All notes are tracked by Git.
# All Obsidian configuration and runtime state is stored here
.obsidian/**/*

View File

@ -5,23 +5,6 @@
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
@ -59,10 +42,3 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/

View File

@ -0,0 +1,42 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tofu
override.tf.json
override.tofu.json
*_override.tf
*_override.tofu
*_override.tf.json
*_override.tofu.json
# Ignore transient lock info files created by tofu apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# !example_override.tofu
# Include tfplan files to ignore the plan output of command: tofu plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc

View File

@ -2,6 +2,7 @@
applet
application.linux-arm64
application.linux-armv6hf
application.linux-riscv64
application.linux32
application.linux64
application.windows32

View File

@ -94,6 +94,12 @@ ipython_config.py
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
@ -106,8 +112,10 @@ ipython_config.py
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
@ -158,3 +166,9 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc

View File

@ -0,0 +1,3 @@
/node_modules/
/lib/
.bsb.lock

View File

@ -3,12 +3,15 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

View File

@ -5,23 +5,6 @@
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
@ -66,10 +49,6 @@ playground.xcworkspace
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
@ -81,10 +60,3 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/

View File

@ -26,6 +26,7 @@
## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
*.bbl-SAVE-ERROR
*.bcf
*.blg
*-blx.aux
@ -39,6 +40,8 @@
*.synctex.gz
*.synctex.gz(busy)
*.pdfsync
*.rubbercache
rubber.cache
## Build tool directories for auxiliary files
# latexrun
@ -138,6 +141,9 @@ acs-*.bib
*.trc
*.xref
# hypdoc
*.hd
# hyperref
*.brf

View File

@ -23,6 +23,9 @@ override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

View File

@ -0,0 +1,3 @@
# Ignore the default terragrunt cache directory
# https://terragrunt.gruntwork.io/docs/features/caching/
.terragrunt-cache

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