Merge branch 'main' into lunny/performance_big_reference_repo

pull/31076/head
Lunny Xiao 4 weeks ago
commit 6536f219ad
  1. 2
      MAINTAINERS
  2. 1
      cmd/main.go
  3. 41
      cmd/main_test.go
  4. 2
      cmd/migrate.go
  5. 2
      cmd/web_acme.go
  6. 4
      custom/conf/app.example.ini
  7. 2
      go.mod
  8. 6
      go.sum
  9. 2
      models/repo/repo.go
  10. 1
      models/unittest/testdb.go
  11. 33
      modules/auth/password/pwn/pwn_test.go
  12. 6
      modules/git/batch_reader.go
  13. 12
      modules/git/command.go
  14. 78
      modules/git/parse.go
  15. 67
      modules/git/parse_nogogit.go
  16. 2
      modules/git/pipeline/lfs_nogogit.go
  17. 5
      modules/git/repo_archive.go
  18. 66
      modules/git/submodule.go
  19. 48
      modules/git/submodule_test.go
  20. 1
      modules/git/tests/repos/repo4_submodules/HEAD
  21. 4
      modules/git/tests/repos/repo4_submodules/config
  22. BIN
      modules/git/tests/repos/repo4_submodules/objects/97/c3d30df0e6492348292600920a6482feaebb74
  23. BIN
      modules/git/tests/repos/repo4_submodules/objects/c7/e064ed49b44523cba8a5dfbc37d2ce1bb41d34
  24. 2
      modules/git/tests/repos/repo4_submodules/objects/e1/e59caba97193d48862d6809912043871f37437
  25. 1
      modules/git/tests/repos/repo4_submodules/refs/heads/master
  26. 4
      modules/git/tree.go
  27. 1
      modules/git/tree_blob_nogogit.go
  28. 12
      modules/git/tree_entry_nogogit.go
  29. 3
      modules/indexer/code/indexer.go
  30. 4
      modules/indexer/code/indexer_test.go
  31. 9
      modules/indexer/internal/bleve/util.go
  32. 7
      modules/indexer/internal/bleve/util_test.go
  33. 2
      modules/markup/sanitizer_default.go
  34. 22
      modules/setting/config_env.go
  35. 3
      modules/setting/indexer.go
  36. 5
      modules/setting/security.go
  37. 6
      modules/templates/helper.go
  38. 2
      options/locale/locale_cs-CZ.ini
  39. 2
      options/locale/locale_de-DE.ini
  40. 2
      options/locale/locale_el-GR.ini
  41. 3
      options/locale/locale_en-US.ini
  42. 2
      options/locale/locale_es-ES.ini
  43. 2
      options/locale/locale_fa-IR.ini
  44. 2
      options/locale/locale_fi-FI.ini
  45. 2
      options/locale/locale_fr-FR.ini
  46. 2
      options/locale/locale_ga-IE.ini
  47. 2
      options/locale/locale_hu-HU.ini
  48. 2
      options/locale/locale_id-ID.ini
  49. 1
      options/locale/locale_is-IS.ini
  50. 2
      options/locale/locale_it-IT.ini
  51. 2
      options/locale/locale_ja-JP.ini
  52. 2
      options/locale/locale_ko-KR.ini
  53. 2
      options/locale/locale_lv-LV.ini
  54. 2
      options/locale/locale_nl-NL.ini
  55. 2
      options/locale/locale_pl-PL.ini
  56. 2
      options/locale/locale_pt-BR.ini
  57. 13
      options/locale/locale_pt-PT.ini
  58. 2
      options/locale/locale_ru-RU.ini
  59. 2
      options/locale/locale_si-LK.ini
  60. 2
      options/locale/locale_sk-SK.ini
  61. 2
      options/locale/locale_sv-SE.ini
  62. 11
      options/locale/locale_tr-TR.ini
  63. 2
      options/locale/locale_uk-UA.ini
  64. 2
      options/locale/locale_zh-CN.ini
  65. 1
      options/locale/locale_zh-HK.ini
  66. 2
      options/locale/locale_zh-TW.ini
  67. 47
      routers/api/packages/pypi/pypi.go
  68. 10
      routers/api/packages/pypi/pypi_test.go
  69. 39
      routers/common/codesearch.go
  70. 2
      routers/web/admin/users.go
  71. 19
      routers/web/explore/code.go
  72. 216
      routers/web/repo/actions/actions.go
  73. 9
      routers/web/repo/actions/view.go
  74. 43
      routers/web/repo/repo.go
  75. 24
      routers/web/repo/search.go
  76. 47
      routers/web/repo/view_home.go
  77. 20
      routers/web/user/code.go
  78. 3
      routers/web/web.go
  79. 3
      services/context/repo.go
  80. 130
      services/repository/generate.go
  81. 8
      templates/admin/user/edit.tmpl
  82. 8
      templates/base/head_navbar.tmpl
  83. 32
      templates/org/create.tmpl
  84. 5
      templates/package/content/nuget.tmpl
  85. 27
      templates/repo/actions/workflow_dispatch.tmpl
  86. 45
      templates/repo/actions/workflow_dispatch_inputs.tmpl
  87. 2
      templates/repo/commit_page.tmpl
  88. 35
      templates/repo/create.tmpl
  89. 359
      templates/repo/diff/compare.tmpl
  90. 21
      templates/repo/empty.tmpl
  91. 2
      templates/repo/header.tmpl
  92. 22
      templates/repo/migrate/codebase.tmpl
  93. 22
      templates/repo/migrate/codecommit.tmpl
  94. 22
      templates/repo/migrate/git.tmpl
  95. 24
      templates/repo/migrate/gitbucket.tmpl
  96. 22
      templates/repo/migrate/gitea.tmpl
  97. 22
      templates/repo/migrate/github.tmpl
  98. 22
      templates/repo/migrate/gitlab.tmpl
  99. 22
      templates/repo/migrate/gogs.tmpl
  100. 25
      templates/repo/migrate/onedev.tmpl
  101. Some files were not shown because too many files have changed in this diff Show More

@ -31,7 +31,6 @@ Gary Kim <gary@garykim.dev> (@gary-kim)
Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k)
Mura Li <typeless@ctli.io> (@typeless)
6543 <6543@obermui.de> (@6543)
jaqra <jaqra@hotmail.com> (@jaqra)
David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson)
a1012112796 <1012112796@qq.com> (@a1012112796)
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
@ -63,3 +62,4 @@ Yu Liu <1240335630@qq.com> (@HEREYUA)
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
hiifong <i@hiif.ong> (@hiifong)
metiftikci <metiftikci@hotmail.com> (@metiftikci)

@ -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
}

@ -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)
})
}
}

@ -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,
}

@ -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

@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

@ -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

@ -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=

@ -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

@ -59,6 +59,7 @@ func InitSettings() {
_ = hash.Register("dummy", hash.NewDummyHasher)
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
setting.InitGiteaEnvVarsForTesting()
}
// TestOptions represents test options

@ -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)

