diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 80017a11764ee..5016f29cf9ffd 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1097,6 +1097,9 @@ PATH = ;; Number of items that are displayed in home feed ;FEED_PAGING_NUM = 20 ;; +;; Number of items that are displayed in a single subsitemap +;SITEMAP_PAGING_NUM = 20 +;; ;; Number of maximum commits displayed in commit graph. ;GRAPH_MAX_COMMIT_NUM = 100 ;; diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 15628a7def051..df659b6540119 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -174,6 +174,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `MEMBERS_PAGING_NUM`: **20**: Number of members that are shown in organization members. - `FEED_MAX_COMMIT_NUM`: **5**: Number of maximum commits shown in one activity feed. - `FEED_PAGING_NUM`: **20**: Number of items that are displayed in home feed. +- `SITEMAP_PAGING_NUM`: **20**: Number of items that are displayed in a single subsitemap. - `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph. - `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment. - `DEFAULT_THEME`: **auto**: \[auto, gitea, arc-green\]: Set the default theme for the Gitea install. diff --git a/docs/content/doc/developers/oauth2-provider.md b/docs/content/doc/developers/oauth2-provider.md index efe78eed97659..ce6e9aad8cbe5 100644 --- a/docs/content/doc/developers/oauth2-provider.md +++ b/docs/content/doc/developers/oauth2-provider.md @@ -41,13 +41,13 @@ To use the Authorization Code Grant as a third party application it is required ## Scopes -Currently Gitea does not support scopes (see [#4300](https://github.com/go-gitea/gitea/issues/4300)) and all third party applications will be granted access to all resources of the user and his/her organizations. +Currently Gitea does not support scopes (see [#4300](https://github.com/go-gitea/gitea/issues/4300)) and all third party applications will be granted access to all resources of the user and their organizations. ## Example **Note:** This example does not use PKCE. -1. Redirect to user to the authorization endpoint in order to get his/her consent for accessing the resources: +1. Redirect to user to the authorization endpoint in order to get their consent for accessing the resources: ```curl https://[YOUR-GITEA-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI& response_type=code&state=STATE diff --git a/docs/content/doc/developers/oauth2-provider.zh-tw.md b/docs/content/doc/developers/oauth2-provider.zh-tw.md index a2013feac4cf3..179c6342cdd6a 100644 --- a/docs/content/doc/developers/oauth2-provider.zh-tw.md +++ b/docs/content/doc/developers/oauth2-provider.zh-tw.md @@ -46,7 +46,7 @@ Gitea 支援作為 OAuth2 提供者,能讓第三方程式能在使用者同意 **備註:** 此範例未使用 PKCE。 1. 重新導向使用者到 authorization endpoint 以獲得他同意授權存取資源: - + ```curl https://[YOUR-GITEA-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI& response_type=code&state=STATE diff --git a/integrations/mssql.ini.tmpl b/integrations/mssql.ini.tmpl index da15e9ef69ee4..d4d1517011e2a 100644 --- a/integrations/mssql.ini.tmpl +++ b/integrations/mssql.ini.tmpl @@ -10,11 +10,13 @@ PASSWD = {{TEST_MSSQL_PASSWORD}} SSL_MODE = disable [indexer] -ISSUE_INDEXER_PATH = integrations/gitea-integration-mssql/indexers/issues.bleve -ISSUE_INDEXER_QUEUE_DIR = integrations/gitea-integration-mssql/indexers/issues.queue REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/gitea-integration-mssql/indexers/repos.bleve +[queue.issue_indexer] +PATH = integrations/gitea-integration-mssql/indexers/issues.bleve +DATADIR = integrations/gitea-integration-mssql/indexers/issues.queue + [queue] TYPE = immediate diff --git a/integrations/mysql.ini.tmpl b/integrations/mysql.ini.tmpl index 4df49336424a6..8f610326e245b 100644 --- a/integrations/mysql.ini.tmpl +++ b/integrations/mysql.ini.tmpl @@ -10,12 +10,14 @@ PASSWD = {{TEST_MYSQL_PASSWORD}} SSL_MODE = disable [indexer] -ISSUE_INDEXER_TYPE = elasticsearch -ISSUE_INDEXER_CONN_STR = http://elastic:changeme@elasticsearch:9200 -ISSUE_INDEXER_QUEUE_DIR = integrations/gitea-integration-mysql/indexers/issues.queue REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/gitea-integration-mysql/indexers/repos.bleve +[queue.issue_indexer] +TYPE = elasticsearch +CONN_STR = http://elastic:changeme@elasticsearch:9200 +DATADIR = integrations/gitea-integration-mysql/indexers/issues.queue + [queue] TYPE = immediate diff --git a/integrations/mysql8.ini.tmpl b/integrations/mysql8.ini.tmpl index 4b63dd51a1fdd..5133058237c20 100644 --- a/integrations/mysql8.ini.tmpl +++ b/integrations/mysql8.ini.tmpl @@ -10,11 +10,13 @@ PASSWD = {{TEST_MYSQL8_PASSWORD}} SSL_MODE = disable [indexer] -ISSUE_INDEXER_PATH = integrations/gitea-integration-mysql8/indexers/issues.bleve -ISSUE_INDEXER_QUEUE_DIR = integrations/gitea-integration-mysql8/indexers/issues.queue REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/gitea-integration-mysql8/indexers/repos.bleve +[queue.issue_indexer] +PATH = integrations/gitea-integration-mysql8/indexers/issues.bleve +DATADIR = integrations/gitea-integration-mysql8/indexers/issues.queue + [queue] TYPE = immediate diff --git a/integrations/pgsql.ini.tmpl b/integrations/pgsql.ini.tmpl index 5b54a02c9fac8..15cd6a057b2c9 100644 --- a/integrations/pgsql.ini.tmpl +++ b/integrations/pgsql.ini.tmpl @@ -11,11 +11,13 @@ SCHEMA = {{TEST_PGSQL_SCHEMA}} SSL_MODE = disable [indexer] -ISSUE_INDEXER_PATH = integrations/gitea-integration-pgsql/indexers/issues.bleve -ISSUE_INDEXER_QUEUE_DIR = integrations/gitea-integration-pgsql/indexers/issues.queue REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/gitea-integration-pgsql/indexers/repos.bleve +[queue.issue_indexer] +PATH = integrations/gitea-integration-pgsql/indexers/issues.bleve +DATADIR = integrations/gitea-integration-pgsql/indexers/issues.queue + [queue] TYPE = immediate diff --git a/integrations/sqlite.ini.tmpl b/integrations/sqlite.ini.tmpl index 2da7fd65d392b..fa57e1aa96b88 100644 --- a/integrations/sqlite.ini.tmpl +++ b/integrations/sqlite.ini.tmpl @@ -6,11 +6,13 @@ DB_TYPE = sqlite3 PATH = integrations/gitea-integration-sqlite/gitea.db [indexer] -ISSUE_INDEXER_PATH = integrations/gitea-integration-sqlite/indexers/issues.bleve -ISSUE_INDEXER_QUEUE_DIR = integrations/gitea-integration-sqlite/indexers/issues.queue REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/gitea-integration-sqlite/indexers/repos.bleve +[queue.issue_indexer] +PATH = integrations/gitea-integration-sqlite/indexers/issues.bleve +DATADIR = integrations/gitea-integration-sqlite/indexers/issues.queue + [queue] TYPE = immediate diff --git a/models/migrations/v154.go b/models/migrations/v154.go index 11407c30ee140..bb17fb4725a04 100644 --- a/models/migrations/v154.go +++ b/models/migrations/v154.go @@ -30,7 +30,7 @@ func addTimeStamps(x *xorm.Engine) error { return err } - // Follow represents relations of user and his/her followers. + // Follow represents relations of user and their followers. type Follow struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } diff --git a/models/org.go b/models/org.go index 849c9b985b38a..efcb7183e7669 100644 --- a/models/org.go +++ b/models/org.go @@ -149,7 +149,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { } } - // Delete member in his/her teams. + // Delete member in their teams. teams, err := organization.GetUserOrgTeams(ctx, org.ID, userID) if err != nil { return err diff --git a/models/task.go b/models/task.go index cabb96c60831e..67f04d9562f42 100644 --- a/models/task.go +++ b/models/task.go @@ -156,14 +156,14 @@ type ErrTaskDoesNotExist struct { Type structs.TaskType } -// IsErrTaskDoesNotExist checks if an error is a ErrTaskIsNotExist. +// IsErrTaskDoesNotExist checks if an error is a ErrTaskDoesNotExist. func IsErrTaskDoesNotExist(err error) bool { _, ok := err.(ErrTaskDoesNotExist) return ok } func (err ErrTaskDoesNotExist) Error() string { - return fmt.Sprintf("task is not exist [id: %d, repo_id: %d, type: %d]", + return fmt.Sprintf("task does not exist [id: %d, repo_id: %d, type: %d]", err.ID, err.RepoID, err.Type) } diff --git a/models/user/follow.go b/models/user/follow.go index 6b02486c438c7..5f24f706d16b5 100644 --- a/models/user/follow.go +++ b/models/user/follow.go @@ -9,7 +9,7 @@ import ( "code.gitea.io/gitea/modules/timeutil" ) -// Follow represents relations of user and his/her followers. +// Follow represents relations of user and their followers. type Follow struct { ID int64 `xorm:"pk autoincr"` UserID int64 `xorm:"UNIQUE(follow)"` diff --git a/models/user/user.go b/models/user/user.go index f7d457b91b5a5..9460bd38fe428 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -86,7 +86,7 @@ type User struct { PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` // MustChangePassword is an attribute that determines if a user - // is to change his/her password after registration. + // is to change their password after registration. MustChangePassword bool `xorm:"NOT NULL DEFAULT false"` LoginType auth.Type @@ -537,7 +537,7 @@ func GetUserSalt() (string, error) { return hex.EncodeToString(rBytes), nil } -// NewGhostUser creates and returns a fake user for someone has deleted his/her account. +// NewGhostUser creates and returns a fake user for someone has deleted their account. func NewGhostUser() *User { return &User{ ID: -1, diff --git a/modules/doctor/mergebase.go b/modules/doctor/mergebase.go index 46369290a13d7..2da91cdcc35f6 100644 --- a/modules/doctor/mergebase.go +++ b/modules/doctor/mergebase.go @@ -30,6 +30,9 @@ func iteratePRs(ctx context.Context, repo *repo_model.Repository, each func(*rep } func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) error { + if err := git.InitOnceWithSync(ctx); err != nil { + return err + } numRepos := 0 numPRs := 0 numPRsUpdated := 0 diff --git a/modules/doctor/misc.go b/modules/doctor/misc.go index 9bee78303e1a5..24175fcaf4bec 100644 --- a/modules/doctor/misc.go +++ b/modules/doctor/misc.go @@ -189,6 +189,75 @@ func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) err return nil } +func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) error { + if err := git.InitOnceWithSync(ctx); err != nil { + return err + } + + numRepos := 0 + numNeedUpdate := 0 + numWritten := 0 + if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { + numRepos++ + + commitGraphExists := func() (bool, error) { + // Check commit-graph exists + commitGraphFile := path.Join(repo.RepoPath(), `objects/info/commit-graph`) + isExist, err := util.IsExist(commitGraphFile) + if err != nil { + logger.Error("Unable to check if %s exists. Error: %v", commitGraphFile, err) + return false, err + } + + if !isExist { + commitGraphsDir := path.Join(repo.RepoPath(), `objects/info/commit-graphs`) + isExist, err = util.IsExist(commitGraphsDir) + if err != nil { + logger.Error("Unable to check if %s exists. Error: %v", commitGraphsDir, err) + return false, err + } + } + return isExist, nil + } + + isExist, err := commitGraphExists() + if err != nil { + return err + } + if !isExist { + numNeedUpdate++ + if autofix { + if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil { + logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err) + return err + } + isExist, err := commitGraphExists() + if err != nil { + return err + } + if isExist { + numWritten++ + logger.Info("Commit-graph written: %s", repo.FullName()) + } else { + logger.Warn("No commit-graph written: %s", repo.FullName()) + } + } + } + return nil + }); err != nil { + logger.Critical("Unable to checkCommitGraph: %v", err) + return err + } + + if autofix { + logger.Info("Wrote commit-graph files for %d of %d repositories.", numWritten, numRepos) + } else { + logger.Info("Checked %d repositories, %d without commit-graphs.", numRepos, numNeedUpdate) + } + + return nil +} + func init() { Register(&Check{ Title: "Check if SCRIPT_TYPE is available", @@ -225,4 +294,11 @@ func init() { Run: checkDaemonExport, Priority: 8, }) + Register(&Check{ + Title: "Check commit-graphs", + Name: "check-commit-graphs", + IsDefault: false, + Run: checkCommitGraph, + Priority: 9, + }) } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 88f306b3fa7e3..7be0842b56a6d 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -207,6 +207,7 @@ var ( // UI settings UI = struct { ExplorePagingNum int + SitemapPagingNum int IssuePagingNum int RepoSearchPagingNum int MembersPagingNum int @@ -260,6 +261,7 @@ var ( } `ini:"ui.meta"` }{ ExplorePagingNum: 20, + SitemapPagingNum: 20, IssuePagingNum: 10, RepoSearchPagingNum: 10, MembersPagingNum: 20, diff --git a/modules/sitemap/sitemap.go b/modules/sitemap/sitemap.go new file mode 100644 index 0000000000000..14953765abbc2 --- /dev/null +++ b/modules/sitemap/sitemap.go @@ -0,0 +1,69 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package sitemap + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "time" +) + +// sitemapFileLimit contains the maximum size of a sitemap file +const sitemapFileLimit = 50 * 1024 * 1024 + +// Url represents a single sitemap entry +type URL struct { + URL string `xml:"loc"` + LastMod *time.Time `xml:"lastmod,omitempty"` +} + +// SitemapUrl represents a sitemap +type Sitemap struct { + XMLName xml.Name + Namespace string `xml:"xmlns,attr"` + + URLs []URL `xml:"url"` +} + +// NewSitemap creates a sitemap +func NewSitemap() *Sitemap { + return &Sitemap{ + XMLName: xml.Name{Local: "urlset"}, + Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9", + } +} + +// NewSitemap creates a sitemap index. +func NewSitemapIndex() *Sitemap { + return &Sitemap{ + XMLName: xml.Name{Local: "sitemapindex"}, + Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9", + } +} + +// Add adds a URL to the sitemap +func (s *Sitemap) Add(u URL) { + s.URLs = append(s.URLs, u) +} + +// Write writes the sitemap to a response +func (s *Sitemap) WriteTo(w io.Writer) (int64, error) { + if len(s.URLs) > 50000 { + return 0, fmt.Errorf("The sitemap contains too many URLs: %d", len(s.URLs)) + } + buf := bytes.NewBufferString(xml.Header) + if err := xml.NewEncoder(buf).Encode(s); err != nil { + return 0, err + } + if err := buf.WriteByte('\n'); err != nil { + return 0, err + } + if buf.Len() > sitemapFileLimit { + return 0, fmt.Errorf("The sitemap is too big: %d", buf.Len()) + } + return buf.WriteTo(w) +} diff --git a/modules/sitemap/sitemap_test.go b/modules/sitemap/sitemap_test.go new file mode 100644 index 0000000000000..63007b84791f6 --- /dev/null +++ b/modules/sitemap/sitemap_test.go @@ -0,0 +1,77 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package sitemap + +import ( + "bytes" + "encoding/xml" + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestOk(t *testing.T) { + testReal := func(s *Sitemap, name string, urls []URL, expected string) { + for _, url := range urls { + s.Add(url) + } + buf := &bytes.Buffer{} + _, err := s.WriteTo(buf) + assert.NoError(t, nil, err) + assert.Equal(t, xml.Header+"<"+name+" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"+expected+"\n", buf.String()) + } + test := func(urls []URL, expected string) { + testReal(NewSitemap(), "urlset", urls, expected) + testReal(NewSitemapIndex(), "sitemapindex", urls, expected) + } + + ts := time.Unix(1651322008, 0).UTC() + + test( + []URL{}, + "", + ) + test( + []URL{ + {URL: "https://gitea.io/test1", LastMod: &ts}, + }, + "https://gitea.io/test12022-04-30T12:33:28Z", + ) + test( + []URL{ + {URL: "https://gitea.io/test2", LastMod: nil}, + }, + "https://gitea.io/test2", + ) + test( + []URL{ + {URL: "https://gitea.io/test1", LastMod: &ts}, + {URL: "https://gitea.io/test2", LastMod: nil}, + }, + "https://gitea.io/test12022-04-30T12:33:28Z"+ + "https://gitea.io/test2", + ) +} + +func TestTooManyURLs(t *testing.T) { + s := NewSitemap() + for i := 0; i < 50001; i++ { + s.Add(URL{URL: fmt.Sprintf("https://gitea.io/test%d", i)}) + } + buf := &bytes.Buffer{} + _, err := s.WriteTo(buf) + assert.EqualError(t, err, "The sitemap contains too many URLs: 50001") +} + +func TestSitemapTooBig(t *testing.T) { + s := NewSitemap() + s.Add(URL{URL: strings.Repeat("b", sitemapFileLimit)}) + buf := &bytes.Buffer{} + _, err := s.WriteTo(buf) + assert.EqualError(t, err, "The sitemap is too big: 52428931") +} diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 06ccd2e76f7d7..c41c8bd1e505c 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -274,7 +274,7 @@ func MustChangePassword(ctx *context.Context) { ctx.HTML(http.StatusOK, tplMustChangePassword) } -// MustChangePasswordPost response for updating a user's password after his/her +// MustChangePasswordPost response for updating a user's password after their // account was created by an admin func MustChangePasswordPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.MustChangePasswordForm) diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index f64642bc95d36..b5485f5832d4d 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -11,7 +11,9 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sitemap" ) const ( @@ -30,11 +32,21 @@ type RepoSearchOptions struct { // RenderRepoSearch render repositories search page func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { - page := ctx.FormInt("page") + // Sitemap index for sitemap paths + page := int(ctx.ParamsInt64("idx")) + isSitemap := ctx.Params("idx") != "" + if page <= 1 { + page = ctx.FormInt("page") + } + if page <= 0 { page = 1 } + if isSitemap { + opts.PageSize = setting.UI.SitemapPagingNum + } + var ( repos []*repo_model.Repository count int64 @@ -100,6 +112,18 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { ctx.ServerError("SearchRepository", err) return } + if isSitemap { + m := sitemap.NewSitemap() + for _, item := range repos { + m.Add(sitemap.URL{URL: item.HTMLURL(), LastMod: item.UpdatedUnix.AsTimePtr()}) + } + ctx.Resp.Header().Set("Content-Type", "text/xml") + if _, err := m.WriteTo(ctx.Resp); err != nil { + log.Error("Failed writing sitemap: %v", err) + } + return + } + ctx.Data["Keyword"] = keyword ctx.Data["Total"] = count ctx.Data["Repos"] = repos diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go index ea0d7d5f9d8c3..ea3d83e8d6c1c 100644 --- a/routers/web/explore/user.go +++ b/routers/web/explore/user.go @@ -12,7 +12,9 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sitemap" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" ) @@ -33,11 +35,20 @@ func isKeywordValid(keyword string) bool { // RenderUserSearch render user search page func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName base.TplName) { - opts.Page = ctx.FormInt("page") + // Sitemap index for sitemap paths + opts.Page = int(ctx.ParamsInt64("idx")) + isSitemap := ctx.Params("idx") != "" + if opts.Page <= 1 { + opts.Page = ctx.FormInt("page") + } if opts.Page <= 1 { opts.Page = 1 } + if isSitemap { + opts.PageSize = setting.UI.SitemapPagingNum + } + var ( users []*user_model.User count int64 @@ -73,6 +84,18 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, return } } + if isSitemap { + m := sitemap.NewSitemap() + for _, item := range users { + m.Add(sitemap.URL{URL: item.HTMLURL(), LastMod: item.UpdatedUnix.AsTimePtr()}) + } + ctx.Resp.Header().Set("Content-Type", "text/xml") + if _, err := m.WriteTo(ctx.Resp); err != nil { + log.Error("Failed writing sitemap: %v", err) + } + return + } + ctx.Data["Keyword"] = opts.Keyword ctx.Data["Total"] = count ctx.Data["Users"] = users diff --git a/routers/web/home.go b/routers/web/home.go index 9036814ddfa9d..0c74987ba7c47 100644 --- a/routers/web/home.go +++ b/routers/web/home.go @@ -7,11 +7,18 @@ package web import ( "net/http" + "strconv" + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sitemap" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers/web/auth" "code.gitea.io/gitea/routers/web/user" @@ -59,6 +66,52 @@ func Home(ctx *context.Context) { ctx.HTML(http.StatusOK, tplHome) } +// HomeSitemap renders the main sitemap +func HomeSitemap(ctx *context.Context) { + m := sitemap.NewSitemapIndex() + if !setting.Service.Explore.DisableUsersPage { + _, cnt, err := user_model.SearchUsers(&user_model.SearchUserOptions{ + Type: user_model.UserTypeIndividual, + ListOptions: db.ListOptions{PageSize: 1}, + IsActive: util.OptionalBoolTrue, + Visible: []structs.VisibleType{structs.VisibleTypePublic}, + }) + if err != nil { + ctx.ServerError("SearchUsers", err) + return + } + count := int(cnt) + idx := 1 + for i := 0; i < count; i += setting.UI.SitemapPagingNum { + m.Add(sitemap.URL{URL: setting.AppURL + "explore/users/sitemap-" + strconv.Itoa(idx) + ".xml"}) + idx++ + } + } + + _, cnt, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{ + ListOptions: db.ListOptions{ + PageSize: 1, + }, + Actor: ctx.Doer, + AllPublic: true, + }) + if err != nil { + ctx.ServerError("SearchRepository", err) + return + } + count := int(cnt) + idx := 1 + for i := 0; i < count; i += setting.UI.SitemapPagingNum { + m.Add(sitemap.URL{URL: setting.AppURL + "explore/repos/sitemap-" + strconv.Itoa(idx) + ".xml"}) + idx++ + } + + ctx.Resp.Header().Set("Content-Type", "text/xml") + if _, err := m.WriteTo(ctx.Resp); err != nil { + log.Error("Failed writing sitemap: %v", err) + } +} + // NotFound render 404 page func NotFound(ctx *context.Context) { ctx.Data["Title"] = "Page Not Found" diff --git a/routers/web/web.go b/routers/web/web.go index 4896bdb1e46f3..80469ef7cd599 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -294,6 +294,7 @@ func RegisterRoutes(m *web.Route) { // Routers. // for health check m.Get("/", Home) + m.Get("/sitemap.xml", ignExploreSignIn, HomeSitemap) m.Group("/.well-known", func() { m.Get("/openid-configuration", auth.OIDCWellKnown) m.Group("", func() { @@ -310,7 +311,9 @@ func RegisterRoutes(m *web.Route) { ctx.Redirect(setting.AppSubURL + "/explore/repos") }) m.Get("/repos", explore.Repos) + m.Get("/repos/sitemap-{idx}.xml", explore.Repos) m.Get("/users", explore.Users) + m.Get("/users/sitemap-{idx}.xml", explore.Users) m.Get("/organizations", explore.Organizations) m.Get("/code", explore.Code) m.Get("/topics/search", explore.TopicSearch) diff --git a/templates/repo/settings/protected_branch.tmpl b/templates/repo/settings/protected_branch.tmpl index 597567f057aa5..e7228f2ac9e15 100644 --- a/templates/repo/settings/protected_branch.tmpl +++ b/templates/repo/settings/protected_branch.tmpl @@ -127,7 +127,7 @@
- +

{{.i18n.Tr "repo.settings.protect_check_status_contexts_desc"}}

diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index eb59bcbe38f7f..a508db39c5e6a 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -192,7 +192,8 @@ export function initGlobalDropzone() { thumbnailWidth: 480, thumbnailHeight: 480, init() { - this.on('success', (_file, data) => { + this.on('success', (file, data) => { + file.uuid = data.uuid; const input = $(``).val(data.uuid); $dropzone.find('.files').append(input); }); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 6cdde6a1e4c27..2bf80d551141b 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -300,6 +300,7 @@ async function onEditContent(event) { thumbnailHeight: 480, init() { this.on('success', (file, data) => { + file.uuid = data.uuid; fileUuidDict[file.uuid] = {submitted: false}; const input = $(``).val(data.uuid); $dropzone.find('.files').append(input);