diff --git a/MAINTAINERS b/MAINTAINERS index ad02ecc7557..f0caae4d221 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -31,7 +31,6 @@ Gary Kim (@gary-kim) Guillermo Prandi (@guillep2k) Mura Li (@typeless) 6543 <6543@obermui.de> (@6543) -jaqra (@jaqra) David Svantesson (@davidsvantesson) a1012112796 <1012112796@qq.com> (@a1012112796) Karl Heinz Marbaise (@khmarbaise) @@ -63,3 +62,4 @@ Yu Liu <1240335630@qq.com> (@HEREYUA) Kemal Zebari (@kemzeb) Rowan Bohde (@bohde) hiifong (@hiifong) +metiftikci (@metiftikci) diff --git a/cmd/main.go b/cmd/main.go index fd648946efa..7251bd09a3f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -165,6 +165,7 @@ func NewMainApp(appVer AppVersion) *cli.App { app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdStandalone...) + setting.InitGiteaEnvVars() return app } diff --git a/cmd/main_test.go b/cmd/main_test.go index c182b440199..3ec584d3236 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -6,7 +6,6 @@ package cmd import ( "fmt" "io" - "os" "path/filepath" "strings" "testing" @@ -113,37 +112,17 @@ func TestCliCmd(t *testing.T) { _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) return nil }) - var envBackup []string - for _, s := range os.Environ() { - if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") { - envBackup = append(envBackup, s) - } - } - clearGiteaEnv := func() { - for _, s := range os.Environ() { - if strings.HasPrefix(s, "GITEA_") { - _ = os.Unsetenv(s) - } - } - } - defer func() { - clearGiteaEnv() - for _, s := range envBackup { - k, v, _ := strings.Cut(s, "=") - _ = os.Setenv(k, v) - } - }() - for _, c := range cases { - clearGiteaEnv() - for k, v := range c.env { - _ = os.Setenv(k, v) - } - args := strings.Split(c.cmd, " ") // for test only, "split" is good enough - r, err := runTestApp(app, args...) - assert.NoError(t, err, c.cmd) - assert.NotEmpty(t, c.exp, c.cmd) - assert.Contains(t, r.Stdout, c.exp, c.cmd) + t.Run(c.cmd, func(t *testing.T) { + for k, v := range c.env { + t.Setenv(k, v) + } + args := strings.Split(c.cmd, " ") // for test only, "split" is good enough + r, err := runTestApp(app, args...) + assert.NoError(t, err, c.cmd) + assert.NotEmpty(t, c.exp, c.cmd) + assert.Contains(t, r.Stdout, c.exp, c.cmd) + }) } } diff --git a/cmd/migrate.go b/cmd/migrate.go index 4e4dd45af3a..459805a76d7 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -18,7 +18,7 @@ import ( 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.", + Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`, Action: runMigrate, } diff --git a/cmd/web_acme.go b/cmd/web_acme.go index 90e4a02764b..2fe14c1f54c 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -54,7 +54,7 @@ func runACME(listenAddr string, m http.Handler) error { altTLSALPNPort = p } - magic := certmagic.NewDefault() + magic := &certmagic.Default magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} // Try to use private CA root if provided, otherwise defaults to system's trust var certPool *x509.CertPool diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index d819b55d282..eacc732c223 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1485,6 +1485,10 @@ LEVEL = Info ;REPO_INDEXER_EXCLUDE = ;; ;MAX_FILE_SIZE = 1048576 +;; +;; Bleve engine has performance problems with fuzzy search, so we limit the fuzziness to 0 by default to disable it. +;; If you'd like to enable it, you can set it to a value between 0 and 2. +;TYPE_BLEVE_MAX_FUZZINESS = 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/go.mod b/go.mod index dc80d2ca2bb..084b2946091 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,6 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/feeds v1.2.0 github.com/gorilla/sessions v1.4.0 - github.com/h2non/gock v1.2.0 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/huandu/xstrings v1.5.0 @@ -230,7 +229,6 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect - github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect diff --git a/go.sum b/go.sum index b5e64321b53..40add64289d 100644 --- a/go.sum +++ b/go.sum @@ -452,10 +452,6 @@ github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= -github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= -github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -599,8 +595,6 @@ github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE= github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek= github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= diff --git a/models/repo/repo.go b/models/repo/repo.go index 5ef4d470c3b..af4a1f7fb58 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -279,6 +279,8 @@ func (repo *Repository) IsBroken() bool { } // MarkAsBrokenEmpty marks the repo as broken and empty +// FIXME: the status "broken" and "is_empty" were abused, +// The code always set them together, no way to distinguish whether a repo is really "empty" or "broken" func (repo *Repository) MarkAsBrokenEmpty() { repo.Status = RepositoryBroken repo.IsEmpty = true diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 75044d19e8e..0dcff166cc3 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -59,6 +59,7 @@ func InitSettings() { _ = hash.Register("dummy", hash.NewDummyHasher) setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy") + setting.InitGiteaEnvVarsForTesting() } // TestOptions represents test options diff --git a/modules/auth/password/pwn/pwn_test.go b/modules/auth/password/pwn/pwn_test.go index b3e7734c3fc..ae03fabc579 100644 --- a/modules/auth/password/pwn/pwn_test.go +++ b/modules/auth/password/pwn/pwn_test.go @@ -4,46 +4,57 @@ package pwn import ( + "errors" + "io" "net/http" + "strings" "testing" - "time" - "github.com/h2non/gock" "github.com/stretchr/testify/assert" ) -var client = New(WithHTTP(&http.Client{ - Timeout: time.Second * 2, -})) +type mockTransport struct{} + +func (mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if req.URL.Host != "api.pwnedpasswords.com" { + return nil, errors.New("unsupported host") + } + respMap := map[string]string{ + "/range/5c1d8": "EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2", + "/range/ba189": "FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4", + "/range/a1733": "C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0", + "/range/5617b": "FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0", + "/range/79082": "FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0", + } + if resp, ok := respMap[req.URL.Path]; ok { + return &http.Response{Request: req, Body: io.NopCloser(strings.NewReader(resp))}, nil + } + return nil, errors.New("unsupported path") +} func TestPassword(t *testing.T) { - defer gock.Off() + client := New(WithHTTP(&http.Client{Transport: mockTransport{}})) count, err := client.CheckPassword("", false) assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword") assert.Equal(t, -1, count) - gock.New("https://api.pwnedpasswords.com").Get("/range/5c1d8").Times(1).Reply(200).BodyString("EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2") count, err = client.CheckPassword("pwned", false) assert.NoError(t, err) assert.Equal(t, 1, count) - gock.New("https://api.pwnedpasswords.com").Get("/range/ba189").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4") count, err = client.CheckPassword("notpwned", false) assert.NoError(t, err) assert.Equal(t, 0, count) - gock.New("https://api.pwnedpasswords.com").Get("/range/a1733").Times(1).Reply(200).BodyString("C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0") count, err = client.CheckPassword("paddedpwned", true) assert.NoError(t, err) assert.Equal(t, 1, count) - gock.New("https://api.pwnedpasswords.com").Get("/range/5617b").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0") count, err = client.CheckPassword("paddednotpwned", true) assert.NoError(t, err) assert.Equal(t, 0, count) - gock.New("https://api.pwnedpasswords.com").Get("/range/79082").Times(1).Reply(200).BodyString("FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0") count, err = client.CheckPassword("paddednotpwnedzero", true) assert.NoError(t, err) assert.Equal(t, 0, count) diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 532dbad9894..33e54fe75cb 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -242,7 +242,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte { return out } -// ParseTreeLine reads an entry from a tree in a cat-file --batch stream +// ParseCatFileTreeLine reads an entry from a tree in a cat-file --batch stream // This carefully avoids allocations - except where fnameBuf is too small. // It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations // @@ -250,7 +250,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte { // SP NUL // // We don't attempt to convert the raw HASH to save a lot of time -func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { +func ParseCatFileTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { var readBytes []byte // Read the Mode & fname @@ -260,7 +260,7 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu } idx := bytes.IndexByte(readBytes, ' ') if idx < 0 { - log.Debug("missing space in readBytes ParseTreeLine: %s", readBytes) + log.Debug("missing space in readBytes ParseCatFileTreeLine: %s", readBytes) return mode, fname, sha, n, &ErrNotExist{} } diff --git a/modules/git/command.go b/modules/git/command.go index b231c3beea0..2584e3cc57c 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -236,10 +236,16 @@ type RunOpts struct { } func commonBaseEnvs() []string { - // at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it envs := []string{ - "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config - "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) + // Make Gitea use internal git config only, to prevent conflicts with user's git config + // It's better to use GIT_CONFIG_GLOBAL, but it requires git >= 2.32, so we still use HOME at the moment. + "HOME=" + HomeDir(), + // Avoid using system git config, it would cause problems (eg: use macOS osxkeychain to show a modal dialog, auto installing lfs hooks) + // This might be a breaking change in 1.24, because some users said that they have put some configs like "receive.certNonceSeed" in "/etc/gitconfig" + // For these users, they need to migrate the necessary configs to Gitea's git config file manually. + "GIT_CONFIG_NOSYSTEM=1", + // Ignore replace references (https://git-scm.com/docs/git-replace) + "GIT_NO_REPLACE_OBJECTS=1", } // some environment variables should be passed to git command diff --git a/modules/git/parse.go b/modules/git/parse.go new file mode 100644 index 00000000000..eb26632cc0e --- /dev/null +++ b/modules/git/parse.go @@ -0,0 +1,78 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "bytes" + "fmt" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/optional" +) + +var sepSpace = []byte{' '} + +type LsTreeEntry struct { + ID ObjectID + EntryMode EntryMode + Name string + Size optional.Option[int64] +} + +func parseLsTreeLine(line []byte) (*LsTreeEntry, error) { + // expect line to be of the form: + // \t + // \t + + var err error + posTab := bytes.IndexByte(line, '\t') + if posTab == -1 { + return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line) + } + + entry := new(LsTreeEntry) + + entryAttrs := line[:posTab] + entryName := line[posTab+1:] + + entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace) + _ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type + entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace) + if len(entryAttrs) > 0 { + entrySize := entryAttrs // the last field is the space-padded-size + size, _ := strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64) + entry.Size = optional.Some(size) + } + + switch string(entryMode) { + case "100644": + entry.EntryMode = EntryModeBlob + case "100755": + entry.EntryMode = EntryModeExec + case "120000": + entry.EntryMode = EntryModeSymlink + case "160000": + entry.EntryMode = EntryModeCommit + case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons + entry.EntryMode = EntryModeTree + default: + return nil, fmt.Errorf("unknown type: %v", string(entryMode)) + } + + entry.ID, err = NewIDFromString(string(entryObjectID)) + if err != nil { + return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err) + } + + if len(entryName) > 0 && entryName[0] == '"' { + entry.Name, err = strconv.Unquote(string(entryName)) + if err != nil { + return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err) + } + } else { + entry.Name = string(entryName) + } + return entry, nil +} diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_nogogit.go index 619d86a72a2..848c2076908 100644 --- a/modules/git/parse_nogogit.go +++ b/modules/git/parse_nogogit.go @@ -10,8 +10,6 @@ import ( "bytes" "fmt" "io" - "strconv" - "strings" "code.gitea.io/gitea/modules/log" ) @@ -21,8 +19,7 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { return parseTreeEntries(data, nil) } -var sepSpace = []byte{' '} - +// parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) return entries, iterateTreeEntries(data, ptree, func(entry *TreeEntry) error { @@ -32,67 +29,27 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { } func iterateTreeEntries(data []byte, ptree *Tree, f func(entry *TreeEntry) error) error { - var err error for pos := 0; pos < len(data); { - // expect line to be of the form: - // \t - // \t posEnd := bytes.IndexByte(data[pos:], '\n') if posEnd == -1 { posEnd = len(data) } else { posEnd += pos } - line := data[pos:posEnd] - posTab := bytes.IndexByte(line, '\t') - if posTab == -1 { - return fmt.Errorf("invalid ls-tree output (no tab): %q", line) - } - - entry := new(TreeEntry) - entry.ptree = ptree - - entryAttrs := line[:posTab] - entryName := line[posTab+1:] - - entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace) - _ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type - entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace) - if len(entryAttrs) > 0 { - entrySize := entryAttrs // the last field is the space-padded-size - entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64) - entry.sized = true - } - switch string(entryMode) { - case "100644": - entry.entryMode = EntryModeBlob - case "100755": - entry.entryMode = EntryModeExec - case "120000": - entry.entryMode = EntryModeSymlink - case "160000": - entry.entryMode = EntryModeCommit - case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons - entry.entryMode = EntryModeTree - default: - return fmt.Errorf("unknown type: %v", string(entryMode)) - } - - entry.ID, err = NewIDFromString(string(entryObjectID)) + line := data[pos:posEnd] + lsTreeLine, err := parseLsTreeLine(line) if err != nil { - return fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err) + return err } - - if len(entryName) > 0 && entryName[0] == '"' { - entry.name, err = strconv.Unquote(string(entryName)) - if err != nil { - return fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err) - } - } else { - entry.name = string(entryName) + entry := &TreeEntry{ + ptree: ptree, + ID: lsTreeLine.ID, + entryMode: lsTreeLine.EntryMode, + name: lsTreeLine.Name, + size: lsTreeLine.Size.Value(), + sized: lsTreeLine.Size.Has(), } - pos = posEnd + 1 if err := f(entry); err != nil { return err @@ -109,7 +66,7 @@ func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio. loop: for sz > 0 { - mode, fname, sha, count, err := ParseTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf) + mode, fname, sha, count, err := ParseCatFileTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf) if err != nil { if err == io.EOF { break loop diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index b22805c1327..92e35c5a102 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -114,7 +114,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err case "tree": var n int64 for n < size { - mode, fname, binObjectID, count, err := git.ParseTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf) + mode, fname, binObjectID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf) if err != nil { return nil, err } diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go index 1bf1aa41b94..2b45a50f191 100644 --- a/modules/git/repo_archive.go +++ b/modules/git/repo_archive.go @@ -8,7 +8,6 @@ import ( "context" "fmt" "io" - "os" "path/filepath" "strings" ) @@ -63,15 +62,11 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t cmd.AddOptionFormat("--format=%s", format.String()) cmd.AddDynamicArguments(commitID) - // Avoid LFS hooks getting installed because of /etc/gitconfig, which can break pull requests. - env := append(os.Environ(), "GIT_CONFIG_NOSYSTEM=1") - var stderr strings.Builder err := cmd.Run(&RunOpts{ Dir: repo.Path, Stdout: target, Stderr: &stderr, - Env: env, }) if err != nil { return ConcatenateError(err, stderr.String()) diff --git a/modules/git/submodule.go b/modules/git/submodule.go new file mode 100644 index 00000000000..017b644052b --- /dev/null +++ b/modules/git/submodule.go @@ -0,0 +1,66 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "bufio" + "context" + "fmt" + "os" + + "code.gitea.io/gitea/modules/log" +) + +type TemplateSubmoduleCommit struct { + Path string + Commit string +} + +// GetTemplateSubmoduleCommits returns a list of submodules paths and their commits from a repository +// This function is only for generating new repos based on existing template, the template couldn't be too large. +func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submoduleCommits []TemplateSubmoduleCommit, _ error) { + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + return nil, err + } + opts := &RunOpts{ + Dir: repoPath, + Stdout: stdoutWriter, + PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() + defer stdoutReader.Close() + + scanner := bufio.NewScanner(stdoutReader) + for scanner.Scan() { + entry, err := parseLsTreeLine(scanner.Bytes()) + if err != nil { + cancel() + return err + } + if entry.EntryMode == EntryModeCommit { + submoduleCommits = append(submoduleCommits, TemplateSubmoduleCommit{Path: entry.Name, Commit: entry.ID.String()}) + } + } + return scanner.Err() + }, + } + err = NewCommand(ctx, "ls-tree", "-r", "--", "HEAD").Run(opts) + if err != nil { + return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err) + } + return submoduleCommits, nil +} + +// AddTemplateSubmoduleIndexes Adds the given submodules to the git index. +// It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir. +func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error { + for _, submodule := range submodules { + cmd := NewCommand(ctx, "update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path) + if stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}); err != nil { + log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err) + return err + } + } + return nil +} diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go new file mode 100644 index 00000000000..d53946a27d4 --- /dev/null +++ b/modules/git/submodule_test.go @@ -0,0 +1,48 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetTemplateSubmoduleCommits(t *testing.T) { + testRepoPath := filepath.Join(testReposDir, "repo4_submodules") + submodules, err := GetTemplateSubmoduleCommits(DefaultContext, testRepoPath) + require.NoError(t, err) + + assert.Len(t, submodules, 2) + + assert.EqualValues(t, "<°)))><", submodules[0].Path) + assert.EqualValues(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit) + + assert.EqualValues(t, "libtest", submodules[1].Path) + assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[1].Commit) +} + +func TestAddTemplateSubmoduleIndexes(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + var err error + _, _, err = NewCommand(ctx, "init").RunStdString(&RunOpts{Dir: tmpDir}) + require.NoError(t, err) + _ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755) + err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}}) + require.NoError(t, err) + _, _, err = NewCommand(ctx, "add", "--all").RunStdString(&RunOpts{Dir: tmpDir}) + require.NoError(t, err) + _, _, err = NewCommand(ctx, "-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(&RunOpts{Dir: tmpDir}) + require.NoError(t, err) + submodules, err := GetTemplateSubmoduleCommits(DefaultContext, tmpDir) + require.NoError(t, err) + assert.Len(t, submodules, 1) + assert.EqualValues(t, "new-dir", submodules[0].Path) + assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[0].Commit) +} diff --git a/modules/git/tests/repos/repo4_submodules/HEAD b/modules/git/tests/repos/repo4_submodules/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/modules/git/tests/repos/repo4_submodules/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/modules/git/tests/repos/repo4_submodules/config b/modules/git/tests/repos/repo4_submodules/config new file mode 100644 index 00000000000..07d359d07cf --- /dev/null +++ b/modules/git/tests/repos/repo4_submodules/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/modules/git/tests/repos/repo4_submodules/objects/97/c3d30df0e6492348292600920a6482feaebb74 b/modules/git/tests/repos/repo4_submodules/objects/97/c3d30df0e6492348292600920a6482feaebb74 new file mode 100644 index 00000000000..7596090b49f Binary files /dev/null and b/modules/git/tests/repos/repo4_submodules/objects/97/c3d30df0e6492348292600920a6482feaebb74 differ diff --git a/modules/git/tests/repos/repo4_submodules/objects/c7/e064ed49b44523cba8a5dfbc37d2ce1bb41d34 b/modules/git/tests/repos/repo4_submodules/objects/c7/e064ed49b44523cba8a5dfbc37d2ce1bb41d34 new file mode 100644 index 00000000000..e3a13c156dc Binary files /dev/null and b/modules/git/tests/repos/repo4_submodules/objects/c7/e064ed49b44523cba8a5dfbc37d2ce1bb41d34 differ diff --git a/modules/git/tests/repos/repo4_submodules/objects/e1/e59caba97193d48862d6809912043871f37437 b/modules/git/tests/repos/repo4_submodules/objects/e1/e59caba97193d48862d6809912043871f37437 new file mode 100644 index 00000000000..a8d6e5c17c8 --- /dev/null +++ b/modules/git/tests/repos/repo4_submodules/objects/e1/e59caba97193d48862d6809912043871f37437 @@ -0,0 +1,2 @@ +x[ +0E*_$M5tifBk Iŕ7k~9ܘܠ.j O "z`#IirF͹$%|4)?t=:K#[$D^ӒyHU/f?G \ No newline at end of file diff --git a/modules/git/tests/repos/repo4_submodules/refs/heads/master b/modules/git/tests/repos/repo4_submodules/refs/heads/master new file mode 100644 index 00000000000..102bc34da8c --- /dev/null +++ b/modules/git/tests/repos/repo4_submodules/refs/heads/master @@ -0,0 +1 @@ +e1e59caba97193d48862d6809912043871f37437 diff --git a/modules/git/tree.go b/modules/git/tree.go index d35dc58d8d0..5a644f6c87a 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -17,7 +17,7 @@ func NewTree(repo *Repository, id ObjectID) *Tree { } } -// SubTree get a sub tree by the sub dir path +// SubTree get a subtree by the sub dir path func (t *Tree) SubTree(rpath string) (*Tree, error) { if len(rpath) == 0 { return t, nil @@ -63,7 +63,7 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error return filelist, err } -// GetTreePathLatestCommitID returns the latest commit of a tree path +// GetTreePathLatestCommit returns the latest commit of a tree path func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1"). AddDynamicArguments(refName).AddDashesAndList(treePath). diff --git a/modules/git/tree_blob_nogogit.go b/modules/git/tree_blob_nogogit.go index 92d3d107a74..b7bcf40edd2 100644 --- a/modules/git/tree_blob_nogogit.go +++ b/modules/git/tree_blob_nogogit.go @@ -17,7 +17,6 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { ptree: t, ID: t.ID, name: "", - fullName: "", entryMode: EntryModeTree, }, nil } diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go index 1c3bcd197a0..81fb638d56f 100644 --- a/modules/git/tree_entry_nogogit.go +++ b/modules/git/tree_entry_nogogit.go @@ -9,23 +9,17 @@ import "code.gitea.io/gitea/modules/log" // TreeEntry the leaf in the git tree type TreeEntry struct { - ID ObjectID - + ID ObjectID ptree *Tree entryMode EntryMode name string - - size int64 - sized bool - fullName string + size int64 + sized bool } // Name returns the name of the entry func (te *TreeEntry) Name() string { - if te.fullName != "" { - return te.fullName - } return te.name } diff --git a/modules/indexer/code/indexer.go b/modules/indexer/code/indexer.go index c1ab26569c6..728b37fab6e 100644 --- a/modules/indexer/code/indexer.go +++ b/modules/indexer/code/indexer.go @@ -123,13 +123,12 @@ func Init() { for _, indexerData := range items { log.Trace("IndexerData Process Repo: %d", indexerData.RepoID) if err := index(ctx, indexer, indexerData.RepoID); err != nil { - unhandled = append(unhandled, indexerData) if !setting.IsInTesting { log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err) } } } - return unhandled + return nil // do not re-queue the failed items, otherwise some broken repo will block the queue } indexerQueue = queue.CreateUniqueQueue(ctx, "code_indexer", handler) diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index d04088531ac..f358bbe7851 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -15,6 +15,8 @@ import ( "code.gitea.io/gitea/modules/indexer/code/bleve" "code.gitea.io/gitea/modules/indexer/code/elasticsearch" "code.gitea.io/gitea/modules/indexer/code/internal" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" _ "code.gitea.io/gitea/models" _ "code.gitea.io/gitea/models/actions" @@ -279,7 +281,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) { func TestBleveIndexAndSearch(t *testing.T) { unittest.PrepareTestEnv(t) - + defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)() dir := t.TempDir() idx := bleve.NewIndexer(dir) diff --git a/modules/indexer/internal/bleve/util.go b/modules/indexer/internal/bleve/util.go index a0c3dc4ad45..b6daa9e14b4 100644 --- a/modules/indexer/internal/bleve/util.go +++ b/modules/indexer/internal/bleve/util.go @@ -9,6 +9,7 @@ import ( "unicode" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "github.com/blevesearch/bleve/v2" @@ -54,9 +55,9 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) { return index, 0, nil } -// This method test the GuessFuzzinessByKeyword method. The fuzziness is based on the levenshtein distance and determines how many chars -// may be different on two string and they still be considered equivalent. -// Given a phrasse, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero. +// GuessFuzzinessByKeyword guesses fuzziness based on the levenshtein distance and determines how many chars +// may be different on two string, and they still be considered equivalent. +// Given a phrase, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero. func GuessFuzzinessByKeyword(s string) int { tokenizer := unicode_tokenizer.NewUnicodeTokenizer() tokens := tokenizer.Tokenize([]byte(s)) @@ -85,5 +86,5 @@ func guessFuzzinessByKeyword(s string) int { return 0 } } - return min(maxFuzziness, len(s)/4) + return min(min(setting.Indexer.TypeBleveMaxFuzzniess, maxFuzziness), len(s)/4) } diff --git a/modules/indexer/internal/bleve/util_test.go b/modules/indexer/internal/bleve/util_test.go index 8f7844464e7..1a7e4db0f42 100644 --- a/modules/indexer/internal/bleve/util_test.go +++ b/modules/indexer/internal/bleve/util_test.go @@ -7,10 +7,15 @@ import ( "fmt" "testing" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "github.com/stretchr/testify/assert" ) func TestBleveGuessFuzzinessByKeyword(t *testing.T) { + defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)() + scenarios := []struct { Input string Fuzziness int // See util.go for the definition of fuzziness in this particular context @@ -46,7 +51,7 @@ func TestBleveGuessFuzzinessByKeyword(t *testing.T) { } for _, scenario := range scenarios { - t.Run(fmt.Sprintf("ensure fuzziness of '%s' is '%d'", scenario.Input, scenario.Fuzziness), func(t *testing.T) { + t.Run(fmt.Sprintf("Fuziniess:%s=%d", scenario.Input, scenario.Fuzziness), func(t *testing.T) { assert.Equal(t, scenario.Fuzziness, GuessFuzzinessByKeyword(scenario.Input)) }) } diff --git a/modules/markup/sanitizer_default.go b/modules/markup/sanitizer_default.go index 5eeafe940a4..14161eb5337 100644 --- a/modules/markup/sanitizer_default.go +++ b/modules/markup/sanitizer_default.go @@ -48,7 +48,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li") // Allow 'color' and 'background-color' properties for the style attribute on text elements. - policy.AllowStyles("color", "background-color").OnElements("span", "p") + policy.AllowStyles("color", "background-color").OnElements("div", "span", "p", "tr", "th", "td") policy.AllowAttrs("src", "autoplay", "controls").OnElements("video") diff --git a/modules/setting/config_env.go b/modules/setting/config_env.go index dfcb7db3c86..5d94a9641f7 100644 --- a/modules/setting/config_env.go +++ b/modules/setting/config_env.go @@ -166,3 +166,25 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) { } return changed } + +// InitGiteaEnvVars initializes the environment variables for gitea +func InitGiteaEnvVars() { + // Ideally Gitea should only accept the environment variables which it clearly knows instead of unsetting the ones it doesn't want, + // but the ideal behavior would be a breaking change, and it seems not bringing enough benefits to end users, + // so at the moment we could still keep "unsetting the unnecessary environments" + + // HOME is managed by Gitea, Gitea's git should use "HOME/.gitconfig". + // But git would try "XDG_CONFIG_HOME/git/config" first if "HOME/.gitconfig" does not exist, + // then our git.InitFull would still write to "XDG_CONFIG_HOME/git/config" if XDG_CONFIG_HOME is set. + _ = os.Unsetenv("XDG_CONFIG_HOME") +} + +func InitGiteaEnvVarsForTesting() { + InitGiteaEnvVars() + _ = os.Unsetenv("GIT_AUTHOR_NAME") + _ = os.Unsetenv("GIT_AUTHOR_EMAIL") + _ = os.Unsetenv("GIT_AUTHOR_DATE") + _ = os.Unsetenv("GIT_COMMITTER_NAME") + _ = os.Unsetenv("GIT_COMMITTER_EMAIL") + _ = os.Unsetenv("GIT_COMMITTER_DATE") +} diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go index 34b4eac15b9..e34baae012b 100644 --- a/modules/setting/indexer.go +++ b/modules/setting/indexer.go @@ -31,6 +31,8 @@ var Indexer = struct { IncludePatterns []*GlobMatcher ExcludePatterns []*GlobMatcher ExcludeVendored bool + + TypeBleveMaxFuzzniess int }{ IssueType: "bleve", IssuePath: "indexers/issues.bleve", @@ -88,6 +90,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) { Indexer.ExcludeVendored = sec.Key("REPO_INDEXER_EXCLUDE_VENDORED").MustBool(true) Indexer.MaxIndexerFileSize = sec.Key("MAX_FILE_SIZE").MustInt64(1024 * 1024) Indexer.StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(30 * time.Second) + Indexer.TypeBleveMaxFuzzniess = sec.Key("TYPE_BLEVE_MAX_FUZZINESS").MustInt(0) } // IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing diff --git a/modules/setting/security.go b/modules/setting/security.go index 3d12fcf8d9f..2f798b75c7e 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -13,8 +13,9 @@ import ( "code.gitea.io/gitea/modules/log" ) +// Security settings + var ( - // Security settings InstallLock bool SecretKey string InternalToken string // internal access token @@ -27,7 +28,7 @@ var ( ReverseProxyTrustedProxies []string MinPasswordLength int ImportLocalPaths bool - DisableGitHooks bool + DisableGitHooks = true DisableWebhooks bool OnlyAllowPushIfGiteaEnvironmentSet bool PasswordComplexity []string diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 7529cadca4d..48d3a8ff89a 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -131,15 +131,9 @@ func NewFuncMap() template.FuncMap { "EnableTimetracking": func() bool { return setting.Service.EnableTimetracking }, - "DisableGitHooks": func() bool { - return setting.DisableGitHooks - }, "DisableWebhooks": func() bool { return setting.DisableWebhooks }, - "DisableImportLocal": func() bool { - return !setting.ImportLocalPaths - }, "UserThemeName": userThemeName, "NotificationSettings": func() map[string]any { return map[string]any{ diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 57b209aaf97..beeb1dc3b89 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1012,7 +1012,6 @@ new_repo_helper=Repozitář obsahuje všechny projektové soubory, včetně hist owner=Vlastník owner_helper=Některé organizace se nemusejí v seznamu zobrazit kvůli maximálnímu dosaženému počtu repozitářů. repo_name=Název repozitáře -repo_name_helper=Dobrý název repozitáře většinou používá krátká, zapamatovatelná a unikátní klíčová slova. repo_size=Velikost repozitáře template=Šablona template_select=Vyberte šablonu. @@ -2833,6 +2832,7 @@ teams.invite.title=Byli jste pozváni do týmu %s v organizaci teams.invite.by=Pozvání od %s teams.invite.description=Pro připojení k týmu klikněte na tlačítko níže. + [admin] maintenance=Údržba dashboard=Přehled diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 5f2f16bfdf3..ce2c43cb568 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1015,7 +1015,6 @@ new_repo_helper=Ein Repository enthält alle Projektdateien, einschließlich des owner=Besitzer owner_helper=Einige Organisationen könnten in der Dropdown-Liste nicht angezeigt werden, da die Anzahl an Repositories begrenzt ist. repo_name=Repository-Name -repo_name_helper=Ein guter Repository-Name besteht normalerweise aus kurzen, unvergesslichen und einzigartigen Schlagwörtern. repo_size=Repository-Größe template=Template template_select=Vorlage auswählen @@ -2861,6 +2860,7 @@ teams.invite.title=Du wurdest eingeladen, dem Team %s in der Or teams.invite.by=Von %s eingeladen teams.invite.description=Bitte klicke auf die folgende Schaltfläche, um dem Team beizutreten. + [admin] maintenance=Wartung dashboard=Dashboard diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index fa9c41d5de0..31e57bbf978 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -908,7 +908,6 @@ new_repo_helper=Ένα αποθετήριο περιέχει όλα τα αρχ owner=Ιδιοκτήτης owner_helper=Ορισμένοι οργανισμοί ενδέχεται να μην εμφανίζονται στο αναπτυσσόμενο μενού λόγω του μέγιστου αριθμού αποθετηρίων. repo_name=Όνομα αποθετηρίου -repo_name_helper=Τα καλά ονόματα αποθετηρίων χρησιμοποιούν σύντομες, αξέχαστες και μοναδικές λέξεις-κλειδιά. repo_size=Μέγεθος Αποθετηρίου template=Πρότυπο template_select=Επιλέξτε πρότυπο. @@ -2593,6 +2592,7 @@ teams.invite.title=Έχετε προσκληθεί να συμμετάσχετε teams.invite.by=Προσκλήθηκε από %s teams.invite.description=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για συμμετοχή στην ομάδα. + [admin] dashboard=Πίνακας Ελέγχου identity_access=Ταυτότητα & Πρόσβαση diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4e9ec275ddc..96404a61431 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1235,6 +1235,7 @@ create_new_repo_command = Creating a new repository on the command line push_exist_repo = Pushing an existing repository from the command line empty_message = This repository does not contain any content. broken_message = The Git data underlying this repository cannot be read. Contact the administrator of this instance or delete this repository. +no_branch = This repository doesn’t have any branches. code = Code code.desc = Access source code, files, commits and branches. @@ -3538,6 +3539,7 @@ versions = Versions versions.view_all = View all dependency.id = ID dependency.version = Version +search_in_external_registry = Search in %s alpine.registry = Setup this registry by adding the url in your /etc/apk/repositories file: alpine.registry.key = Download the registry public RSA key into the /etc/apk/keys/ folder to verify the index signature: alpine.registry.info = Choose $branch and $repository from the list below. @@ -3763,6 +3765,7 @@ workflow.not_found = Workflow '%s' not found. workflow.run_success = Workflow '%s' run successfully. workflow.from_ref = Use workflow from workflow.has_workflow_dispatch = This workflow has a workflow_dispatch event trigger. +workflow.has_no_workflow_dispatch = Workflow '%s' has no workflow_dispatch event trigger. need_approval_desc = Need approval to run workflows for fork pull request. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index d7d1cadd089..cdfe1fb2e58 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -898,7 +898,6 @@ visibility.private_tooltip=Visible sólo para los miembros de organizaciones a l owner=Propietario owner_helper=Algunas organizaciones pueden no aparecer en el menú desplegable debido a un límite máximo de recuento de repositorios. repo_name=Nombre del repositorio -repo_name_helper=Un buen nombre de repositorio está compuesto por palabras clave cortas, memorables y únicas. repo_size=Tamaño del repositorio template=Plantilla template_select=Seleccionar una plantilla. @@ -2574,6 +2573,7 @@ teams.invite.title=Has sido invitado a unirte al equipo %s en l teams.invite.by=Invitado por %s teams.invite.description=Por favor, haga clic en el botón de abajo para unirse al equipo. + [admin] dashboard=Panel de control identity_access=Identidad y acceso diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 6dcd35560a6..4d90cf98762 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -704,7 +704,6 @@ visibility.private=خصوصی owner=مالک owner_helper=بخاطر بیشینه تعداد مخزن، ممکن است برخی از سازمان‌ها در لیست کشویی دیده نشود. repo_name=نام مخزن -repo_name_helper=نام خوب مخزن معمولا از کلمات کلیدی کوتاه و به یاد ماندنی و منحصر به فرد تشکیل شده است. repo_size=اندازه مخزن template=قالب / الگو template_select=انتخاب یک قالب/ الگو. @@ -1993,6 +1992,7 @@ teams.all_repositories_read_permission_desc=این تیم دسترسی teams.all_repositories_write_permission_desc=این تیم دسترسی نوشتن مخازن همه را می بخشد: اعضا می توانند مخازن را مشاهده و درج کنند. teams.all_repositories_admin_permission_desc=این تیم دسترسی مدیر به مخازن همه را می بخشد: اعضا می توانند مخازن را بخواند، همکار و مخزن اضافه کنند. + [admin] dashboard=پیشخوان users=حساب کاربران diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index b4f3869db41..b5fa5c8afcd 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -635,7 +635,6 @@ visibility.private=Yksityinen owner=Omistaja owner_helper=Jotkin organisaatiot eivät välttämättä näy pudotusvalikossa, koska repojen maksimimäärää on rajoitettu. repo_name=Repon nimi -repo_name_helper=Hyvä repon nimi on lyhyt, mieleenpainuva ja yksilöllinen. repo_size=Repon koko template=Malli template_select=Valitse malli. @@ -1361,6 +1360,7 @@ teams.repositories=Tiimin repot teams.members.none=Ei jäseniä tässä tiimissä. teams.all_repositories=Kaikki repot + [admin] dashboard=Kojelauta users=Käyttäjätilit diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index b024be4d882..743b7662565 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1015,7 +1015,6 @@ new_repo_helper=Un dépôt contient tous les fichiers d’un projet, ainsi que l owner=Propriétaire owner_helper=Certaines organisations peuvent ne pas apparaître dans la liste déroulante en raison d'une limite maximale du nombre de dépôts. repo_name=Nom du dépôt -repo_name_helper=Idéalement, le nom d'un dépôt devrait être court, mémorisable et unique. repo_size=Taille du dépôt template=Modèle template_select=Répliquer un modèle @@ -2861,6 +2860,7 @@ teams.invite.title=Vous avez été invité à rejoindre l'équipe %s%spermessi teams.all_repositories_write_permission_desc=Questo team concede permessi di scrittura accesso a tutte le repository: i membri possono leggere e pushare le repository. teams.all_repositories_admin_permission_desc=Questo team concede a Amministratore l'accesso a tutte le repository: i membri possono leggere, pushare e aggiungere collaboratori alle repository. + [admin] dashboard=Pannello di Controllo users=Account utenti diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index efcf34806a4..89582af89c3 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1015,7 +1015,6 @@ new_repo_helper=リポジトリには、プロジェクトのすべてのファ owner=オーナー owner_helper=リポジトリ数の上限により、一部の組織はドロップダウンに表示されない場合があります。 repo_name=リポジトリ名 -repo_name_helper=リポジトリ名は、短く、覚えやすく、他と重複しないキーワードを使用しましょう。 repo_size=リポジトリサイズ template=テンプレート template_select=テンプレートを選択してください。 @@ -2853,6 +2852,7 @@ teams.invite.title=あなたは組織 %[2]s 内のチーム %[2]s< teams.invite.by=Uzaicināja %s teams.invite.description=Nospiediet pogu zemāk, lai pievienotos komandai. + [admin] dashboard=Infopanelis self_check=Pašpārbaude diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index fe41c4529aa..8a6dabbceb2 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -753,7 +753,6 @@ visibility.private=Privé owner=Eigenaar owner_helper=Sommige organisaties kunnen niet worden weergegeven in de dropdown vanwege een limiet op het maximale aantal repositories. repo_name=Naam van repository -repo_name_helper=Goede repository-namen zijn kort, makkelijk te onthouden en uniek. repo_size=Repositorygrootte template=Sjabloon template_select=Selecteer een sjabloon. @@ -2055,6 +2054,7 @@ teams.all_repositories=Alle repositories teams.all_repositories_helper=Team heeft toegang tot alle repositories. Door dit te selecteren worden alle bestaande repositories aan het team toegevoegd. teams.all_repositories_read_permission_desc=Dit team heeft Lees toegang tot alle repositories: leden kunnen repositories bekijken en klonen. + [admin] dashboard=Overzicht users=Gebruikersacount diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 13c05eebe07..4d049c83d18 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -711,7 +711,6 @@ visibility.private=Prywatny owner=Właściciel owner_helper=Niektóre organizacje mogą nie pojawiać się w liście ze względu na limit maksymalnej liczby repozytoriów. repo_name=Nazwa repozytorium -repo_name_helper=Dobra nazwa repozytorium jest utworzona z krótkich, łatwych do zapamiętania i unikalnych słów kluczowych. repo_size=Rozmiar repozytorium template=Szablon template_select=Wybierz szablon. @@ -1934,6 +1933,7 @@ teams.all_repositories_read_permission_desc=Ten zespół nadaje uprawnienie Zapisu do wszystkich repozytoriów: jego członkowie mogą odczytywać i przesyłać do repozytoriów. teams.all_repositories_admin_permission_desc=Ten zespół nadaje uprawnienia Administratora do wszystkich repozytoriów: jego członkowie mogą odczytywać, przesyłać oraz dodawać innych współtwórców do repozytoriów. + [admin] dashboard=Pulpit users=Konta użytkownika diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 9a8b6aeb62c..f0c034a1331 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -908,7 +908,6 @@ new_repo_helper=Um repositório contém todos os arquivos do projeto, inclusive owner=Proprietário owner_helper=Algumas organizações podem não aparecer no menu devido a um limite de contagem dos repositórios. repo_name=Nome do repositório -repo_name_helper=Um bom nome de repositório é composto por palavras curtas, memorizáveis e únicas. repo_size=Tamanho do repositório template=Modelo template_select=Selecione um modelo. @@ -2551,6 +2550,7 @@ teams.invite.title=Você foi convidado para fazer parte da equipe %sdocumentação antes de alterar quaisquer configurações. require_db_desc=Gitea requer MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (protocolo MySQL). @@ -494,7 +495,7 @@ register_notify.text_3=Se esta conta foi criada para si, defina a s reset_password=Recupere a sua conta reset_password.title=%s, você pediu para recuperar a sua conta -reset_password.text=Por favor clique na seguinte ligação para recuperar a sua conta em %s: +reset_password.text=Para recuperar a sua conta, clique na ligação seguinte (válida por %s): register_success=Inscrição bem sucedida @@ -1015,7 +1016,9 @@ new_repo_helper=Um repositório contém todos os ficheiros do trabalho, incluind owner=Proprietário(a) owner_helper=Algumas organizações podem não aparecer na lista suspensa devido a um limite máximo de contagem de repositórios. repo_name=Nome do repositório -repo_name_helper=Um bom nome de repositório utiliza palavras curtas, memoráveis e únicas. +repo_name_profile_public_hint=.profile é um repositório especial que pode usar para adicionar README.md ao seu perfil público da organização, visível para qualquer pessoa. Certifique-se que é público e inicialize-o com um README na pasta do perfil para começar. +repo_name_profile_private_hint=.profile-private é um repositório especial que pode usar para adicionar um README.md ao seu perfil de membro da organização, visível apenas para membros da organização. Certifique-se que é privado e inicialize-o com um README na pasta de perfil para começar. +repo_name_helper=Bons nomes de repositórios usam palavras-chave curtas, memorizáveis e únicas. Um repositório chamado ".profile" ou ".profile-private" pode ser usado para adicionar um README.md ao perfil do utilizador ou da organização. repo_size=Tamanho do repositório template=Modelo template_select=Escolha um modelo. @@ -1232,6 +1235,7 @@ create_new_repo_command=Criando um novo repositório na linha de comandos push_exist_repo=Enviando, pela linha de comandos, um repositório existente empty_message=Este repositório não contém qualquer conteúdo. broken_message=Os dados Git subjacentes a este repositório não podem ser lidos. Contacte o administrador desta instância ou elimine este repositório. +no_branch=Este repositório não tem quaisquer ramos. code=Código code.desc=Aceder ao código fonte, ficheiros, cometimentos e ramos. @@ -2861,6 +2865,10 @@ teams.invite.title=Foi-lhe feito um convite para se juntar à equipa %s< teams.invite.by=Convidado(a) por %s teams.invite.description=Clique no botão abaixo para se juntar à equipa. +view_as_role=Ver como: %s +view_as_public_hint=Está a ver o README como um utilizador público. +view_as_member_hint=Está a ver o README como um membro desta organização. + [admin] maintenance=Manutenção dashboard=Painel de controlo @@ -3530,6 +3538,7 @@ versions=Versões versions.view_all=Ver todas dependency.id=ID dependency.version=Versão +search_in_external_registry=Procurar em %s alpine.registry=Configure este registo adicionando o URL no seu ficheiro /etc/apk/repositories: alpine.registry.key=Descarregue a chave RSA pública do registo para dentro da pasta /etc/apk/keys/ para verificar a assinatura do índice: alpine.registry.info=Escolha $branch e $repository da lista abaixo. diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index dc72dd7621b..027a2cb19d3 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -899,7 +899,6 @@ visibility.private_tooltip=Виден только членам организа owner=Владелец owner_helper=Некоторые организации могут не отображаться в раскрывающемся списке из-за максимального ограничения количества репозиториев. repo_name=Название репозитория -repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов. repo_size=Размер репозитория template=Шаблон template_select=Выбрать шаблон. @@ -2542,6 +2541,7 @@ teams.invite.title=Вас пригласили присоединиться к teams.invite.by=Приглашен(а) %s teams.invite.description=Нажмите на кнопку ниже, чтобы присоединиться к команде. + [admin] dashboard=Панель identity_access=Идентификация и доступ diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index f722d160eb5..167ecaf24a3 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -693,7 +693,6 @@ visibility.private=පෞද්ගලික owner=හිමිකරු owner_helper=උපරිම නිධි ගණන් සීමාවක් හේතුවෙන් සමහර සංවිධාන පහත වැටීමේ දී පෙන්විය නොහැක. repo_name=කෝෂ්ඨයේ නම -repo_name_helper=හොඳ ගබඩාවක් නම් කෙටි, අමතක නොවන සහ අද්විතීය මූල පද භාවිතා කරයි. repo_size=කෝෂ්ඨයේ ප්‍රමාණය template=සැකිල්ල template_select=අච්චුවක් තෝරන්න. @@ -1955,6 +1954,7 @@ teams.all_repositories_read_permission_desc=මෙම කණ්ඩායම ප teams.all_repositories_write_permission_desc=මෙම කණ්ඩායම ප්රදානය කරයි වෙත ප්රවේශය ලියන්න සියලු ගබඩාවන්ට: සාමාජිකයින්ට කියවීමට සහ ගබඩාවන්ට තල්ලු කළ හැකිය. teams.all_repositories_admin_permission_desc=මෙම කණ්ඩායම ප්රදානය කරයි පරිපාලක වෙත ප්රවේශය සියලු ගබඩාවන්ට: සාමාජිකයින්ට කියවීමට, තල්ලු කිරීමට සහ ගබඩාවන්ට සහයෝගීකයින් එකතු කිරීමට. + [admin] dashboard=උපකරණ පුවරුව users=පරිශීලක ගිණුම් diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index 39c13c358ef..43b190098ff 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -817,7 +817,6 @@ visibility.private=Súkromný owner=Vlastník owner_helper=Niektoré organizácie sa nemusia zobraziť v rozbaľovacej ponuke z dôvodu maximálneho limitu počtu repozitárov. repo_name=Názov repozitára -repo_name_helper=Dobrý názov repozitára sa zvyčajne skladá z krátkych, jedinečných a ľahko zapamätateľných kľúčových slov. repo_size=Veľkosť repozitára template=Šablóna template_select=Vyberte šablónu. @@ -1235,6 +1234,7 @@ teams.all_repositories_read_permission_desc=Tomuto tímu je pridelený prístup teams.all_repositories_write_permission_desc=Tomuto tímu je pridelený prístup na Zápis do všetkých repozitárov: členovia môžu prezerať a nahrávať do repozitárov. teams.all_repositories_admin_permission_desc=Tomuto tímu je pridelený Admin prístup ku všetkým repozitárom: členovia môžu prezerať, nahrávať do repozitárov a pridávať do nich spolupracovníkov. + [admin] repositories=Repozitáre hooks=Webhooky diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 2fd8277b76f..0315ebe9a11 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -598,7 +598,6 @@ visibility.private=Privat [repo] owner=Ägare repo_name=Utvecklingskatalogens namn -repo_name_helper=Bra namn på utvecklingskataloger består utav korta, unika nyckelord som är enkla att komma ihåg. repo_size=Utvecklingskatalogens storlek template=Mall template_select=Välj mall. @@ -1592,6 +1591,7 @@ teams.all_repositories_read_permission_desc=Detta team beviljar LäsSkriv-rättigheter till alla utvecklingskataloger: medlemmar kan läsa från och pusha till utvecklingskataloger. teams.all_repositories_admin_permission_desc=Detta team beviljar Admin-rättigheter till alla utvecklingskataloger: medlemmar kan läsa från, pusha till och lägga till kollaboratörer för utvecklingskatalogerna. + [admin] dashboard=Instrumentpanel users=Användarkonto diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 0c3884fd648..fb30ab3b67b 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -104,6 +104,7 @@ copy_url=URL'yi kopyala copy_hash=Hash'i kopyala copy_content=İçeriği kopyala copy_branch=Dal adını kopyala +copy_path=Yolu kopyala copy_success=Kopyalandı! copy_error=Kopyalama başarısız oldu copy_type_unsupported=Bu dosya türü kopyalanamaz @@ -144,6 +145,7 @@ confirm_delete_selected=Tüm seçili öğeleri gerçekten silmek istiyor musunuz name=İsim value=Değer +readme=Benioku filter=Filtre filter.clear=Filtreyi Temizle @@ -159,6 +161,7 @@ filter.public=Genel filter.private=Özel no_results_found=Sonuç bulunamadı. +internal_error_skipped=Dahili bir hata oluştu ama atlandı: %s [search] search=Ara... @@ -177,6 +180,8 @@ code_search_by_git_grep=Mevcut kod arama sonuçları "git grep" ile sağlanıyor package_kind=Paketleri ara... project_kind=Projeleri ara... branch_kind=Dalları ara... +tag_kind=Etiketleri ara... +tag_tooltip=Eşleşen etiketler için arama. Herhangi bir numara serisi bulmak için '%' kullanın. commit_kind=İşlemeleri ara... runner_kind=Çalıştırıcıları ara... no_results=Eşleşen sonuç bulunamadı. @@ -206,7 +211,10 @@ buttons.link.tooltip=Bağlantı ekle buttons.list.unordered.tooltip=Maddeli liste ekle buttons.list.ordered.tooltip=Numaralandırılmış liste ekle buttons.list.task.tooltip=Görev listesi ekle +buttons.table.add.tooltip=Tablo ekle buttons.table.add.insert=Ekle +buttons.table.rows=Satırlar +buttons.table.cols=Sütunlar buttons.mention.tooltip=Bir kişiye veya takıma değin buttons.ref.tooltip=Bir konuya veya değişiklik isteğine değin buttons.switch_to_legacy.tooltip=Eski düzenleyiciyi kullan @@ -219,6 +227,7 @@ string.desc=Z - A [error] occurred=Bir hata oluştu +report_message=Bunun bir Gitea hatası olduğunu düşünüyorsanız, lütfen GitHub sayfasında sorunu arayın veya gerekiyorsa yeni bir sorun oluşturun. not_found=Hedef bulunamadı. network_error=Ağ hatası @@ -984,7 +993,6 @@ new_repo_helper=Bir depo, sürüm geçmişi dahil tüm proje dosyalarını içer owner=Sahibi owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir. repo_name=Depo İsmi -repo_name_helper=İyi bir depo ismi kısa, akılda kalıcı ve özgün anahtar kelimelerden oluşur. repo_size=Depo Boyutu template=Şablon template_select=Bir şablon seçin. @@ -2747,6 +2755,7 @@ teams.invite.title=%s takımına (Organizasyon: %sЗапис для всіх репозиторіїв: учасники можуть переглядати та виконувати push в репозиторіях. teams.all_repositories_admin_permission_desc=Ця команда надає дозвіл Адміністрування для всіх репозиторіїв: учасники можуть переглядати, виконувати push та додавати співробітників. + [admin] dashboard=Панель управління users=Облікові записи користувачів diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 572ad2d667c..5e4723a4cd8 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1015,7 +1015,6 @@ new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史 owner=拥有者 owner_helper=由于最大仓库数量限制,一些组织可能不会显示在下拉列表中。 repo_name=仓库名称 -repo_name_helper=好的仓库名称应当使用简短、有意义和独特的关键字。 repo_size=仓库大小 template=模板 template_select=选择模板 @@ -2861,6 +2860,7 @@ teams.invite.title=您已被邀请加入组织 %s 中的团队 teams.invite.by=邀请人 %s teams.invite.description=请点击下面的按钮加入团队。 + [admin] maintenance=维护 dashboard=管理面板 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index e1ca6ebfc83..77f8d8a25d8 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -684,6 +684,7 @@ teams.add_team_member=新增團隊成員 teams.delete_team_success=該團隊已被刪除。 teams.repositories=團隊儲存庫 + [admin] dashboard=控制面版 organizations=組織管理 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index a3bf6ca8886..948b47bc9cb 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -1012,7 +1012,6 @@ new_repo_helper=儲存庫包含所有專案檔案,包括修訂歷史。已經 owner=擁有者 owner_helper=組織可能因為儲存庫數量上限而未列入此選單。 repo_name=儲存庫名稱 -repo_name_helper=好的儲存庫名稱通常是簡短的、好記的、且獨特的。 repo_size=儲存庫大小 template=範本 template_select=選擇範本 @@ -2852,6 +2851,7 @@ teams.invite.title=您已被邀請加入組織 %s 中的團隊 teams.invite.by=邀請人 %s teams.invite.description=請點擊下方按鈕加入團隊。 + [admin] maintenance=維護 dashboard=資訊主頁 diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go index 5ea86071a9e..19aa54628d2 100644 --- a/routers/api/packages/pypi/pypi.go +++ b/routers/api/packages/pypi/pypi.go @@ -10,6 +10,7 @@ import ( "regexp" "sort" "strings" + "unicode" packages_model "code.gitea.io/gitea/models/packages" packages_module "code.gitea.io/gitea/modules/packages" @@ -139,9 +140,30 @@ func UploadPackageFile(ctx *context.Context) { return } - projectURL := ctx.Req.FormValue("home_page") - if !validation.IsValidURL(projectURL) { - projectURL = "" + // Ensure ctx.Req.Form exists. + _ = ctx.Req.ParseForm() + + var homepageURL string + projectURLs := ctx.Req.Form["project_urls"] + for _, purl := range projectURLs { + label, url, found := strings.Cut(purl, ",") + if !found { + continue + } + if normalizeLabel(label) != "homepage" { + continue + } + homepageURL = strings.TrimSpace(url) + break + } + + if len(homepageURL) == 0 { + // TODO: Home-page is a deprecated metadata field. Remove this branch once it's no longer apart of the spec. + homepageURL = ctx.Req.FormValue("home_page") + } + + if !validation.IsValidURL(homepageURL) { + homepageURL = "" } _, _, err = packages_service.CreatePackageOrAddFileToExisting( @@ -160,7 +182,7 @@ func UploadPackageFile(ctx *context.Context) { Description: ctx.Req.FormValue("description"), LongDescription: ctx.Req.FormValue("long_description"), Summary: ctx.Req.FormValue("summary"), - ProjectURL: projectURL, + ProjectURL: homepageURL, License: ctx.Req.FormValue("license"), RequiresPython: ctx.Req.FormValue("requires_python"), }, @@ -189,6 +211,23 @@ func UploadPackageFile(ctx *context.Context) { ctx.Status(http.StatusCreated) } +// Normalizes a Project-URL label. +// See https://packaging.python.org/en/latest/specifications/well-known-project-urls/#label-normalization. +func normalizeLabel(label string) string { + var builder strings.Builder + + // "A label is normalized by deleting all ASCII punctuation and whitespace, and then converting the result + // to lowercase." + for _, r := range label { + if unicode.IsPunct(r) || unicode.IsSpace(r) { + continue + } + builder.WriteRune(unicode.ToLower(r)) + } + + return builder.String() +} + func isValidNameAndVersion(packageName, packageVersion string) bool { return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion) } diff --git a/routers/api/packages/pypi/pypi_test.go b/routers/api/packages/pypi/pypi_test.go index 3023692177f..786105693f1 100644 --- a/routers/api/packages/pypi/pypi_test.go +++ b/routers/api/packages/pypi/pypi_test.go @@ -36,3 +36,13 @@ func TestIsValidNameAndVersion(t *testing.T) { assert.False(t, isValidNameAndVersion("test-name", "1.0.1aa")) assert.False(t, isValidNameAndVersion("test-name", "1.0.0-alpha.beta")) } + +func TestNormalizeLabel(t *testing.T) { + // Cases fetched from https://packaging.python.org/en/latest/specifications/well-known-project-urls/#label-normalization. + assert.Equal(t, "homepage", normalizeLabel("Homepage")) + assert.Equal(t, "homepage", normalizeLabel("Home-page")) + assert.Equal(t, "homepage", normalizeLabel("Home page")) + assert.Equal(t, "changelog", normalizeLabel("Change_Log")) + assert.Equal(t, "whatsnew", normalizeLabel("What's New?")) + assert.Equal(t, "github", normalizeLabel("github")) +} diff --git a/routers/common/codesearch.go b/routers/common/codesearch.go new file mode 100644 index 00000000000..a14af126e55 --- /dev/null +++ b/routers/common/codesearch.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/context" +) + +func PrepareCodeSearch(ctx *context.Context) (ret struct { + Keyword string + Language string + IsFuzzy bool +}, +) { + ret.Language = ctx.FormTrim("l") + ret.Keyword = ctx.FormTrim("q") + + fuzzyDefault := setting.Indexer.RepoIndexerEnabled + fuzzyAllow := true + if setting.Indexer.RepoType == "bleve" && setting.Indexer.TypeBleveMaxFuzzniess == 0 { + fuzzyDefault = false + fuzzyAllow = false + } + isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(fuzzyDefault) + if isFuzzy && !fuzzyAllow { + ctx.Flash.Info("Fuzzy search is disabled by default due to performance reasons") + isFuzzy = false + } + + ctx.Data["IsBleveFuzzyDisabled"] = true + ctx.Data["Keyword"] = ret.Keyword + ctx.Data["Language"] = ret.Language + ctx.Data["IsFuzzy"] = isFuzzy + + ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled + return ret +} diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index be2ba4424cd..f6a3af1c866 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -313,6 +313,8 @@ func editUserCommon(ctx *context.Context) { ctx.Data["PageIsAdminUsers"] = true ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations + ctx.Data["DisableGitHooks"] = setting.DisableGitHooks + ctx.Data["DisableImportLocal"] = !setting.ImportLocalPaths ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) } diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 36581446109..ae5ff3db761 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -11,6 +11,7 @@ import ( code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/context" ) @@ -32,18 +33,10 @@ func Code(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExploreCode"] = true - - language := ctx.FormTrim("l") - keyword := ctx.FormTrim("q") - - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) - - ctx.Data["Keyword"] = keyword - ctx.Data["Language"] = language - ctx.Data["IsFuzzy"] = isFuzzy ctx.Data["PageIsViewCode"] = true - if keyword == "" { + prepareSearch := common.PrepareCodeSearch(ctx) + if prepareSearch.Keyword == "" { ctx.HTML(http.StatusOK, tplExploreCode) return } @@ -80,9 +73,9 @@ func Code(ctx *context.Context) { if (len(repoIDs) > 0) || isAdmin { total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{ RepoIDs: repoIDs, - Keyword: keyword, - IsKeywordFuzzy: isFuzzy, - Language: language, + Keyword: prepareSearch.Keyword, + IsKeywordFuzzy: prepareSearch.IsFuzzy, + Language: prepareSearch.Language, Paginator: &db.ListOptions{ Page: page, PageSize: setting.UI.RepoSearchPagingNum, diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index f0d8d81fee5..539c4b6ed00 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -32,8 +32,9 @@ import ( ) const ( - tplListActions templates.TplName = "repo/actions/list" - tplViewActions templates.TplName = "repo/actions/view" + tplListActions templates.TplName = "repo/actions/list" + tplDispatchInputsActions templates.TplName = "repo/actions/workflow_dispatch_inputs" + tplViewActions templates.TplName = "repo/actions/view" ) type Workflow struct { @@ -64,107 +65,143 @@ func MustEnableActions(ctx *context.Context) { func List(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.actions") ctx.Data["PageIsActions"] = true + + commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) + if err != nil { + ctx.ServerError("GetBranchCommit", err) + return + } + + workflows := prepareWorkflowDispatchTemplate(ctx, commit) + if ctx.Written() { + return + } + + prepareWorkflowList(ctx, workflows) + if ctx.Written() { + return + } + + ctx.HTML(http.StatusOK, tplListActions) +} + +func WorkflowDispatchInputs(ctx *context.Context) { + ref := ctx.FormString("ref") + if ref == "" { + ctx.NotFound("WorkflowDispatchInputs: no ref", nil) + return + } + // get target commit of run from specified ref + refName := git.RefName(ref) + var commit *git.Commit + var err error + if refName.IsTag() { + commit, err = ctx.Repo.GitRepo.GetTagCommit(refName.TagName()) + } else if refName.IsBranch() { + commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName.BranchName()) + } else { + ctx.ServerError("UnsupportedRefType", nil) + return + } + if err != nil { + ctx.ServerError("GetTagCommit/GetBranchCommit", err) + return + } + prepareWorkflowDispatchTemplate(ctx, commit) + if ctx.Written() { + return + } + ctx.HTML(http.StatusOK, tplDispatchInputsActions) +} + +func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (workflows []Workflow) { workflowID := ctx.FormString("workflow") - actorID := ctx.FormInt64("actor") - status := ctx.FormInt("status") ctx.Data["CurWorkflow"] = workflowID + ctx.Data["CurWorkflowExists"] = false - var workflows []Workflow var curWorkflow *model.Workflow - if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil { - ctx.ServerError("IsEmpty", err) - return - } else if !empty { - commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return - } - entries, err := actions.ListWorkflows(commit) - if err != nil { - ctx.ServerError("ListWorkflows", err) - return - } - // Get all runner labels - runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{ - RepoID: ctx.Repo.Repository.ID, - IsOnline: optional.Some(true), - WithAvailable: true, - }) + entries, err := actions.ListWorkflows(commit) + if err != nil { + ctx.ServerError("ListWorkflows", err) + return nil + } + + // Get all runner labels + runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{ + RepoID: ctx.Repo.Repository.ID, + IsOnline: optional.Some(true), + WithAvailable: true, + }) + if err != nil { + ctx.ServerError("FindRunners", err) + return nil + } + allRunnerLabels := make(container.Set[string]) + for _, r := range runners { + allRunnerLabels.AddMultiple(r.AgentLabels...) + } + + workflows = make([]Workflow, 0, len(entries)) + for _, entry := range entries { + workflow := Workflow{Entry: *entry} + content, err := actions.GetContentFromEntry(entry) if err != nil { - ctx.ServerError("FindRunners", err) - return + ctx.ServerError("GetContentFromEntry", err) + return nil } - allRunnerLabels := make(container.Set[string]) - for _, r := range runners { - allRunnerLabels.AddMultiple(r.AgentLabels...) + wf, err := model.ReadWorkflow(bytes.NewReader(content)) + if err != nil { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) + workflows = append(workflows, workflow) + continue } - - workflows = make([]Workflow, 0, len(entries)) - for _, entry := range entries { - workflow := Workflow{Entry: *entry} - content, err := actions.GetContentFromEntry(entry) - if err != nil { - ctx.ServerError("GetContentFromEntry", err) - return - } - wf, err := model.ReadWorkflow(bytes.NewReader(content)) - if err != nil { - workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) - workflows = append(workflows, workflow) + // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run. + hasJobWithoutNeeds := false + // Check whether you have matching runner and a job without "needs" + emptyJobsNumber := 0 + for _, j := range wf.Jobs { + if j == nil { + emptyJobsNumber++ continue } - // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run. - hasJobWithoutNeeds := false - // Check whether have matching runner and a job without "needs" - emptyJobsNumber := 0 - for _, j := range wf.Jobs { - if j == nil { - emptyJobsNumber++ + if !hasJobWithoutNeeds && len(j.Needs()) == 0 { + hasJobWithoutNeeds = true + } + runsOnList := j.RunsOn() + for _, ro := range runsOnList { + if strings.Contains(ro, "${{") { + // Skip if it contains expressions. + // The expressions could be very complex and could not be evaluated here, + // so just skip it, it's OK since it's just a tooltip message. continue } - if !hasJobWithoutNeeds && len(j.Needs()) == 0 { - hasJobWithoutNeeds = true - } - runsOnList := j.RunsOn() - for _, ro := range runsOnList { - if strings.Contains(ro, "${{") { - // Skip if it contains expressions. - // The expressions could be very complex and could not be evaluated here, - // so just skip it, it's OK since it's just a tooltip message. - continue - } - if !allRunnerLabels.Contains(ro) { - workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro) - break - } - } - if workflow.ErrMsg != "" { + if !allRunnerLabels.Contains(ro) { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro) break } } - if !hasJobWithoutNeeds { - workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") - } - if emptyJobsNumber == len(wf.Jobs) { - workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job") + if workflow.ErrMsg != "" { + break } - workflows = append(workflows, workflow) + } + if !hasJobWithoutNeeds { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") + } + if emptyJobsNumber == len(wf.Jobs) { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job") + } + workflows = append(workflows, workflow) - if workflow.Entry.Name() == workflowID { - curWorkflow = wf - } + if workflow.Entry.Name() == workflowID { + curWorkflow = wf + ctx.Data["CurWorkflowExists"] = true } } + ctx.Data["workflows"] = workflows ctx.Data["RepoLink"] = ctx.Repo.Repository.Link() - page := ctx.FormInt("page") - if page <= 0 { - page = 1 - } - actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() ctx.Data["ActionsConfig"] = actionsConfig @@ -188,7 +225,7 @@ func List(ctx *context.Context) { branches, err := git_model.FindBranchNames(ctx, branchOpts) if err != nil { ctx.ServerError("FindBranchNames", err) - return + return nil } // always put default branch on the top if it exists if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) { @@ -200,12 +237,23 @@ func List(ctx *context.Context) { tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("GetTagNamesByRepoID", err) - return + return nil } ctx.Data["Tags"] = tags } } } + return workflows +} + +func prepareWorkflowList(ctx *context.Context, workflows []Workflow) { + actorID := ctx.FormInt64("actor") + status := ctx.FormInt("status") + workflowID := ctx.FormString("workflow") + page := ctx.FormInt("page") + if page <= 0 { + page = 1 + } // if status or actor query param is not given to frontend href, (href="//actions") // they will be 0 by default, which indicates get all status or actors @@ -264,8 +312,6 @@ func List(ctx *context.Context) { pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0 - - ctx.HTML(http.StatusOK, tplListActions) } // loadIsRefDeleted loads the IsRefDeleted field for each run in the list. diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index ba17fa427d1..9a18ca53058 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -812,13 +812,8 @@ func Run(ctx *context_module.Context) { return } - // get workflow entry from default branch commit - defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) - if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) - return - } - entries, err := actions.ListWorkflows(defaultBranchCommit) + // get workflow entry from runTargetCommit + entries, err := actions.ListWorkflows(runTargetCommit) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 85a745acbd3..0f408b22e09 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -147,27 +147,33 @@ func getRepoPrivate(ctx *context.Context) bool { } } -// Create render creating repository page -func Create(ctx *context.Context) { +func createCommon(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_repo") - - // Give default value for template to render. ctx.Data["Gitignores"] = repo_module.Gitignores ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles ctx.Data["Licenses"] = repo_module.Licenses ctx.Data["Readmes"] = repo_module.Readmes - ctx.Data["readme"] = "Default" - ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - ctx.Data["default_branch"] = setting.Repository.DefaultBranch + ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() + ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() + ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats + ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat +} +// Create render creating repository page +func Create(ctx *context.Context) { + createCommon(ctx) ctxUser := checkContextUser(ctx, ctx.FormInt64("org")) if ctx.Written() { return } ctx.Data["ContextUser"] = ctxUser + ctx.Data["readme"] = "Default" + ctx.Data["private"] = getRepoPrivate(ctx) + ctx.Data["default_branch"] = setting.Repository.DefaultBranch ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select") + templateID := ctx.FormInt64("template_id") if templateID > 0 { templateRepo, err := repo_model.GetRepositoryByID(ctx, templateID) @@ -177,11 +183,6 @@ func Create(ctx *context.Context) { } } - ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() - ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() - ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats - ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat - ctx.HTML(http.StatusOK, tplCreate) } @@ -219,16 +220,8 @@ func handleCreateError(ctx *context.Context, owner *user_model.User, err error, // CreatePost response for creating repository func CreatePost(ctx *context.Context) { + createCommon(ctx) form := web.GetForm(ctx).(*forms.CreateRepoForm) - ctx.Data["Title"] = ctx.Tr("new_repo") - - ctx.Data["Gitignores"] = repo_module.Gitignores - ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles - ctx.Data["Licenses"] = repo_module.Licenses - ctx.Data["Readmes"] = repo_module.Readmes - - ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() - ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() ctxUser := checkContextUser(ctx, form.UID) if ctx.Written() { @@ -236,6 +229,14 @@ func CreatePost(ctx *context.Context) { } ctx.Data["ContextUser"] = ctxUser + if form.RepoTemplate > 0 { + templateRepo, err := repo_model.GetRepositoryByID(ctx, form.RepoTemplate) + if err == nil && access_model.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) { + ctx.Data["repo_template"] = form.RepoTemplate + ctx.Data["repo_template_name"] = templateRepo.Name + } + } + if ctx.HasError() { ctx.HTML(http.StatusOK, tplCreate) return diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go index cbc7e2e0fe8..c60301475f3 100644 --- a/routers/web/repo/search.go +++ b/routers/web/repo/search.go @@ -12,6 +12,7 @@ import ( code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/context" ) @@ -29,18 +30,9 @@ func indexSettingToGitGrepPathspecList() (list []string) { // Search render repository search page func Search(ctx *context.Context) { - language := ctx.FormTrim("l") - keyword := ctx.FormTrim("q") - - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) - - ctx.Data["Keyword"] = keyword - ctx.Data["Language"] = language - ctx.Data["IsFuzzy"] = isFuzzy ctx.Data["PageIsViewCode"] = true - ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled - - if keyword == "" { + prepareSearch := common.PrepareCodeSearch(ctx) + if prepareSearch.Keyword == "" { ctx.HTML(http.StatusOK, tplSearch) return } @@ -57,9 +49,9 @@ func Search(ctx *context.Context) { var err error total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{ RepoIDs: []int64{ctx.Repo.Repository.ID}, - Keyword: keyword, - IsKeywordFuzzy: isFuzzy, - Language: language, + Keyword: prepareSearch.Keyword, + IsKeywordFuzzy: prepareSearch.IsFuzzy, + Language: prepareSearch.Language, Paginator: &db.ListOptions{ Page: page, PageSize: setting.UI.RepoSearchPagingNum, @@ -75,9 +67,9 @@ func Search(ctx *context.Context) { ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) } } else { - res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ + res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, prepareSearch.Keyword, git.GrepOptions{ ContextLineNumber: 1, - IsFuzzy: isFuzzy, + IsFuzzy: prepareSearch.IsFuzzy, RefName: git.RefNameFromBranch(ctx.Repo.BranchName).String(), // BranchName should be default branch or the first existing branch PathspecList: indexSettingToGitGrepPathspecList(), }) diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index 70ba07f9a89..f890225d67b 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -223,16 +223,38 @@ func prepareRecentlyPushedNewBranches(ctx *context.Context) { } } +func updateContextRepoEmptyAndStatus(ctx *context.Context, empty bool, status repo_model.RepositoryStatus) { + ctx.Repo.Repository.IsEmpty = empty + if ctx.Repo.Repository.Status == repo_model.RepositoryReady || ctx.Repo.Repository.Status == repo_model.RepositoryBroken { + ctx.Repo.Repository.Status = status // only handle ready and broken status, leave other status as-is + } + if err := repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty", "status"); err != nil { + ctx.ServerError("updateContextRepoEmptyAndStatus: UpdateRepositoryCols", err) + return + } +} + func handleRepoEmptyOrBroken(ctx *context.Context) { showEmpty := true - var err error if ctx.Repo.GitRepo != nil { - showEmpty, err = ctx.Repo.GitRepo.IsEmpty() + reallyEmpty, err := ctx.Repo.GitRepo.IsEmpty() if err != nil { + showEmpty = true // the repo is broken + updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryBroken) log.Error("GitRepo.IsEmpty: %v", err) - ctx.Repo.Repository.Status = repo_model.RepositoryBroken - showEmpty = true ctx.Flash.Error(ctx.Tr("error.occurred"), true) + } else if reallyEmpty { + showEmpty = true // the repo is really empty + updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady) + } else if ctx.Repo.Commit == nil { + showEmpty = true // it is not really empty, but there is no branch + // at the moment, other repo units like "actions" are not able to handle such case, + // so we just mark the repo as empty to prevent from displaying these units. + ctx.Data["RepoHasContentsWithoutBranch"] = true + updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady) + } else { + // the repo is actually not empty and has branches, need to update the database later + showEmpty = false } } if showEmpty { @@ -240,18 +262,11 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { return } - // the repo is not really empty, so we should update the modal in database - // such problem may be caused by: - // 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually - // and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos. - // it's possible for a repository to be non-empty by that flag but still 500 - // because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed. - ctx.Repo.Repository.IsEmpty = false - if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil { - ctx.ServerError("UpdateRepositoryCols", err) - return - } - if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil { + // The repo is not really empty, so we should update the model in database, such problem may be caused by: + // 1) an error occurs during pushing/receiving. + // 2) the user replaces an empty git repo manually. + updateContextRepoEmptyAndStatus(ctx, false, repo_model.RepositoryReady) + if err := repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil { ctx.ServerError("UpdateRepoSize", err) return } diff --git a/routers/web/user/code.go b/routers/web/user/code.go index 9d515596f11..665ce1a6a6f 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -11,6 +11,7 @@ import ( code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/routers/common" shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/context" ) @@ -34,20 +35,11 @@ func CodeSearch(ctx *context.Context) { } ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled - ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["Title"] = ctx.Tr("explore.code") - - language := ctx.FormTrim("l") - keyword := ctx.FormTrim("q") - - isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true) - - ctx.Data["Keyword"] = keyword - ctx.Data["Language"] = language - ctx.Data["IsFuzzy"] = isFuzzy ctx.Data["IsCodePage"] = true - if keyword == "" { + prepareSearch := common.PrepareCodeSearch(ctx) + if prepareSearch.Keyword == "" { ctx.HTML(http.StatusOK, tplUserCode) return } @@ -77,9 +69,9 @@ func CodeSearch(ctx *context.Context) { if len(repoIDs) > 0 { total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{ RepoIDs: repoIDs, - Keyword: keyword, - IsKeywordFuzzy: isFuzzy, - Language: language, + Keyword: prepareSearch.Keyword, + IsKeywordFuzzy: prepareSearch.IsFuzzy, + Language: prepareSearch.Language, Paginator: &db.ListOptions{ Page: page, PageSize: setting.UI.RepoSearchPagingNum, diff --git a/routers/web/web.go b/routers/web/web.go index 5e0995545e8..ff91bda3d2e 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1412,6 +1412,7 @@ func registerRoutes(m *web.Router) { m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile) m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) m.Post("/run", reqRepoActionsWriter, actions.Run) + m.Get("/workflow-dispatch-inputs", reqRepoActionsWriter, actions.WorkflowDispatchInputs) m.Group("/runs/{run}", func() { m.Combo(""). @@ -1433,7 +1434,7 @@ func registerRoutes(m *web.Router) { m.Group("/workflows/{workflow_name}", func() { m.Get("/badge.svg", actions.GetWorkflowBadge) }) - }, optSignIn, context.RepoAssignment, reqRepoActionsReader, actions.MustEnableActions) + }, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoActionsReader, actions.MustEnableActions) // end "/{username}/{reponame}/actions" m.Group("/{username}/{reponame}/wiki", func() { diff --git a/services/context/repo.go b/services/context/repo.go index 2a473f4a541..63529e1d811 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -897,10 +897,8 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func refName = brs[0].Name } else if len(brs) == 0 { log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) - ctx.Repo.Repository.MarkAsBrokenEmpty() } else { log.Error("GetBranches error: %v", err) - ctx.Repo.Repository.MarkAsBrokenEmpty() } } ctx.Repo.RefName = refName @@ -911,7 +909,6 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func } else if strings.Contains(err.Error(), "fatal: not a git repository") || strings.Contains(err.Error(), "object does not exist") { // if the repository is broken, we can continue to the handler code, to show "Settings -> Delete Repository" for end users log.Error("GetBranchCommit: %v", err) - ctx.Repo.Repository.MarkAsBrokenEmpty() } else { ctx.ServerError("GetBranchCommit", err) return diff --git a/services/repository/generate.go b/services/repository/generate.go index 24cf9d1b9bf..ef9a8dc9406 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -9,7 +9,6 @@ import ( "context" "fmt" "os" - "path" "path/filepath" "regexp" "strconv" @@ -123,7 +122,7 @@ func (gt *GiteaTemplate) Globs() []glob.Glob { return gt.globs } -func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { +func readGiteaTemplateFile(tmpDir string) (*GiteaTemplate, error) { gtPath := filepath.Join(tmpDir, ".gitea", "template") if _, err := os.Stat(gtPath); os.IsNotExist(err) { return nil, nil @@ -136,12 +135,55 @@ func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { return nil, err } - gt := &GiteaTemplate{ - Path: gtPath, - Content: content, + return &GiteaTemplate{Path: gtPath, Content: content}, nil +} + +func processGiteaTemplateFile(tmpDir string, templateRepo, generateRepo *repo_model.Repository, giteaTemplateFile *GiteaTemplate) error { + if err := util.Remove(giteaTemplateFile.Path); err != nil { + return fmt.Errorf("remove .giteatemplate: %w", err) } + if len(giteaTemplateFile.Globs()) == 0 { + return nil // Avoid walking tree if there are no globs + } + tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" + return filepath.WalkDir(tmpDirSlash, func(path string, d os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if d.IsDir() { + return nil + } + + base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) + for _, g := range giteaTemplateFile.Globs() { + if g.Match(base) { + content, err := os.ReadFile(path) + if err != nil { + return err + } - return gt, nil + generatedContent := []byte(generateExpansion(string(content), templateRepo, generateRepo, false)) + if err := os.WriteFile(path, generatedContent, 0o644); err != nil { + return err + } + + substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, generateExpansion(base, templateRepo, generateRepo, true))) + + // Create parent subdirectories if needed or continue silently if it exists + if err = os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil { + return err + } + + // Substitute filename variables + if err = os.Rename(path, substPath); err != nil { + return err + } + break + } + } + return nil + }) // end: WalkDir } func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository, tmpDir string) error { @@ -167,81 +209,43 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r return fmt.Errorf("git clone: %w", err) } - if err := util.RemoveAll(path.Join(tmpDir, ".git")); err != nil { + // Get active submodules from the template + submodules, err := git.GetTemplateSubmoduleCommits(ctx, tmpDir) + if err != nil { + return fmt.Errorf("GetTemplateSubmoduleCommits: %w", err) + } + + if err = util.RemoveAll(filepath.Join(tmpDir, ".git")); err != nil { return fmt.Errorf("remove git dir: %w", err) } // Variable expansion - gt, err := checkGiteaTemplate(tmpDir) + giteaTemplateFile, err := readGiteaTemplateFile(tmpDir) if err != nil { - return fmt.Errorf("checkGiteaTemplate: %w", err) + return fmt.Errorf("readGiteaTemplateFile: %w", err) } - if gt != nil { - if err := util.Remove(gt.Path); err != nil { - return fmt.Errorf("remove .giteatemplate: %w", err) - } - - // Avoid walking tree if there are no globs - if len(gt.Globs()) > 0 { - tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" - if err := filepath.WalkDir(tmpDirSlash, func(path string, d os.DirEntry, walkErr error) error { - if walkErr != nil { - return walkErr - } - - if d.IsDir() { - return nil - } - - base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) - for _, g := range gt.Globs() { - if g.Match(base) { - content, err := os.ReadFile(path) - if err != nil { - return err - } - - if err := os.WriteFile(path, - []byte(generateExpansion(string(content), templateRepo, generateRepo, false)), - 0o644); err != nil { - return err - } - - substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, - generateExpansion(base, templateRepo, generateRepo, true))) - - // Create parent subdirectories if needed or continue silently if it exists - if err := os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil { - return err - } - - // Substitute filename variables - if err := os.Rename(path, substPath); err != nil { - return err - } - - break - } - } - return nil - }); err != nil { - return err - } + if giteaTemplateFile != nil { + err = processGiteaTemplateFile(tmpDir, templateRepo, generateRepo, giteaTemplateFile) + if err != nil { + return err } } - if err := git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil { + if err = git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil { return err } - repoPath := repo.RepoPath() - if stdout, _, err := git.NewCommand(ctx, "remote", "add", "origin").AddDynamicArguments(repoPath). + if stdout, _, err := git.NewCommand(ctx, "remote", "add", "origin").AddDynamicArguments(repo.RepoPath()). RunStdString(&git.RunOpts{Dir: tmpDir, Env: env}); err != nil { log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git remote add: %w", err) } + if err = git.AddTemplateSubmoduleIndexes(ctx, tmpDir, submodules); err != nil { + return fmt.Errorf("failed to add submodules: %v", err) + } + // set default branch based on whether it's specified in the newly generated repo or not defaultBranch := repo.DefaultBranch if strings.TrimSpace(defaultBranch) == "" { diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 41b00defb45..d591a645d89 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -128,16 +128,16 @@ -
+
- +
-
+
- +
{{if not .DisableRegularOrgCreation}} diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index ed7a8d6f243..baf37494b98 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -70,7 +70,7 @@ {{svg "octicon-triangle-down"}}
diff --git a/templates/package/content/nuget.tmpl b/templates/package/content/nuget.tmpl index 5bb98a86ddf..4a4ea8ca436 100644 --- a/templates/package/content/nuget.tmpl +++ b/templates/package/content/nuget.tmpl @@ -35,11 +35,12 @@ + {{$tooltipSearchInNuget := ctx.Locale.Tr "packages.search_in_external_registry" "nuget.org"}} {{range $framework, $dependencies := .PackageDescriptor.Metadata.Dependencies}} {{range $dependencies}} - {{.ID}} - {{.Version}} + {{.ID}} {{svg "octicon-link-external"}} + {{.Version}} {{svg "octicon-link-external"}} {{$framework}} {{end}} diff --git a/templates/repo/actions/workflow_dispatch.tmpl b/templates/repo/actions/workflow_dispatch.tmpl index 21f3ef20772..55fe1224194 100644 --- a/templates/repo/actions/workflow_dispatch.tmpl +++ b/templates/repo/actions/workflow_dispatch.tmpl @@ -11,7 +11,7 @@ diff --git a/templates/repo/actions/workflow_dispatch_inputs.tmpl b/templates/repo/actions/workflow_dispatch_inputs.tmpl new file mode 100644 index 00000000000..8b8292af1d8 --- /dev/null +++ b/templates/repo/actions/workflow_dispatch_inputs.tmpl @@ -0,0 +1,45 @@ +{{if not .WorkflowDispatchConfig}} +
{{/* using "ui message" in "ui form" needs to force to display */}} + {{if not .CurWorkflowExists}} + {{ctx.Locale.Tr "actions.workflow.not_found" $.CurWorkflow}} + {{else}} + {{ctx.Locale.Tr "actions.workflow.has_no_workflow_dispatch" $.CurWorkflow}} + {{end}} +
+{{else}} + {{range $item := .WorkflowDispatchConfig.Inputs}} +
+ {{if eq .Type "choice"}} + + {{/* htmx won't initialize the fomantic dropdown, so it is a standard "select" input */}} + + {{else if eq .Type "boolean"}} + {{/* htmx doesn't trigger our JS code to attach fomantic label to checkbox, so here we use standard checkbox */}} + + {{else if eq .Type "number"}} + + + {{else}} + + + {{end}} +
+ {{end}} +
+ +
+{{end}} +{{range .workflows}} + {{if and .ErrMsg (eq .Entry.Name $.CurWorkflow)}} +
+
{{svg "octicon-alert" 16 "text red"}} {{.ErrMsg}}
+
+ {{end}} +{{end}} diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 3d95e8a715d..bc63db9b627 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -16,7 +16,7 @@ {{ctx.Locale.Tr "repo.commit.operations"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}