@ -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 {
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH>
//
// 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{}
}

@ -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

@ -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:
// <mode> <type> <sha> <space-padded-size>\t<filename>
// <mode> <type> <sha>\t<filename>
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
}

@ -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:
// <mode> <type> <sha> <space-padded-size>\t<filename>
// <mode> <type> <sha>\t<filename>
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

@ -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
}

@ -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())

@ -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
}

@ -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)
}

@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

@ -0,0 +1,2 @@
x<EFBFBD><EFBFBD>[
Β0EύΞ*ζ_<EFBFBD>ι$MΡ5tifBk IΕ•Ή7ζk~ήΓ9ά<EFBFBD>—εά ό¦π.jΦΘ Ε δΙ"zΒ`ί#I<EFBFBD>irF…µΝΉΐΨ$%ΉΒης|4)°―?tΌΙ=”Λ:K¦ο­#[$DΏ―ϋΏ^<EFBFBD><EFBFBD>…΅®Σ’y½HU/<EFBFBD>f?G

@ -0,0 +1 @@
e1e59caba97193d48862d6809912043871f37437

@ -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).

@ -17,7 +17,6 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
ptree: t,
ID: t.ID,
name: "",
fullName: "",
entryMode: EntryModeTree,
}, nil
}

@ -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
}

@ -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)

@ -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)

@ -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)
}

@ -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))
})
}

@ -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")

@ -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")
}

@ -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

@ -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

@ -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{

@ -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 <strong>%s</strong> 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

@ -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 <strong>%s</strong> 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

@ -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=Ταυτότητα & Πρόσβαση

@ -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 <code>/etc/apk/repositories</code> file:
alpine.registry.key = Download the registry public RSA key into the <code>/etc/apk/keys/</code> 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.

@ -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 <strong>%s</strong> 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

@ -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=این تیم دسترسی<strong>
teams.all_repositories_write_permission_desc=این تیم دسترسی<strong> نوشتن </strong> <strong> مخازن همه</strong> را می بخشد: اعضا می توانند مخازن را مشاهده و درج کنند.
teams.all_repositories_admin_permission_desc=این تیم دسترسی<strong> مدیر </strong> به <strong> مخازن همه</strong> را می بخشد: اعضا می توانند مخازن را بخواند، همکار و مخزن اضافه کنند.
[admin]
dashboard=پیشخوان
users=حساب کاربران

@ -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

@ -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 <strong>%s</st
teams.invite.by=Invité par %s
teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindre l’équipe.
[admin]
maintenance=Maintenance
dashboard=Tableau de bord

@ -1015,7 +1015,6 @@ new_repo_helper=Tá gach comhad tionscadail i stór, lena n-áirítear stair ath
owner=Úinéir
owner_helper=B'fhéidir nach dtaispeánfar roinnt eagraíochtaí sa anuas mar gheall ar theorainn uasta comhaireamh stórais.
repo_name=Ainm Stórais
repo_name_helper=Úsáideann dea-ainmneacha stórtha eochairfhocail ghearr, i gcuimhne agus uathúla.
repo_size=Méid an Stóras
template=Teimpléad
template_select=Roghnaigh teimpléad.
@ -2861,6 +2860,7 @@ teams.invite.title=Tugadh cuireadh duit dul isteach i bhfoireann <strong>%s</str
teams.invite.by=Ar cuireadh ó %s
teams.invite.description=Cliceáil ar an gcnaipe thíos le do thoil chun dul isteach san fhoireann.
[admin]
maintenance=Cothabháil
dashboard=Deais

@ -563,7 +563,6 @@ visibility.private=Privát
[repo]
owner=Tulajdonos
repo_name=Tároló neve
repo_name_helper=A jó tárolónév általában rövid, megjegyezhető és egyedi kulcsszavakból tevődik össze.
repo_size=Repozitórium mérete
template=Sablon
template_select=Válasszon sablont.
@ -1229,6 +1228,7 @@ teams.members.none=Ennek a csapatnak nincsenek tagjai.
teams.specific_repositories=Meghatározott tárolók
teams.all_repositories=Minden tároló
[admin]
dashboard=Műszerfal
users=Felhasználói fiókok

@ -585,7 +585,6 @@ visibility.private=Pribadi
[repo]
owner=Pemilik
repo_name=Nama Repositori
repo_name_helper=Nama repositori yang baik menggunakan kata kunci yang pendek, unik, dan bisa diingat.
repo_size=Ukuran Repositori
template=Templat
template_select=Pilih template.
@ -1083,6 +1082,7 @@ teams.add_team_member=Tambahkan Anggota Tim
teams.delete_team_success=Tim sudah di hapus.
teams.repositories=Tim repositori
[admin]
dashboard=Dasbor
organizations=Organisasi

@ -1136,6 +1136,7 @@ teams.settings=Stillingar
teams.update_settings=Uppfæra Stillingar
teams.all_repositories=Öll hugbúnaðarsöfn
[admin]
repositories=Hugbúnaðarsöfn
config=Stilling

@ -755,7 +755,6 @@ visibility.private=Privato
owner=Proprietario
owner_helper=Alcune organizzazioni potrebbero non essere visualizzate nel menu a discesa a causa di un limite massimo al numero di repository.
repo_name=Nome Repository
repo_name_helper=Un buon nome per un repository è costituito da parole chiave corte, facili da ricordare e uniche.
repo_size=Dimensione repository
template=Modello
template_select=Seleziona un modello.
@ -2154,6 +2153,7 @@ teams.all_repositories_read_permission_desc=Questo team concede <strong>permessi
teams.all_repositories_write_permission_desc=Questo team concede <strong>permessi di scrittura</strong> accesso a <strong>tutte le repository</strong>: i membri possono leggere e pushare le repository.
teams.all_repositories_admin_permission_desc=Questo team concede a <strong>Amministratore</strong> l'accesso a <strong>tutte le repository</strong>: i membri possono leggere, pushare e aggiungere collaboratori alle repository.
[admin]
dashboard=Pannello di Controllo
users=Account utenti

@ -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=あなたは組織 <strong>%[2]s</strong> 内のチーム <st
teams.invite.by=%s からの招待
teams.invite.description=下のボタンをクリックしてチームに参加してください。
[admin]
maintenance=メンテナンス
dashboard=ダッシュボード

@ -531,7 +531,6 @@ visibility.private=비공개
[repo]
owner=소유자
repo_name=저장소 이름
repo_name_helper=좋은 저장소 이름은 보통 짧고 기억하기 좋은 특별한 키워드로 이루어 집니다.
repo_size=저장소 용량
template=템플릿
template_select=템플릿 고르기
@ -1191,6 +1190,7 @@ teams.repositories=팀 저장소
teams.add_duplicate_users=사용자가 이미 팀 멤버입니다.
teams.members.none=이 팀에 멤버가 없습니다.
[admin]
dashboard=대시보드
users=사용자 계정

@ -913,7 +913,6 @@ new_repo_helper=Repozitorijs satur visus projekta failus, tajā skaitā izmaiņu
owner=Īpašnieks
owner_helper=Ņemot vērā maksimālā repozitoriju skaita ierobežojumu, ne visas organizācijas var tikt parādītas sarakstā.
repo_name=Repozitorija nosaukums
repo_name_helper=Labi repozitorija nosaukumi ir īsi, unikāli un tādi, ko viegli atcerēties.
repo_size=Repozitorija izmērs
template=Sagatave
template_select=Izvēlieties sagatavi.
@ -2596,6 +2595,7 @@ teams.invite.title=Tu esi uzaicināts pievienoties organizācijas <strong>%[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

@ -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 <strong>alle bestaande</strong> repositories aan het team toegevoegd.
teams.all_repositories_read_permission_desc=Dit team heeft <strong>Lees</strong> toegang tot <strong>alle repositories</strong>: leden kunnen repositories bekijken en klonen.
[admin]
dashboard=Overzicht
users=Gebruikersacount

@ -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 <str
teams.all_repositories_write_permission_desc=Ten zespół nadaje uprawnienie <strong>Zapisu</strong> do <strong>wszystkich repozytoriów</strong>: jego członkowie mogą odczytywać i przesyłać do repozytoriów.
teams.all_repositories_admin_permission_desc=Ten zespół nadaje uprawnienia <strong>Administratora</strong> do <strong>wszystkich repozytoriów</strong>: jego członkowie mogą odczytywać, przesyłać oraz dodawać innych współtwórców do repozytoriów.
[admin]
dashboard=Pulpit
users=Konta użytkownika

@ -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 <strong>%s</st
teams.invite.by=Convidado por %s
teams.invite.description=Por favor, clique no botão abaixo para se juntar à equipe.
[admin]
dashboard=Painel
identity_access=Identidade e acesso

@ -244,6 +244,7 @@ license_desc=Vá buscar <a target="_blank" rel="noopener noreferrer" href="%[1]s
[install]
install=Instalação
installing_desc=Instalando agora, por favor aguarde...
title=Configuração inicial
docker_helper=Se correr o Gitea dentro do Docker, leia a <a target="_blank" rel="noopener noreferrer" href="%s">documentação</a> 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, <a href="%s">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 <b>%s</b>:
reset_password.text=Para recuperar a sua conta, clique na ligação seguinte (válida por <b>%s</b>):
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 <strong>%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 <code>/etc/apk/repositories</code>:
alpine.registry.key=Descarregue a chave RSA pública do registo para dentro da pasta <code>/etc/apk/keys/</code> para verificar a assinatura do índice:
alpine.registry.info=Escolha $branch e $repository da lista abaixo.

@ -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=Идентификация и доступ

@ -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=ම කණයම පරදනය කරය <strong></strong> වත පරවශය ලයනන <strong>සයල ගබඩවනට</strong>: සකයට කයවමට සහ ගබඩවනට තල කළ හය.
teams.all_repositories_admin_permission_desc=ම කණයම පරදනය කරය <strong>පරලක</strong> වත පරවශය <strong>සයල ගබඩවනට</strong>: සකයට කයවමට, තලමට සහ ගබඩවනට සහයකය එකතමට.
[admin]
dashboard=උපකරණ පවර
users=පරලක ග

@ -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 <strong>Zápis</strong> do <strong>všetkých repozitárov</strong>: členovia môžu prezerať a nahrávať do repozitárov.
teams.all_repositories_admin_permission_desc=Tomuto tímu je pridelený <strong>Admin</strong> prístup ku <strong>všetkým repozitárom</strong>: členovia môžu prezerať, nahrávať do repozitárov a pridávať do nich spolupracovníkov.
[admin]
repositories=Repozitáre
hooks=Webhooky

@ -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 <strong>Läs</st
teams.all_repositories_write_permission_desc=Detta team beviljar <strong>Skriv</strong>-rättigheter till <strong>alla utvecklingskataloger</strong>: medlemmar kan läsa från och pusha till utvecklingskataloger.
teams.all_repositories_admin_permission_desc=Detta team beviljar <strong>Admin</strong>-rättigheter till <strong>alla utvecklingskataloger</strong>: medlemmar kan läsa från, pusha till och lägga till kollaboratörer för utvecklingskatalogerna.
[admin]
dashboard=Instrumentpanel
users=Användarkonto

@ -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 <a href="%s" target="_blank">GitHub</a> 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=<strong>%s</strong> takımına (Organizasyon: <strong>%s</str
teams.invite.by=%s tarafından davet edildi
teams.invite.description=Takıma katılmak için aşağıdaki düğmeye tıklayın.
[admin]
maintenance=Bakım
dashboard=Pano

@ -712,7 +712,6 @@ visibility.private=Приватний
owner=Власник
owner_helper=Деякі організації можуть не відображатися у випадаючому списку через максимальну кількість репозиторііїв.
repo_name=Назва репозиторію
repo_name_helper=Хороші назви репозиторіїв використовують короткі, унікальні ключові слова що легко запам'ятати.
repo_size=Розмір репозиторію
template=Шаблон
template_select=Оберіть шаблон.
@ -2003,6 +2002,7 @@ teams.all_repositories_read_permission_desc=Ця команда надає до
teams.all_repositories_write_permission_desc=Ця команда надає дозвіл <strong>Запис</strong> для <strong>всіх репозиторіїв</strong>: учасники можуть переглядати та виконувати push в репозиторіях.
teams.all_repositories_admin_permission_desc=Ця команда надає дозвіл <strong>Адміністрування</strong> для <strong>всіх репозиторіїв</strong>: учасники можуть переглядати, виконувати push та додавати співробітників.
[admin]
dashboard=Панель управління
users=Облікові записи користувачів

@ -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=您已被邀请加入组织 <strong>%s</strong> 中的团队
teams.invite.by=邀请人 %s
teams.invite.description=请点击下面的按钮加入团队。
[admin]
maintenance=维护
dashboard=管理面板

@ -684,6 +684,7 @@ teams.add_team_member=新增團隊成員
teams.delete_team_success=該團隊已被刪除。
teams.repositories=團隊儲存庫
[admin]
dashboard=控制面版
organizations=組織管理

@ -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=您已被邀請加入組織 <strong>%s</strong> 中的團隊
teams.invite.by=邀請人 %s
teams.invite.description=請點擊下方按鈕加入團隊。
[admin]
maintenance=維護
dashboard=資訊主頁

@ -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)
}

@ -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"))
}

@ -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
}

@ -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)
}

@ -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,

@ -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="/<repoLink>/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.

@ -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

@ -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

@ -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(),
})

@ -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
}

@ -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,

@ -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() {

@ -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

@ -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) == "" {

@ -128,16 +128,16 @@
<input name="restricted" type="checkbox" {{if .User.IsRestricted}}checked{{end}}>
</div>
</div>
<div class="inline field {{if DisableGitHooks}}tw-hidden{{end}}">
<div class="inline field {{if .DisableGitHooks}}tw-hidden{{end}}">
<div class="ui checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.users.allow_git_hook_tooltip"}}">
<label><strong>{{ctx.Locale.Tr "admin.users.allow_git_hook"}}</strong></label>
<input name="allow_git_hook" type="checkbox" {{if .User.CanEditGitHook}}checked{{end}} {{if DisableGitHooks}}disabled{{end}}>
<input name="allow_git_hook" type="checkbox" {{if .User.CanEditGitHook}}checked{{end}} {{if .DisableGitHooks}}disabled{{end}}>
</div>
</div>
<div class="inline field {{if or (DisableImportLocal) (.DisableMigrations)}}tw-hidden{{end}}">
<div class="inline field {{if or (.DisableImportLocal) (.DisableMigrations)}}tw-hidden{{end}}">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.users.allow_import_local"}}</strong></label>
<input name="allow_import_local" type="checkbox" {{if .User.CanImportLocal}}checked{{end}} {{if DisableImportLocal}}disabled{{end}}>
<input name="allow_import_local" type="checkbox" {{if .User.CanImportLocal}}checked{{end}} {{if .DisableImportLocal}}disabled{{end}}>
</div>
</div>
{{if not .DisableRegularOrgCreation}}

@ -70,7 +70,7 @@
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span>
<div class="menu user-menu">
<div class="ui header">
<div class="header">
{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
</div>
@ -130,7 +130,7 @@
{{/* do not localize it, here it needs the fixed length (width) to make UI comfortable */}}
{{if .IsAdmin}}<span class="navbar-profile-admin">admin</span>{{end}}
<div class="menu user-menu">
<div class="ui header">
<div class="header">
{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
</div>
@ -192,7 +192,7 @@
<span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span>
</a>
<div class="tw-flex tw-gap-1">
<form class="stopwatch-commit" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/toggle">
<form class="stopwatch-commit form-fetch-action" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/toggle">
{{.CsrfTokenHtml}}
<button
type="submit"
@ -200,7 +200,7 @@
data-tooltip-content="{{ctx.Locale.Tr "repo.issues.stop_tracking"}}"
>{{svg "octicon-square-fill"}}</button>
</form>
<form class="stopwatch-cancel" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/cancel">
<form class="stopwatch-cancel form-fetch-action" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/cancel">
{{.CsrfTokenHtml}}
<button
type="submit"

@ -2,22 +2,22 @@
<div role="main" aria-label="{{.Title}}" class="page-content organization new org">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "new_org"}}
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "new_org"}}
</h3>
<div class="ui attached segment">
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_OrgName}}error{{end}}">
<label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}}</label>
<input id="org_name" name="org_name" value="{{.org_name}}" autofocus required maxlength="40">
<span class="help">{{ctx.Locale.Tr "org.org_name_helper"}}</span>
</div>
<div class="inline field {{if .Err_OrgVisibility}}error{{end}}">
<span class="inline required field"><label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label></span>
<div class="inline-grouped-list">
<div class="inline field required {{if .Err_OrgVisibility}}error{{end}}">
<label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
<div class="inline-right">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="0" {{if .DefaultOrgVisibilityMode.IsPublic}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
@ -35,11 +35,9 @@
<div class="inline field" id="permission_box">
<label>{{ctx.Locale.Tr "org.settings.permission"}}</label>
<div class="inline-grouped-list">
<div class="ui checkbox">
<input type="checkbox" name="repo_admin_change_team_access" checked>
<label>{{ctx.Locale.Tr "org.settings.repoadminchangeteam"}}</label>
</div>
<div class="ui checkbox">
<input type="checkbox" name="repo_admin_change_team_access" checked>
<label>{{ctx.Locale.Tr "org.settings.repoadminchangeteam"}}</label>
</div>
</div>
@ -49,8 +47,8 @@
{{ctx.Locale.Tr "org.create_org"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -35,11 +35,12 @@
</tr>
</thead>
<tbody>
{{$tooltipSearchInNuget := ctx.Locale.Tr "packages.search_in_external_registry" "nuget.org"}}
{{range $framework, $dependencies := .PackageDescriptor.Metadata.Dependencies}}
{{range $dependencies}}
<tr>
<td>{{.ID}}</td>
<td>{{.Version}}</td>
<td>{{.ID}} <a target="_blank" rel="noreferrer" href="https://www.nuget.org/packages/{{.ID}}" data-tooltip-content="{{$tooltipSearchInNuget}}">{{svg "octicon-link-external"}}</a></td>
<td>{{.Version}} <a target="_blank" rel="noreferrer" href="https://www.nuget.org/packages/{{.ID}}/{{.Version}}" data-tooltip-content="{{$tooltipSearchInNuget}}">{{svg "octicon-link-external"}}</a></td>
<td>{{$framework}}</td>
</tr>
{{end}}

@ -11,7 +11,7 @@
<label>{{ctx.Locale.Tr "actions.workflow.from_ref"}}:</label>
</span>
<div class="ui inline field dropdown button select-branch branch-selector-dropdown ellipsis-items-nowrap">
<input type="hidden" name="ref" value="refs/heads/{{index .Branches 0}}">
<input type="hidden" name="ref" hx-sync="this:replace" hx-target="#runWorkflowDispatchModalInputs" hx-swap="innerHTML" hx-get="{{$.Link}}/workflow-dispatch-inputs?workflow={{$.CurWorkflow}}" hx-trigger="change" value="refs/heads/{{index .Branches 0}}">
{{svg "octicon-git-branch" 14}}
<div class="default text">{{index .Branches 0}}</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
@ -49,30 +49,9 @@
<div class="divider"></div>
{{range $item := .WorkflowDispatchConfig.Inputs}}
<div class="ui field {{if .Required}}required{{end}}">
{{if eq .Type "choice"}}
<label>{{.Description}}:</label>
<select class="ui selection type dropdown" name="{{.Name}}">
{{range .Options}}
<option value="{{.}}" {{if eq $item.Default .}}selected{{end}} >{{.}}</option>
{{end}}
</select>
{{else if eq .Type "boolean"}}
<div class="ui inline checkbox">
<label>{{.Description}}</label>
<input type="checkbox" name="{{.Name}}" {{if eq .Default "true"}}checked{{end}}>
</div>
{{else if eq .Type "number"}}
<label>{{.Description}}:</label>
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
{{else}}
<label>{{.Description}}:</label>
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
{{end}}
<div id="runWorkflowDispatchModalInputs">
{{template "repo/actions/workflow_dispatch_inputs" .}}
</div>
{{end}}
<button class="ui tiny primary button" type="submit">Submit</button>
</form>
</div>
</div>

@ -0,0 +1,45 @@
{{if not .WorkflowDispatchConfig}}
<div class="ui error message tw-block">{{/* 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}}
</div>
{{else}}
{{range $item := .WorkflowDispatchConfig.Inputs}}
<div class="ui field {{if .Required}}required{{end}}">
{{if eq .Type "choice"}}
<label>{{or .Description .Name}}:</label>
{{/* htmx won't initialize the fomantic dropdown, so it is a standard "select" input */}}
<select class="ui selection dropdown" name="{{.Name}}">
{{range .Options}}
<option value="{{.}}" {{if eq $item.Default .}}selected{{end}}>{{.}}</option>
{{end}}
</select>
{{else if eq .Type "boolean"}}
{{/* htmx doesn't trigger our JS code to attach fomantic label to checkbox, so here we use standard checkbox */}}
<label class="tw-flex flex-text-inline">
<input type="checkbox" name="{{.Name}}" {{if eq .Default "true"}}checked{{end}}>
{{or .Description .Name}}
</label>
{{else if eq .Type "number"}}
<label>{{or .Description .Name}}:</label>
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
{{else}}
<label>{{or .Description .Name}}:</label>
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
{{end}}
</div>
{{end}}
<div class="ui field">
<button class="ui tiny primary button" type="submit">{{ctx.Locale.Tr "actions.workflow.run"}}</button>
</div>
{{end}}
{{range .workflows}}
{{if and .ErrMsg (eq .Entry.Name $.CurWorkflow)}}
<div class="ui field">
<div>{{svg "octicon-alert" 16 "text red"}} {{.ErrMsg}}</div>
</div>
{{end}}
{{end}}

@ -16,7 +16,7 @@
{{ctx.Locale.Tr "repo.commit.operations"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<div class="ui header">{{ctx.Locale.Tr "repo.commit.operations"}}</div>
<div class="header">{{ctx.Locale.Tr "repo.commit.operations"}}</div>
<div class="divider"></div>
<div class="item show-create-branch-modal"
data-content="{{ctx.Locale.Tr "repo.branch.new_branch_from" (.CommitID)}}" {{/* used by the form */}}

@ -2,20 +2,20 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new-repo">
<div class="ui middle very relaxed page one column grid">
<div class="column">
<form class="ui form new-repo-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "new_repo"}}
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
{{template "repo/create_helper" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "new_repo"}}
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
{{template "repo/create_helper" .}}
{{if not .CanCreateRepo}}
<div class="ui negative message">
<p>{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}</p>
</div>
{{end}}
{{if not .CanCreateRepo}}
<div class="ui negative message">
<p>{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}</p>
</div>
{{end}}
<form class="ui form left-right-form new-repo-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_Owner}}error{{end}}">
<label>{{ctx.Locale.Tr "repo.owner"}}</label>
<div class="ui selection owner dropdown">
@ -69,7 +69,7 @@
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.template"}}</label>
<div id="repo_template_search" class="ui search selection dropdown">
<input type="hidden" id="repo_template" name="repo_template" value="{{.repo_template}}">
<input type="hidden" id="repo_template" name="repo_template" value="{{or .repo_template ""}}">
<div class="default text">{{.repo_template_name}}</div>
<div class="menu">
</div>
@ -178,6 +178,7 @@
<span class="help">{{ctx.Locale.Tr "repo.readme_helper_desc"}}</span>
</div>
<div class="inline field">
<label></label>
<div class="ui checkbox" id="auto-init">
<input name="auto_init" type="checkbox" {{if .auto_init}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.auto_init"}}</label>
@ -191,7 +192,7 @@
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.object_format"}}</label>
<div class="ui selection owner dropdown">
<input type="hidden" id="object_format_name" name="object_format_name" value="{{.DefaultObjectFormat.Name}}" required>
<input type="hidden" id="object_format_name" name="object_format_name" value="{{or .object_format_name .DefaultObjectFormat.Name}}" required>
<div class="default text">{{.DefaultObjectFormat.Name}}</div>
<div class="menu">
{{range .SupportedObjectFormats}}
@ -216,8 +217,8 @@
{{ctx.Locale.Tr "repo.create_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -1,230 +1,229 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository diff {{if .PageIsComparePull}}compare pull{{end}}">
{{template "repo/header" .}}
{{$showDiffBox := false}}
<div class="ui container fluid padded">
<h2 class="ui header">
{{if and $.PageIsComparePull $.IsSigned (not .Repository.IsArchived)}}
{{ctx.Locale.Tr "repo.pulls.compare_changes"}}
<div class="sub header">{{ctx.Locale.Tr "repo.pulls.compare_changes_desc"}}</div>
{{else}}
{{ctx.Locale.Tr "action.compare_commits_general"}}
{{end}}
</h2>
{{$BaseCompareName := $.BaseName -}}
{{- $HeadCompareName := $.HeadRepo.OwnerName -}}
{{- if and (eq $.BaseName $.HeadRepo.OwnerName) (ne $.Repository.Name $.HeadRepo.Name) -}}
{{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}}
{{- end -}}
{{- $OwnForkCompareName := "" -}}
{{- if .OwnForkRepo -}}
{{- $OwnForkCompareName = .OwnForkRepo.OwnerName -}}
{{- end -}}
{{- $RootRepoCompareName := "" -}}
{{- if .RootRepo -}}
{{- $RootRepoCompareName = .RootRepo.OwnerName -}}
{{- if eq $.HeadRepo.OwnerName .RootRepo.OwnerName -}}
<h2 class="ui header">
{{if and $.PageIsComparePull $.IsSigned (not .Repository.IsArchived)}}
{{ctx.Locale.Tr "repo.pulls.compare_changes"}}
<div class="sub header">{{ctx.Locale.Tr "repo.pulls.compare_changes_desc"}}</div>
{{else}}
{{ctx.Locale.Tr "action.compare_commits_general"}}
{{end}}
</h2>
{{$BaseCompareName := $.BaseName -}}
{{- $HeadCompareName := $.HeadRepo.OwnerName -}}
{{- if and (eq $.BaseName $.HeadRepo.OwnerName) (ne $.Repository.Name $.HeadRepo.Name) -}}
{{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}}
{{- end -}}
{{- end -}}
<div class="ui segment choose branch">
<a class="tw-mr-2" href="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments $.HeadBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{end}}{{PathEscapeSegments $.BaseBranch}}" title="{{ctx.Locale.Tr "repo.pulls.switch_head_and_base"}}">{{svg "octicon-git-compare"}}</a>
<div class="ui floating filter dropdown" data-no-results="{{ctx.Locale.Tr "no_results_found"}}">
<div class="ui basic small button">
<span class="text">{{if $.PageIsComparePull}}{{ctx.Locale.Tr "repo.pulls.compare_base"}}{{else}}{{ctx.Locale.Tr "repo.compare.compare_base"}}{{end}}: {{$BaseCompareName}}:{{$.BaseBranch}}</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="menu">
<div class="ui icon search input">
<i class="icon">{{svg "octicon-filter" 16}}</i>
<input name="search" placeholder="{{ctx.Locale.Tr "repo.filter_branch_and_tag"}}...">
{{- $OwnForkCompareName := "" -}}
{{- if .OwnForkRepo -}}
{{- $OwnForkCompareName = .OwnForkRepo.OwnerName -}}
{{- end -}}
{{- $RootRepoCompareName := "" -}}
{{- if .RootRepo -}}
{{- $RootRepoCompareName = .RootRepo.OwnerName -}}
{{- if eq $.HeadRepo.OwnerName .RootRepo.OwnerName -}}
{{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}}
{{- end -}}
{{- end -}}
<div class="ui segment choose branch">
<a class="tw-mr-2" href="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments $.HeadBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{end}}{{PathEscapeSegments $.BaseBranch}}" title="{{ctx.Locale.Tr "repo.pulls.switch_head_and_base"}}">{{svg "octicon-git-compare"}}</a>
<div class="ui dropdown jump select-branch">
<div class="ui basic small button">
<span class="text">{{if $.PageIsComparePull}}{{ctx.Locale.Tr "repo.pulls.compare_base"}}{{else}}{{ctx.Locale.Tr "repo.compare.compare_base"}}{{end}}: {{$BaseCompareName}}:{{$.BaseBranch}}</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="header">
<div class="ui grid">
<div class="two column row">
<a class="reference column" href="#" data-target=".base-branch-list">
<span class="text black">
{{svg "octicon-git-branch" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.branches"}}
</span>
</a>
<a class="reference column" href="#" data-target=".base-tag-list">
<span class="text black">
{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.tags"}}
</span>
</a>
<div class="menu">
<div class="ui icon search input">
<i class="icon">{{svg "octicon-filter" 16}}</i>
<input name="search" placeholder="{{ctx.Locale.Tr "repo.filter_branch_and_tag"}}...">
</div>
<div class="header">
<div class="ui grid">
<div class="two column row">
<a class="reference column" href="#" data-target=".base-branch-list">
<span class="text black">
{{svg "octicon-git-branch"}} {{ctx.Locale.Tr "repo.branches"}}
</span>
</a>
<a class="reference column" href="#" data-target=".base-tag-list">
<span class="text black">
{{svg "octicon-tag"}} {{ctx.Locale.Tr "repo.tags"}}
</span>
</a>
</div>
</div>
</div>
</div>
<div class="scrolling menu reference-list-menu base-branch-list">
{{range .Branches}}
<div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments $.HeadBranch}}">{{$BaseCompareName}}:{{.}}</div>
{{end}}
{{if not .PullRequestCtx.SameRepo}}
{{range .HeadBranches}}
<div class="item" data-url="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$HeadCompareName}}:{{.}}</div>
<div class="scrolling menu reference-list-menu base-branch-list">
{{range .Branches}}
<a class="item {{if eq $.BaseBranch .}}selected{{end}}" href="{{$.RepoLink}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments $.HeadBranch}}">{{$BaseCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{if .OwnForkRepo}}
{{range .OwnForkRepoBranches}}
<div class="item" data-url="{{$.OwnForkRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$OwnForkCompareName}}:{{.}}</div>
{{if not .PullRequestCtx.SameRepo}}
{{range .HeadBranches}}
<a class="item" href="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$HeadCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if and .RootRepo (.RootRepo.AllowsPulls ctx)}}
{{range .RootRepoBranches}}
<div class="item" data-url="{{$.RootRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$RootRepoCompareName}}:{{.}}</div>
{{if .OwnForkRepo}}
{{range .OwnForkRepoBranches}}
<a class="item" href="{{$.OwnForkRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$OwnForkCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
</div>
<div class="scrolling menu reference-list-menu base-tag-list tw-hidden">
{{range .Tags}}
<div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments $.HeadBranch}}">{{$BaseCompareName}}:{{.}}</div>
{{end}}
{{if not .PullRequestCtx.SameRepo}}
{{range .HeadTags}}
<div class="item" data-url="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$HeadCompareName}}:{{.}}</div>
{{if and .RootRepo (.RootRepo.AllowsPulls ctx)}}
{{range .RootRepoBranches}}
<a class="item" href="{{$.RootRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$RootRepoCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if .OwnForkRepo}}
{{range .OwnForkRepoTags}}
<div class="item" data-url="{{$.OwnForkRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$OwnForkCompareName}}:{{.}}</div>
</div>
<div class="scrolling menu reference-list-menu base-tag-list tw-hidden">
{{range .Tags}}
<a class="item {{if eq $.BaseBranch .}}selected{{end}}" href="{{$.RepoLink}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments $.HeadBranch}}">{{$BaseCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{if .RootRepo}}
{{range .RootRepoTags}}
<div class="item" data-url="{{$.RootRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$RootRepoCompareName}}:{{.}}</div>
{{if not .PullRequestCtx.SameRepo}}
{{range .HeadTags}}
<a class="item" href="{{$.HeadRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$HeadCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if .OwnForkRepo}}
{{range .OwnForkRepoTags}}
<a class="item" href="{{$.OwnForkRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$OwnForkCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{if .RootRepo}}
{{range .RootRepoTags}}
<a class="item" href="{{$.RootRepo.Link}}/compare/{{PathEscapeSegments .}}{{$.CompareSeparator}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{PathEscapeSegments $.HeadBranch}}">{{$RootRepoCompareName}}:{{.}}</a>
{{end}}
{{end}}
</div>
</div>
</div>
</div>
<a href="{{.RepoLink}}/compare/{{PathEscapeSegments .BaseBranch}}{{.OtherCompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments $.HeadBranch}}" title="{{ctx.Locale.Tr "repo.pulls.switch_comparison_type"}}">{{svg "octicon-arrow-left" 16}}<div class="compare-separator">{{.CompareSeparator}}</div></a>
<div class="ui floating filter dropdown">
<div class="ui basic small button">
<span class="text">{{if $.PageIsComparePull}}{{ctx.Locale.Tr "repo.pulls.compare_compare"}}{{else}}{{ctx.Locale.Tr "repo.compare.compare_head"}}{{end}}: {{$HeadCompareName}}:{{$.HeadBranch}}</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="menu">
<div class="ui icon search input">
<i class="icon">{{svg "octicon-filter" 16}}</i>
<input name="search" placeholder="{{ctx.Locale.Tr "repo.filter_branch_and_tag"}}...">
<a href="{{.RepoLink}}/compare/{{PathEscapeSegments .BaseBranch}}{{.OtherCompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments $.HeadBranch}}" title="{{ctx.Locale.Tr "repo.pulls.switch_comparison_type"}}">{{svg "octicon-arrow-left" 16}}<div class="compare-separator">{{.CompareSeparator}}</div></a>
<div class="ui dropdown jump select-branch">
<div class="ui basic small button">
<span class="text">{{if $.PageIsComparePull}}{{ctx.Locale.Tr "repo.pulls.compare_compare"}}{{else}}{{ctx.Locale.Tr "repo.compare.compare_head"}}{{end}}: {{$HeadCompareName}}:{{$.HeadBranch}}</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="header">
<div class="ui grid">
<div class="two column row">
<a class="reference column" href="#" data-target=".head-branch-list">
<span class="text black">
{{svg "octicon-git-branch" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.branches"}}
</span>
</a>
<a class="reference column" href="#" data-target=".head-tag-list">
<span class="text black">
{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.tags"}}
</span>
</a>
<div class="menu">
<div class="ui icon search input">
<i class="icon">{{svg "octicon-filter" 16}}</i>
<input name="search" placeholder="{{ctx.Locale.Tr "repo.filter_branch_and_tag"}}...">
</div>
<div class="header">
<div class="ui grid">
<div class="two column row">
<a class="reference column" href="#" data-target=".head-branch-list">
<span class="text black">
{{svg "octicon-git-branch"}} {{ctx.Locale.Tr "repo.branches"}}
</span>
</a>
<a class="reference column" href="#" data-target=".head-tag-list">
<span class="text black">
{{svg "octicon-tag"}} {{ctx.Locale.Tr "repo.tags"}}
</span>
</a>
</div>
</div>
</div>
</div>
<div class="scrolling menu reference-list-menu head-branch-list">
{{range .HeadBranches}}
<div class="{{if eq $.HeadBranch .}}selected{{end}} item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments .}}">{{$HeadCompareName}}:{{.}}</div>
{{end}}
{{if not .PullRequestCtx.SameRepo}}
{{range .Branches}}
<div class="item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{PathEscapeSegments .}}">{{$BaseCompareName}}:{{.}}</div>
<div class="scrolling menu reference-list-menu head-branch-list">
{{range .HeadBranches}}
<a class="{{if eq $.HeadBranch .}}selected{{end}} item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments .}}">{{$HeadCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{if .OwnForkRepo}}
{{range .OwnForkRepoBranches}}
<div class="item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.OwnForkRepo.OwnerName}}/{{PathEscape $.OwnForkRepo.Name}}:{{PathEscapeSegments .}}">{{$OwnForkCompareName}}:{{.}}</div>
{{if not .PullRequestCtx.SameRepo}}
{{range .Branches}}
<a class="item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{PathEscapeSegments .}}">{{$BaseCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if .RootRepo}}
{{range .RootRepoBranches}}
<div class="item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.RootRepo.OwnerName}}/{{PathEscape $.RootRepo.Name}}:{{PathEscapeSegments .}}">{{$RootRepoCompareName}}:{{.}}</div>
{{if .OwnForkRepo}}
{{range .OwnForkRepoBranches}}
<a class="item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.OwnForkRepo.OwnerName}}/{{PathEscape $.OwnForkRepo.Name}}:{{PathEscapeSegments .}}">{{$OwnForkCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
</div>
<div class="scrolling menu reference-list-menu head-tag-list tw-hidden">
{{range .HeadTags}}
<div class="{{if eq $.HeadBranch .}}selected{{end}} item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments .}}">{{$HeadCompareName}}:{{.}}</div>
{{end}}
{{if not .PullRequestCtx.SameRepo}}
{{range .Tags}}
<div class="item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{PathEscapeSegments .}}">{{$BaseCompareName}}:{{.}}</div>
{{if .RootRepo}}
{{range .RootRepoBranches}}
<a class="item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.RootRepo.OwnerName}}/{{PathEscape $.RootRepo.Name}}:{{PathEscapeSegments .}}">{{$RootRepoCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if .OwnForkRepo}}
{{range .OwnForkRepoTags}}
<div class="item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.OwnForkRepo.OwnerName}}/{{PathEscape $.OwnForkRepo.Name}}:{{PathEscapeSegments .}}">{{$OwnForkCompareName}}:{{.}}</div>
</div>
<div class="scrolling menu reference-list-menu head-tag-list tw-hidden">
{{range .HeadTags}}
<a class="{{if eq $.HeadBranch .}}selected{{end}} item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{if not $.PullRequestCtx.SameRepo}}{{PathEscape $.HeadUser.Name}}/{{PathEscape $.HeadRepo.Name}}:{{end}}{{PathEscapeSegments .}}">{{$HeadCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{if .RootRepo}}
{{range .RootRepoTags}}
<div class="item" data-url="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.RootRepo.OwnerName}}/{{PathEscape $.RootRepo.Name}}:{{PathEscapeSegments .}}">{{$RootRepoCompareName}}:{{.}}</div>
{{if not .PullRequestCtx.SameRepo}}
{{range .Tags}}
<a class="item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.BaseName}}/{{PathEscape $.Repository.Name}}:{{PathEscapeSegments .}}">{{$BaseCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if .OwnForkRepo}}
{{range .OwnForkRepoTags}}
<a class="item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.OwnForkRepo.OwnerName}}/{{PathEscape $.OwnForkRepo.Name}}:{{PathEscapeSegments .}}">{{$OwnForkCompareName}}:{{.}}</a>
{{end}}
{{end}}
{{if .RootRepo}}
{{range .RootRepoTags}}
<a class="item" href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.BaseBranch}}{{$.CompareSeparator}}{{PathEscape $.RootRepo.OwnerName}}/{{PathEscape $.RootRepo.Name}}:{{PathEscapeSegments .}}">{{$RootRepoCompareName}}:{{.}}</a>
{{end}}
{{end}}
</div>
</div>
</div>
</div>
</div>
{{if .IsNothingToCompare}}
{{if and $.IsSigned $.AllowEmptyPr (not .Repository.IsArchived) .PageIsComparePull}}
<div class="ui segment">{{ctx.Locale.Tr "repo.pulls.nothing_to_compare_and_allow_empty_pr"}}</div>
<div class="ui info message show-form-container {{if .Flash}}tw-hidden{{end}}">
<button class="ui button primary show-form">{{ctx.Locale.Tr "repo.pulls.new"}}</button>
</div>
<div class="pullrequest-form {{if not .Flash}}tw-hidden{{end}}">
{{template "repo/issue/new_form" .}}
</div>
{{else if and .HeadIsBranch .BaseIsBranch}}
<div class="ui segment">{{ctx.Locale.Tr "repo.pulls.nothing_to_compare"}}</div>
{{else}}
<div class="ui segment">{{ctx.Locale.Tr "repo.pulls.nothing_to_compare_have_tag"}}</div>
{{end}}
{{else if and .PageIsComparePull (gt .CommitCount 0)}}
{{if .HasPullRequest}}
<div class="ui segment flex-text-block tw-gap-4">
{{template "shared/issueicon" .}}
<div class="issue-title tw-break-anywhere">
{{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx)}}
<span class="index">#{{.PullRequest.Issue.Index}}</span>
{{$showDiffBox := and .CommitCount (not .IsNothingToCompare)}}
{{if and .IsSigned .PageIsComparePull}}
{{$allowCreatePR := or $.AllowEmptyPr (not .IsNothingToCompare)}}
{{if .IsNothingToCompare}}
<div class="ui segment">
{{if $allowCreatePR}}
{{ctx.Locale.Tr "repo.pulls.nothing_to_compare_and_allow_empty_pr"}}
{{else if and .HeadIsBranch .BaseIsBranch}}
{{ctx.Locale.Tr "repo.pulls.nothing_to_compare"}}
{{else}}
{{ctx.Locale.Tr "repo.pulls.nothing_to_compare_have_tag"}}
{{end}}
</div>
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
{{ctx.Locale.Tr "repo.pulls.view"}}
</a>
</div>
{{else}}
{{if and $.IsSigned (not .Repository.IsArchived)}}
<div class="ui info message show-form-container {{if .Flash}}tw-hidden{{end}}">
<button class="ui button primary show-form">{{ctx.Locale.Tr "repo.pulls.new"}}</button>
{{end}}
{{if .HasPullRequest}}
<div class="ui segment flex-text-block tw-gap-4">
{{template "shared/issueicon" .}}
<div class="issue-title tw-break-anywhere">
{{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx)}}
<span class="index">#{{.PullRequest.Issue.Index}}</span>
</div>
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
{{ctx.Locale.Tr "repo.pulls.view"}}
</a>
</div>
{{else if .Repository.IsArchived}}
<div class="ui warning message tw-text-center">
<div class="ui warning message">
{{if .Repository.ArchivedUnix.IsZero}}
{{ctx.Locale.Tr "repo.archive.title"}}
{{else}}
{{ctx.Locale.Tr "repo.archive.title_date" (DateUtils.AbsoluteLong .Repository.ArchivedUnix)}}
{{end}}
</div>
{{end}}
{{if $.IsSigned}}
{{else if $allowCreatePR}}
<div class="ui info message pullrequest-form-toggle {{if .Flash}}tw-hidden{{end}}">
<button class="ui button primary show-panel toggle" data-panel=".pullrequest-form-toggle, .pullrequest-form">{{ctx.Locale.Tr "repo.pulls.new"}}</button>
</div>
<div class="pullrequest-form {{if not .Flash}}tw-hidden{{end}}">
{{template "repo/issue/new_form" .}}
</div>
{{end}}
{{$showDiffBox = true}}
{{else}}{{/* not singed-in or not for pull-request */}}
{{if not .CommitCount}}
<div class="ui segment">{{ctx.Locale.Tr "repo.commits.nothing_to_compare"}}</div>
{{end}}
{{end}}
{{else if not .IsNothingToCompare}}
{{$showDiffBox = true}}
{{end}}
</div>
{{if $showDiffBox}}
<div class="ui container fluid padded">
{{template "repo/commits_table" .}}
{{template "repo/diff/box" .}}
</div>
<div class="ui container fluid padded tw-my-4">
{{template "repo/commits_table" .}}
{{template "repo/diff/box" .}}
</div>
{{end}}
</div>
{{template "base/footer" .}}

@ -14,14 +14,13 @@
{{end}}
</div>
{{end}}
{{if .Repository.IsBroken}}
<div class="ui segment center">
{{ctx.Locale.Tr "repo.broken_message"}}
</div>
<div class="ui segment center">{{ctx.Locale.Tr "repo.broken_message"}}</div>
{{else if .RepoHasContentsWithoutBranch}}
<div class="ui segment center">{{ctx.Locale.Tr "repo.no_branch"}}</div>
{{else if .CanWriteCode}}
<h4 class="ui top attached header">
{{ctx.Locale.Tr "repo.quick_guide"}}
</h4>
<h4 class="ui top attached header">{{ctx.Locale.Tr "repo.quick_guide"}}</h4>
<div class="ui attached guide table segment empty-repo-guide">
<div class="item">
<h3>{{ctx.Locale.Tr "repo.clone_this_repo"}} <small>{{ctx.Locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository"}}</small></h3>
@ -66,12 +65,10 @@ git push -u origin {{.Repository.DefaultBranch}}</code></pre>
</div>
</div>
{{end}}
{{else}}
<div class="ui segment center">
{{ctx.Locale.Tr "repo.empty_message"}}
</div>
{{end}}
</div>
</div>
{{else}}
<div class="ui segment center">{{ctx.Locale.Tr "repo.empty_message"}}</div>
{{end}}
</div>
</div>
</div>

@ -162,7 +162,7 @@
</a>
{{end}}
{{if and .EnableActions (.Permission.CanRead ctx.Consts.RepoUnitTypeActions)}}
{{if and .EnableActions (.Permission.CanRead ctx.Consts.RepoUnitTypeActions) (not .IsEmptyRepo)}}
<a class="{{if .PageIsActions}}active {{end}}item" href="{{.RepoLink}}/actions">
{{svg "octicon-play"}} {{ctx.Locale.Tr "actions.actions"}}
{{if .Repository.NumOpenActionRuns}}

@ -2,15 +2,15 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -108,8 +108,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,15 +2,15 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -109,8 +109,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,15 +2,15 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -82,8 +82,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,15 +2,15 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -38,7 +38,7 @@
</div>
</div>
<div id="migrate_items">
<div id="migrate_items" class="inline field">
<span class="help">{{ctx.Locale.Tr "repo.migrate.migrate_items_options"}}</span>
<div class="inline field">
<label></label>
@ -124,8 +124,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,14 +2,14 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -34,7 +34,7 @@
</div>
</div>
<div id="migrate_items">
<div id="migrate_items" class="inline field">
<span class="help">{{ctx.Locale.Tr "repo.migrate.migrate_items_options"}}</span>
<div class="inline field">
<label></label>
@ -120,8 +120,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,14 +2,14 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -36,7 +36,7 @@
<label>{{ctx.Locale.Tr "repo.migrate_items_wiki"}}</label>
</div>
</div>
<div id="migrate_items">
<div id="migrate_items" class="inline field">
<span class="help">{{ctx.Locale.Tr "repo.migrate.migrate_items_options"}}</span>
<div class="inline field">
<label></label>
@ -122,8 +122,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,14 +2,14 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -33,7 +33,7 @@
<label>{{ctx.Locale.Tr "repo.migrate_items_wiki"}}</label>
</div>
</div>
<div id="migrate_items">
<div id="migrate_items" class="inline field">
<span class="help">{{ctx.Locale.Tr "repo.migrate.migrate_items_options"}}</span>
<div class="inline field">
<label></label>
@ -119,8 +119,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,14 +2,14 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -34,7 +34,7 @@
</div>
</div>
<div id="migrate_items">
<div id="migrate_items" class="inline field">
<span class="help">{{ctx.Locale.Tr "repo.migrate.migrate_items_options"}}</span>
<div class="inline field">
<label></label>
@ -122,8 +122,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

@ -2,15 +2,15 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<h3 class="ui top attached header">
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
<input id="service_type" type="hidden" name="service" value="{{.service}}">
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
<form class="ui form left-right-form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
@ -30,7 +30,8 @@
{{template "repo/migrate/options" .}}
<div id="migrate_items">
<div id="migrate_items" class="inline field">
<label></label>
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.migrate_items"}}</label>
<div class="ui checkbox">
@ -108,8 +109,8 @@
{{ctx.Locale.Tr "repo.migrate_repo"}}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</div>

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

Loading…
Cancel
Save