From 0be7543560da892e246ad08f4aad2825fc9117ae Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 16 Jun 2026 10:40:13 -0700 Subject: [PATCH 1/4] fix(mssql): expand legacy issue and comment long-text columns (#38120) ## Summary This fixes pull request creation failures on upgraded MSSQL instances where legacy `issue` and `comment` long-text columns are still limited to `nvarchar(4000)`. When a PR is created, Gitea stores a pull request push timeline comment containing JSON with `commit_ids`. For PRs with many commits, that payload can exceed 4000 characters and MSSQL rejects the insert with: > String or binary data would be truncated in table 'comment', column 'content' This change adds a migration that expands the affected legacy MSSQL columns to `NVARCHAR(MAX)`. The previous migration in models/migrations/v1_16/v191.go only applies to MySQL, not MSSQL. migration now skips columns already using NVARCHAR(MAX) / VARCHAR(MAX) Closes #37893 ## Changes - add migration `338` for MSSQL-only long-text expansion - expand: - `issue.content` - `comment.content` - `comment.patch` - add an MSSQL regression test that starts from a legacy `VARCHAR(4000)` schema and verifies inserts larger than 4000 characters succeed after migration ## Why this approach The current model already declares these fields as `LONGTEXT`, so the bug is caused by stale upgraded MSSQL schemas rather than by PR creation logic itself. Fixing the schema is the smallest and safest change, and also prevents similar truncation issues for other long issue/comment content. --- models/migrations/migrations.go | 1 + models/migrations/v1_27/v338.go | 73 ++++++++++++++++++++++++++++ models/migrations/v1_27/v338_test.go | 52 ++++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 models/migrations/v1_27/v338.go create mode 100644 models/migrations/v1_27/v338_test.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 81a618a207..be7b333a2e 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -415,6 +415,7 @@ func prepareMigrationTasks() []*migration { newMigration(335, "Add reusable workflow fields and action_run_attempt_job_id_index table for ActionRunJob", v1_27.AddReusableWorkflowFieldsToActionRunJob), newMigration(336, "Add ActionRunJobSummary table", v1_27.AddActionRunJobSummaryTable), newMigration(337, "Add visibility to team", v1_27.AddVisibilityToTeam), + newMigration(338, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL), } return preparedMigrations } diff --git a/models/migrations/v1_27/v338.go b/models/migrations/v1_27/v338.go new file mode 100644 index 0000000000..a495907174 --- /dev/null +++ b/models/migrations/v1_27/v338.go @@ -0,0 +1,73 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_27 + +import ( + "fmt" + "strings" + + "gitea.dev/models/db" + "gitea.dev/models/migrations/base" + + "xorm.io/xorm/schemas" +) + +type issueWithLongTextContent struct { + Content string `xorm:"LONGTEXT"` +} + +func (issueWithLongTextContent) TableName() string { + return "issue" +} + +type commentWithLongTextFields struct { + Content string `xorm:"LONGTEXT"` + PatchQuoted string `xorm:"LONGTEXT patch"` +} + +func (commentWithLongTextFields) TableName() string { + return "comment" +} + +func isMSSQLMaxTextColumn(column *schemas.Column) bool { + if column.Length != -1 { + return false + } + return strings.EqualFold(column.SQLType.Name, schemas.Varchar) || strings.EqualFold(column.SQLType.Name, schemas.NVarchar) +} + +func modifyLongTextColumnsForMSSQL(x db.EngineMigration, bean any, columnNames ...string) error { + table, err := x.TableInfo(bean) + if err != nil { + return err + } + + for _, columnName := range columnNames { + column := table.GetColumn(columnName) + if column == nil { + return fmt.Errorf("column %s does not exist in table %s", columnName, table.Name) + } + if isMSSQLMaxTextColumn(column) { + continue + } + if err := base.ModifyColumn(x, table.Name, column); err != nil { + return fmt.Errorf("modify %s.%s: %w", table.Name, columnName, err) + } + } + + return nil +} + +// ExpandIssueAndCommentLongTextFieldsForMSSQL expands legacy MSSQL nvarchar(4000) +// columns to nvarchar(max) so PR push comments and long issue content are not truncated. +func ExpandIssueAndCommentLongTextFieldsForMSSQL(x db.EngineMigration) error { + if x.Dialect().URI().DBType != schemas.MSSQL { + return nil + } + + if err := modifyLongTextColumnsForMSSQL(x, new(issueWithLongTextContent), "content"); err != nil { + return err + } + return modifyLongTextColumnsForMSSQL(x, new(commentWithLongTextFields), "content", "patch") +} diff --git a/models/migrations/v1_27/v338_test.go b/models/migrations/v1_27/v338_test.go new file mode 100644 index 0000000000..4e47253779 --- /dev/null +++ b/models/migrations/v1_27/v338_test.go @@ -0,0 +1,52 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_27 + +import ( + "strings" + "testing" + + "gitea.dev/models/migrations/migrationtest" + "gitea.dev/modules/setting" + + "github.com/stretchr/testify/require" +) + +type issueBeforeLongTextMSSQLMigration struct { + ID int64 `xorm:"pk autoincr"` + Content string `xorm:"VARCHAR(4000)"` +} + +func (issueBeforeLongTextMSSQLMigration) TableName() string { + return "issue" +} + +type commentBeforeLongTextMSSQLMigration struct { + ID int64 `xorm:"pk autoincr"` + Content string `xorm:"VARCHAR(4000)"` + Patch string `xorm:"VARCHAR(4000) patch"` +} + +func (commentBeforeLongTextMSSQLMigration) TableName() string { + return "comment" +} + +func Test_ExpandIssueAndCommentLongTextFieldsForMSSQL(t *testing.T) { + if !setting.Database.Type.IsMSSQL() { + t.Skip("Only MSSQL needs to expand legacy nvarchar(4000) long-text columns") + } + + x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(issueBeforeLongTextMSSQLMigration), new(commentBeforeLongTextMSSQLMigration)) + defer deferrable() + + require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x)) + require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x)) + + longText := strings.Repeat("x", 5000) + _, err := x.Insert(&issueBeforeLongTextMSSQLMigration{Content: longText}) + require.NoError(t, err) + + _, err = x.Insert(&commentBeforeLongTextMSSQLMigration{Content: longText, Patch: longText}) + require.NoError(t, err) +} From 795531cea0644dd2d0ad5eddc9e56e70d3eb1db0 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 17 Jun 2026 01:24:58 +0000 Subject: [PATCH 2/4] [skip ci] Updated translations via Crowdin --- options/locale/locale_zh-CN.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json index 03c6e417ce..7c3d45f528 100644 --- a/options/locale/locale_zh-CN.json +++ b/options/locale/locale_zh-CN.json @@ -2865,6 +2865,14 @@ "org.teams.all_repositories_read_permission_desc": "此团队授予读取所有仓库的访问权限: 成员可以查看和克隆仓库。", "org.teams.all_repositories_write_permission_desc": "此团队授予修改所有仓库的访问权限: 成员可以查看和推送至仓库。", "org.teams.all_repositories_admin_permission_desc": "该团队拥有 管理 所有仓库的权限:团队成员可以读取、克隆、推送以及添加其它仓库协作者。", + "org.teams.visibility": "可见性", + "org.teams.visibility_private": "私有", + "org.teams.visibility_private_helper": "仅对团队成员和组织所有者可见。", + "org.teams.visibility_limited": "受限", + "org.teams.visibility_limited_helper": "对组织所有成员可见。", + "org.teams.visibility_public": "公开", + "org.teams.visibility_public_helper": "对任何登录用户可见。", + "org.teams.owners_visibility_fixed": "所有者的团队可见性无法更改。", "org.teams.invite.title": "您已被邀请加入组织 %s 中的团队 %s。", "org.teams.invite.by": "邀请人 %s", "org.teams.invite.description": "请点击下面的按钮加入团队。", From 9e84deb969aff5c1115c2984e41250f28c78451f Mon Sep 17 00:00:00 2001 From: bircni Date: Wed, 17 Jun 2026 06:50:25 +0200 Subject: [PATCH 3/4] fix: Various sec fixes 2 (#38108) - Enforce repository token scope on RSS/Atom feed endpoints so a PAT without repo scope can no longer read private repo commit data. - Block HTTP redirects during repository migration clones to prevent SSRF reaching internal addresses via an attacker-controlled redirect. - Redact the notification subject after repo access is revoked so private issue/PR metadata is no longer leaked through the notification API. --------- Co-authored-by: Lunny Xiao --- modules/git/repo.go | 3 +++ modules/git/repo_test.go | 23 ++++++++++++++++ routers/web/feed/branch.go | 3 +++ routers/web/feed/file.go | 3 +++ routers/web/feed/release.go | 3 +++ routers/web/feed/render.go | 9 +++++++ routers/web/feed/repo.go | 3 +++ services/convert/notification.go | 29 +++++++++++--------- services/convert/notification_test.go | 30 +++++++++++++++++++++ tests/integration/feed_repo_test.go | 39 +++++++++++++++++++++++++++ 10 files changed, 132 insertions(+), 13 deletions(-) diff --git a/modules/git/repo.go b/modules/git/repo.go index e17e7e1460..289033332b 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -121,6 +121,9 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { } cmd := gitcmd.NewCommand().AddArguments("clone") + // Never follow HTTP redirects: no clone caller needs them, and a remote redirecting to an + // otherwise-blocked address would be an SSRF vector (e.g. migrating from an attacker URL). + cmd.AddArguments("-c", "http.followRedirects=false") if opts.SkipTLSVerify { cmd.AddArguments("-c", "http.sslVerify=false") } diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index 776c297a34..be0a21a83d 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -4,7 +4,10 @@ package git import ( + "net/http" + "net/http/httptest" "path/filepath" + "sync/atomic" "testing" "github.com/stretchr/testify/assert" @@ -19,3 +22,23 @@ func TestRepoIsEmpty(t *testing.T) { assert.NoError(t, err) assert.True(t, isEmpty) } + +// TestCloneRefusesRedirects ensures Clone never follows HTTP redirects, so a remote +// cannot redirect to an otherwise-blocked address (SSRF, e.g. during migration). +func TestCloneRefusesRedirects(t *testing.T) { + var targetHit atomic.Bool + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + targetHit.Store(true) + w.WriteHeader(http.StatusNotFound) + })) + defer target.Close() + + redirect := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, target.URL+r.URL.Path, http.StatusFound) + })) + defer redirect.Close() + + err := Clone(t.Context(), redirect.URL, filepath.Join(t.TempDir(), "dst"), CloneRepoOptions{}) + assert.Error(t, err) + assert.False(t, targetHit.Load(), "git must not follow the redirect to the target") +} diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go index e7a1cbbe4f..139b7ebc43 100644 --- a/routers/web/feed/branch.go +++ b/routers/web/feed/branch.go @@ -15,6 +15,9 @@ import ( // ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) { + if !checkRepoFeedTokenScope(ctx) { + return + } var commits []*git.Commit var err error if ctx.Repo.Commit != nil { diff --git a/routers/web/feed/file.go b/routers/web/feed/file.go index f115521928..fa590dfcf0 100644 --- a/routers/web/feed/file.go +++ b/routers/web/feed/file.go @@ -16,6 +16,9 @@ import ( // ShowFileFeed shows tags and/or releases on the repo as RSS / Atom feed func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string) { + if !checkRepoFeedTokenScope(ctx) { + return + } fileName := ctx.Repo.TreePath if len(fileName) == 0 { return diff --git a/routers/web/feed/release.go b/routers/web/feed/release.go index 6767c502ff..274eb0cd8a 100644 --- a/routers/web/feed/release.go +++ b/routers/web/feed/release.go @@ -15,6 +15,9 @@ import ( // shows tags and/or releases on the repo as RSS / Atom feed func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleasesOnly bool, formatType string) { + if !checkRepoFeedTokenScope(ctx) { + return + } releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{ IncludeTags: !isReleasesOnly, RepoID: ctx.Repo.Repository.ID, diff --git a/routers/web/feed/render.go b/routers/web/feed/render.go index 21ae03a595..d06aa22e64 100644 --- a/routers/web/feed/render.go +++ b/routers/web/feed/render.go @@ -4,9 +4,18 @@ package feed import ( + auth_model "gitea.dev/models/auth" "gitea.dev/services/context" ) +// checkRepoFeedTokenScope ensures an API token has repository read scope before a +// feed serves private repository content, mirroring checkDownloadTokenScope for +// downloads. Returns false (and writes the response) when the token is denied. +func checkRepoFeedTokenScope(ctx *context.Context) bool { + context.CheckRepoScopedToken(ctx, ctx.Repo.Repository, auth_model.Read) + return !ctx.Written() +} + // RenderBranchFeed render format for branch or file func RenderBranchFeed(ctx *context.Context, feedType string) { if ctx.Repo.TreePath == "" { diff --git a/routers/web/feed/repo.go b/routers/web/feed/repo.go index 8c8946eec1..2986e00bb6 100644 --- a/routers/web/feed/repo.go +++ b/routers/web/feed/repo.go @@ -16,6 +16,9 @@ import ( // ShowRepoFeed shows user activity on the repo as RSS / Atom feed func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType string) { + if !checkRepoFeedTokenScope(ctx) { + return + } actions, _, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{ RequestedRepo: repo, Actor: ctx.Doer, diff --git a/services/convert/notification.go b/services/convert/notification.go index 7ddd8ea5d1..fee1de4074 100644 --- a/services/convert/notification.go +++ b/services/convert/notification.go @@ -24,19 +24,22 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification) } // since user only get notifications when he has access to use minimal access mode - if n.Repository != nil { - perm, err := access_model.GetIndividualUserRepoPermission(ctx, n.Repository, n.User) - if err != nil { - log.Error("GetIndividualUserRepoPermission failed: %v", err) - return result - } - if perm.HasAnyUnitAccessOrPublicAccess() { // if user has been revoked access to repo, do not show repo info - result.Repository = ToRepo(ctx, n.Repository, perm) - // This permission is not correct and we should not be reporting it - for repository := result.Repository; repository != nil; repository = repository.Parent { - repository.Permissions = nil - } - } + if n.Repository == nil { + return result + } + perm, err := access_model.GetIndividualUserRepoPermission(ctx, n.Repository, n.User) + if err != nil { + log.Error("GetIndividualUserRepoPermission failed: %v", err) + return result + } + // if the user has been revoked access to the repo, do not leak repo or subject info + if !perm.HasAnyUnitAccessOrPublicAccess() { + return result + } + result.Repository = ToRepo(ctx, n.Repository, perm) + // This permission is not correct and we should not be reporting it + for repository := result.Repository; repository != nil; repository = repository.Parent { + repository.Permissions = nil } // handle Subject diff --git a/services/convert/notification_test.go b/services/convert/notification_test.go index 5718258672..277ee83ec2 100644 --- a/services/convert/notification_test.go +++ b/services/convert/notification_test.go @@ -39,6 +39,36 @@ func TestToNotificationThreadOmitsRepoWhenAccessRevoked(t *testing.T) { assert.Nil(t, thread.Repository) } +func TestToNotificationThreadOmitsSubjectWhenAccessRevoked(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + ctx := t.Context() + // repo 2 is private; user 4 has no access to it + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + assert.NoError(t, repo.LoadOwner(ctx)) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4, RepoID: repo.ID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + + n := &activities_model.Notification{ + ID: 12345, + UserID: user.ID, + RepoID: repo.ID, + Status: activities_model.NotificationStatusUnread, + Source: activities_model.NotificationSourceIssue, + IssueID: issue.ID, + UpdatedUnix: timeutil.TimeStampNow(), + Issue: issue, + Repository: repo, + User: user, + } + + thread := ToNotificationThread(ctx, n) + + // must not leak private issue metadata once access is revoked + assert.Nil(t, thread.Repository) + assert.Nil(t, thread.Subject) +} + func TestToNotificationThread(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) diff --git a/tests/integration/feed_repo_test.go b/tests/integration/feed_repo_test.go index 59189bf070..ffe908c74b 100644 --- a/tests/integration/feed_repo_test.go +++ b/tests/integration/feed_repo_test.go @@ -8,6 +8,7 @@ import ( "net/http" "testing" + auth_model "gitea.dev/models/auth" "gitea.dev/tests" "github.com/stretchr/testify/assert" @@ -33,3 +34,41 @@ func TestFeedRepo(t *testing.T) { assert.NotEmpty(t, rss.Channel.Items[0].PubDate) }) } + +// TestFeedRepoContentTokenScopes ensures repository feed endpoints enforce the +// repository token scope, so a PAT without repository scope cannot read private +// repository commit/activity data through RSS/Atom feeds. +func TestFeedRepoContentTokenScopes(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // user2/repo2 is a private repository owned by user2 + ownerReadToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) + miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) + + urls := []string{ + "/user2/repo2.rss", + "/user2/repo2.atom", + "/user2/repo2/rss/branch/master", + "/user2/repo2/atom/branch/master", + "/user2/repo2/rss/branch/master/README.md", + "/user2/repo2/tags.rss", + "/user2/repo2/tags.atom", + "/user2/repo2/releases.rss", + "/user2/repo2/releases.atom", + } + + for _, url := range urls { + t.Run(url, func(t *testing.T) { + // feed routes only accept basic auth, so authenticate as the advisory PoC does (user:token) + reqDenied := NewRequest(t, "GET", url) + reqDenied.SetBasicAuth("user2", miscToken) + // a token without repository scope must be denied + MakeRequest(t, reqDenied, http.StatusForbidden) + + reqAllowed := NewRequest(t, "GET", url) + reqAllowed.SetBasicAuth("user2", ownerReadToken) + // a token with repository read scope is allowed + MakeRequest(t, reqAllowed, http.StatusOK) + }) + } +} From c68925152b1b6c8f92806cdbda9c4672dcc1608f Mon Sep 17 00:00:00 2001 From: bircni Date: Wed, 17 Jun 2026 08:39:22 +0200 Subject: [PATCH 4/4] docs: add development setup guide (#37960) Moves the "Hacking on Gitea" page out of the documentation website and into the repository as `docs/development.md`, so contributors find build and test instructions next to the code. The content has been cleaned up and corrected for in-repo use. --------- Signed-off-by: bircni Co-authored-by: wxiaoguang Co-authored-by: Lunny Xiao --- CONTRIBUTING.md | 50 +++---- README.md | 53 ++++---- contrib/development/README.md | 4 +- docs/build-setup.md | 67 ++++++++++ docs/build-source.md | 92 +++++++++++++ docs/development.md | 141 ++++++++++++++++++++ docs/guideline-backend.md | 63 --------- docs/guideline-frontend.md | 17 --- docs/guidelines-backend.md | 132 +++++++++++++++++++ docs/guidelines-frontend.md | 98 ++++++++++++++ docs/guidelines-refactoring.md | 38 ++++++ docs/testing.md | 151 ++++++++++++++++++++++ tests/integration/README.md | 94 -------------- tests/integration/api_org_avatar_test.go | 8 +- tests/integration/api_repo_avatar_test.go | 8 +- tests/integration/api_user_avatar_test.go | 8 +- 16 files changed, 762 insertions(+), 262 deletions(-) create mode 100644 docs/build-setup.md create mode 100644 docs/build-source.md create mode 100644 docs/development.md delete mode 100644 docs/guideline-backend.md delete mode 100644 docs/guideline-frontend.md create mode 100644 docs/guidelines-backend.md create mode 100644 docs/guidelines-frontend.md create mode 100644 docs/guidelines-refactoring.md create mode 100644 docs/testing.md delete mode 100644 tests/integration/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6888b4ad65..71087d3446 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,12 +2,17 @@ This document explains how to contribute changes to the Gitea project. Topic-specific guides live in separate files so the essentials are easier to find. -| Topic | Document | -| :---- | :------- | -| Backend (Go modules, API v1) | [docs/guideline-backend.md](docs/guideline-backend.md) | -| Frontend (npm, UI guidelines) | [docs/guideline-frontend.md](docs/guideline-frontend.md) | -| Maintainers, TOC, labels, merge queue, commit format for mergers | [docs/community-governance.md](docs/community-governance.md) | -| Release cycle, backports, tagging releases | [docs/release-management.md](docs/release-management.md) | +| Topic | Document | +|:-----------------------|:-----------------------------------------------------------------| +| Setup and requirements | [docs/build-setup.md](docs/build-setup.md) | +| Development workflow | [docs/development.md](docs/development.md) | +| Build from source | [docs/build-source.md](docs/build-source.md) | +| Running the tests | [docs/testing.md](docs/testing.md) | +| Frontend guidelines | [docs/guidelines-frontend.md](docs/guidelines-frontend.md) | +| Backend guidelines | [docs/guidelines-backend.md](docs/guidelines-backend.md) | +| Refactoring | [docs/guidelines-refactoring.md](docs/guidelines-refactoring.md) | +| Community Governance | [docs/community-governance.md](docs/community-governance.md) | +| Release management | [docs/release-management.md](docs/release-management.md) |
Table of Contents @@ -43,7 +48,7 @@ This document explains how to contribute changes to the Gitea project. Topic-spe It assumes you have followed the [installation instructions](https://docs.gitea.com/category/installation). \ Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io). -For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/). +For configuring IDEs for Gitea development, see the [IDE setup notes](docs/development.md#ide-configuration) and the [contributed configurations](contrib/development/). ## AI Contribution Policy @@ -106,7 +111,8 @@ If further discussion is needed, we encourage you to open a new issue instead an ## Building Gitea -See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea). +See [docs/setup.md](docs/setup.md) for prerequisites and [docs/development.md](docs/development.md) +for building Gitea and the development workflow. ## Styleguide @@ -125,33 +131,7 @@ Afterwards, copyright should only be modified when the copyright author changes. ## Testing -Before submitting a pull request, run all tests to make sure your changes don't cause a regression elsewhere. - -Here's how to run the test suite: - -- code lint - -| | | -| :-------------------- | :--------------------------------------------------------------------------- | -|``make lint`` | lint everything (not needed if you only change the front- **or** backend) | -|``make lint-frontend`` | lint frontend files | -|``make lint-backend`` | lint backend files | - -- run tests (we suggest running them on Linux) - -| Command | Action | | -|:----------------------------------------------|:-----------------------------------------------------| ------------------------------------------- | -| ``make test-backend[\#SpecificTestName]`` | run unit test(s) | | -| ``make test-integration[\#SpecificTestName]`` | run [integration](tests/integration) test(s) | [More details](tests/integration/README.md) | -| ``make test-e2e`` | run [end-to-end](tests/e2e) test(s) using Playwright | | - -- E2E test environment variables - -| Variable | Description | -| :-------------------------------- | :---------------------------------------------------------- | -| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output | -| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` | -| ``GITEA_TEST_E2E_TIMEOUT_FACTOR`` | Timeout multiplier (default: 4 on CI, 1 locally) | +Before submitting a pull request, run the linters (`make lint`, or the scoped `make lint-backend` / `make lint-frontend`) and the tests to make sure your changes don't cause a regression elsewhere. See [docs/testing.md](docs/testing.md) for how to run the unit, integration, end-to-end, and migration tests. ## Translation diff --git a/README.md b/README.md index 3aa5138dac..521f2baf24 100644 --- a/README.md +++ b/README.md @@ -14,21 +14,21 @@ ## Purpose -The goal of this project is to make the easiest, fastest, and most -painless way of setting up a self-hosted Git service. +The goal of Gitea is to make the easiest, fastest, and most painless way of +setting up a self-hosted all-in-one software development service, +including Git hosting, code management, code review, issue tracking, project kanban, wiki, +team collaboration, package registry and CI/CD which can reuse GitHub Actions. As Gitea is written in Go, it works across **all** the platforms and -architectures that are supported by Go, including Linux, macOS, and -Windows on x86, amd64, ARM and PowerPC architectures. -This project has been -[forked](https://blog.gitea.com/welcome-to-gitea/) from -[Gogs](https://gogs.io) since November of 2016, but a lot has changed. +architectures that are supported by Go, including Linux, macOS, FreeBSD/OpenBSD and Windows +on x86, amd64, ARM, RISC-V 64 and PowerPC architectures. For online demonstrations, you can visit [demo.gitea.com](https://demo.gitea.com). For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login). -To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com). +To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com), +or use container (docker/podman/etc) to deploy on your own server with the [official image](https://hub.docker.com/r/gitea/gitea). ## Documentation @@ -40,27 +40,12 @@ If you have any suggestions or would like to contribute to it, you can visit the ## Building -From the root of the source tree, run: +See [docs/build-setup.md](docs/build-setup.md) for prerequisites +and [docs/development.md](docs/development.md) for setting up a local development environment, linting, and testing. - TAGS="bindata" make build +If you'd like to build from source or make a distribution package, see [docs/build-source.md](docs/build-source.md) for more information. -The `build` target is split into two sub-targets: - -- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod). -- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and [pnpm](https://pnpm.io/installation). - -Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js. - -More info: https://docs.gitea.com/installation/install-from-source - -## Using - -After building, a binary file named `gitea` will be generated in the root of the source tree by default. To run it, use: - - ./gitea web - -> [!NOTE] -> If you're interested in using our APIs, we have experimental support with [documentation](https://docs.gitea.com/api). +After building, you can run `./gitea web` to start the server, or `./gitea help` to see all available commands. ## Contributing @@ -69,7 +54,8 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request > [!NOTE] > > 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.** -> 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks! +> 2. New to the codebase? The [development guide](docs/development.md) walks through setting up a local environment and building from source. +> 3. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks! ## Translating @@ -126,14 +112,19 @@ Support this project by becoming a sponsor. Your logo will show up here with a l Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g. -**Why is this not hosted on a Gitea instance?** +**How do I configure Gitea?** -We're [working on it](https://github.com/go-gitea/gitea/issues/1029). +For dynamic config options, you can change it on your admin panel's configuration section. + +For static config options, you can edit your `app.ini` file and resart the instance. +See [app.example.ini](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) or [configuration documentation](https://docs.gitea.com/administration/config-cheat-sheet) for more details. **Where can I find the security patches?** In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches. +(more FAQs are listed in [FAQ documentation](https://docs.gitea.com/help/faq)) + ## License This project is licensed under the MIT License. @@ -143,7 +134,7 @@ for the full license text. ## Further information
-Looking for an overview of the interface? Check it out! +Looking for an overview of the interface? Check it out the screenshots! ### Login/Register Page diff --git a/contrib/development/README.md b/contrib/development/README.md index a9abfa3ef1..5fc5dd39f0 100644 --- a/contrib/development/README.md +++ b/contrib/development/README.md @@ -1,12 +1,14 @@ # IDE and code editor configuration ## Table of Contents + - [IDE and code editor configuration](#ide-and-code-editor-configuration) - [Microsoft Visual Studio Code](#microsoft-visual-studio-code) ## Microsoft Visual Studio Code + Download Microsoft Visual Studio Code at https://code.visualstudio.com/ and follow instructions at https://code.visualstudio.com/docs/languages/go to setup Go extension for it. -Create new directory `.vscode` in Gitea root folder and copy contents of folder [contrib/ide/vscode](vscode/) to it. You can now use `Ctrl`+`Shift`+`B` to build gitea executable and `F5` to run it in debug mode. +Create new directory `.vscode` in Gitea root folder and copy contents of folder [contrib/development/vscode](vscode/) to it. You can now use `Ctrl`+`Shift`+`B` to build gitea executable and `F5` to run it in debug mode. Supported on Debian, Ubuntu, Red Hat, Fedora, SUSE Linux, MacOS and Microsoft Windows. diff --git a/docs/build-setup.md b/docs/build-setup.md new file mode 100644 index 0000000000..d1cd465a60 --- /dev/null +++ b/docs/build-setup.md @@ -0,0 +1,67 @@ +# Setup and requirements + +This document lists the tools you need to build Gitea from source and how to get +the code. Once your environment is ready, see [development.md](development.md) for +the build and development workflow, and [testing.md](testing.md) for running tests. + +For the contribution workflow and review process, see [CONTRIBUTING.md](../CONTRIBUTING.md). + +## Requirements + +### Go + +[Install Go](https://go.dev/doc/install) and set up your Go environment. The +required version is the one declared in [`go.mod`](../go.mod); installing the same +version your continuous integration uses avoids `gofmt` differences between Go +releases. + +> [!NOTE] +> Some `make` tasks build external Go tools on demand (for example `make +> watch-backend`). To use them, the `"$GOPATH"/bin` directory must be on your +> executable `PATH`; otherwise you have to manage those tools yourself. + +### Node.js and pnpm + +[Install Node.js](https://nodejs.org/en/download/) to build the JavaScript and CSS +files. The minimum supported version is the one declared in +[`package.json`](../package.json) (`engines.node`); the latest LTS is recommended. + +Gitea manages frontend dependencies with [pnpm](https://pnpm.io/). The `make` +targets invoke it for you, so installing pnpm manually is only needed if you want +to run `pnpm` commands directly. + +### Make + +Gitea uses [Make](https://www.gnu.org/software/make/) to drive builds, linting, and +tests. On Windows it can be installed via [MSYS2](https://www.msys2.org/) or +[Chocolatey](https://chocolatey.org/packages/make). + +### Python with uv (optional) + +Linting the templates, workflow files, and YAML requires Python tooling that Gitea +runs through [uv](https://docs.astral.sh/uv/). After installing uv, `make` creates +the environment automatically (`uv sync`); you only need this if you run +`make lint-templates`, `make lint-yaml`, or `make lint-actions` locally. + +### Git LFS + +The integration tests require [Git LFS](https://git-lfs.com/) to be installed. + +## Getting the source code + +Clone the repository: + +```bash +git clone https://github.com/go-gitea/gitea +``` + +To contribute changes, [fork the repository](https://github.com/go-gitea/gitea) on +GitHub and add your fork as a git remote so you can push branches and open pull +requests. See GitHub's [working with forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks) +documentation for the details. + +## Installing dependencies + +Most build and test targets install the dependencies they need on their own. To +fetch everything up front, run `make deps` (or the per-group `make deps-frontend`, +`make deps-backend`, `make deps-tools`, `make deps-py`). diff --git a/docs/build-source.md b/docs/build-source.md new file mode 100644 index 0000000000..17e2ef7ae0 --- /dev/null +++ b/docs/build-source.md @@ -0,0 +1,92 @@ +# Prepare build environment + +Complete the steps in [build-setup.md](build-setup.md) to prepare your environment for building Gitea from source. + +## Choose a branch + +By default, the cloned repository is on main branch (the current development branch for next major release, aka: main nightly). + +You can switch to a versioned branch (the branch for the next minor stable release, aka: stable nightly ) +or a versioned tag (matches the official releases with version numbers) + +To test a Pull Request, you can fetch its code by its Pull Request number (take `PR #123456` as example): + +```bash +git fetch origin pull/123456/head:pr-123456 +``` + +# Build + +Various [make tasks](https://github.com/go-gitea/gitea/blob/main/Makefile) +are provided to keep the build process as simple as possible. + +Depending on requirements, the following build tags can be included. + +- `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build. +- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules). + Can be used to authenticate local users or extend authentication to methods available to PAM. +- `gogit`: (EXPERIMENTAL) Use go-git variants of Git commands. + +To include all assets, use the `bindata` tag: + +```bash +TAGS="bindata" make build +``` + +Tag `gogit` is used to try to resolve some Windows-specific performance problems, POSIX systems don't need it. +You can build a Windows binary by: + +```bash +GOOS=windows TAGS="bindata gogit" make build +``` + +## Changing default paths + +Gitea will search for a number of things from the _`CustomPath`_. +By default, this is the `custom/` directory in the current working directory when running Gitea. +It will also look for its configuration file _`CustomConf`_ in `$(CustomPath)/conf/app.ini`, +and will use the current working directory as the relative base path _`AppWorkPath`_. + +These values, although useful when developing, may conflict with downstream users preferences. + +For packagers who need to use paths like `/etc/gitea/app.ini`, +they should define these values at build time for `make build` by environment variable like +`LDFLAGS='-X "module.Var1=Value1" -X "module.Var2=Value2"' TAGS="bindata" make build`. + +- _`CustomConf`_: `-X "code.gitea.io/gitea/modules/setting.CustomConf=/etc/gitea/app.ini"` +- _`AppWorkPath`_: `-X "code.gitea.io/gitea/modules/setting.AppWorkPath=/var/lib/gitea"` +- _`CustomPath`_: `-X "code.gitea.io/gitea/modules/setting.CustomPath=/var/lib/gitea/custom"` +- Default PID file location: `-X "code.gitea.io/gitea/cmd.PIDFile=/run/gitea.pid"` + +Add as many of the strings with their preceding `-X` to the `LDFLAGS` variable and run `make build` +with the appropriate `TAGS` as above. + +Running `gitea help` will allow you to review what the computed settings will be for your `gitea`. + +## Cross Build + +Gitea use's Golang's toolchain variables for cross-building. + +For example, to cross build for Linux ARM64: + +``` +GOOS=linux GOARCH=arm64 TAGS="bindata" make build +``` + +### Adding shell autocompletion + +Shell completion can be generated directly from binary with: +```sh +gitea completion +``` + +Supported values for `` are `bash`, `fish`, `pwsh` and `zsh`. +Details on how to load the completion for your shell can be found in the completion command help. + +## Source Maps + +By default, gitea generates reduced source maps for frontend files to conserve space. This can be controlled with the `ENABLE_SOURCEMAP` environment variable: + +- `ENABLE_SOURCEMAP=true` generates all source maps, the default for development builds +- `ENABLE_SOURCEMAP=reduced` generates limited source maps, the default for production builds +- `ENABLE_SOURCEMAP=false` generates no source maps diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000000..e0858a716f --- /dev/null +++ b/docs/development.md @@ -0,0 +1,141 @@ +# Development + +This document describes how to build Gitea from source and the day-to-day +development workflow. For prerequisites and how to obtain the code, see +[build-setup.md](build-setup.md). For running tests, see [testing.md](testing.md). For the +contribution workflow and review process, see [CONTRIBUTING.md](../CONTRIBUTING.md). + +Area-specific guidelines: + +- [Backend development guidelines](guidelines-backend.md) +- [Frontend development guidelines](guidelines-frontend.md) +- [Refactoring guidelines](guidelines-refactoring.md) + +## Building + +To build Gitea for development, run: + +```bash +make build +``` + +No build tags are required: SQLite support is compiled in by default, which is +enough for local development. The `build` target runs two sub-targets, `frontend` +and `backend`. The `bindata` tag embeds the frontend assets into the binary and is +only needed when packaging a self-contained build, so leave it out during +development. + +See `make help` for all available targets, and the workflows in +[`.github/workflows`](https://github.com/go-gitea/gitea/tree/main/.github/workflows) +to see how continuous integration builds and checks Gitea. + +## Building continuously + +To rebuild automatically when source files change: + +```bash +# watch both frontend and backend +make watch + +# or watch only the frontend (starts the Vite dev server) +make watch-frontend + +# or watch only the backend (Go) +make watch-backend +``` + +Watching all backend source files may hit the default open-files limit on macOS or +Linux; raise it with `ulimit -n 12288` for the current shell, or in your shell +startup file to make it permanent. + +## Formatting, linting and checks + +Continuous integration rejects pull requests that fail formatting, linting, or +consistency checks. Format your code first: + +```bash +make fmt +``` + +Then lint: + +```bash +# lint everything +make lint +# or only one side +make lint-backend +make lint-frontend +``` + +Many linters can fix issues automatically with `make lint-fix` (or the scoped +`make lint-backend-fix` / `make lint-frontend-fix`). The combined consistency +checks that CI runs are available as `make checks`. + +## Building and adding SVGs + +SVG icons are built with `make svg`, which compiles the icon sources into +`public/assets/img/svg`. Custom icons can be added under `web_src/svg`. + +## Updating the API + +When you create or change API routes, you **must** update the +[Swagger](https://swagger.io/docs/specification/2-0/what-is-swagger/) documentation +using [go-swagger](https://goswagger.io/) comments. See the +[backend development guidelines](guidelines-backend.md) for how API routes, +request/response structs, and swagger definitions fit together. + +Regenerate and validate the spec after changing an endpoint, then commit the +updated JSON: + +```bash +make generate-swagger +make swagger-validate +``` + +CI verifies the committed spec is up to date with: + +```bash +make swagger-check +``` + +## Creating new configuration options + +When adding configuration options it is not enough to add them to the +`modules/setting` files. Also update +[`custom/conf/app.example.ini`](../custom/conf/app.example.ini), and document them in +the [configuration cheat sheet](https://docs.gitea.com/administration/config-cheat-sheet), +which lives in the [documentation repository](https://gitea.com/gitea/docs). + +## Database migrations + +If you make breaking changes to a database-persisted struct under `models/`, add a +new migration in `models/migrations/`. See [testing.md](testing.md#migration-tests) +for running the migration tests. + +## Testing + +For unit, integration, end-to-end, and migration tests, see [testing.md](testing.md). + +## IDE configuration + +### Visual Studio Code + +A `launch.json` and `tasks.json` are provided in +[`contrib/development/vscode`](../contrib/development/vscode). See +[`contrib/development/README.md`](../contrib/development/README.md) for details. + +### GoLand + +Clicking the `Run Application` arrow on `func main()` in `/main.go` starts a +debuggable Gitea instance. + +The `Output Directory` in `Run/Debug Configuration` **must** be set to the Gitea +project directory (the one containing `main.go` and `go.mod`). Otherwise the working +directory is a GoLand temporary directory, which prevents Gitea from loading dynamic +resources (such as templates) in development. + +## Submitting your changes + +Push your branch and open a pull request. See [CONTRIBUTING.md](../CONTRIBUTING.md) +for the review process and PR requirements. For help, join the `#Develop` channel on +[Discord](https://discord.gg/gitea). diff --git a/docs/guideline-backend.md b/docs/guideline-backend.md deleted file mode 100644 index 29fa92288c..0000000000 --- a/docs/guideline-backend.md +++ /dev/null @@ -1,63 +0,0 @@ -# Backend development - -This document covers backend-specific contribution expectations. For general contribution workflow, see [CONTRIBUTING.md](../CONTRIBUTING.md). - -For coding style and architecture, see also the [backend development guideline](https://docs.gitea.com/contributing/guidelines-backend) on the documentation site. - -## Dependencies - -Go dependencies are managed using [Go Modules](https://go.dev/cmd/go/#hdr-Module_maintenance). \ -You can find more details in the [go mod documentation](https://go.dev/ref/mod) and the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules). - -Pull requests should only modify `go.mod` and `go.sum` where it is related to your change, be it a bugfix or a new feature. \ -Apart from that, these files should only be modified by Pull Requests whose only purpose is to update dependencies. - -The `go.mod`, `go.sum` update needs to be justified as part of the PR description, -and must be verified by the reviewers and/or merger to always reference -an existing upstream commit. - -## API v1 - -The API is documented by [swagger](https://gitea.com/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest). - -### GitHub API compatibility - -Gitea's API should use the same endpoints and fields as the GitHub API as far as possible, unless there are good reasons to deviate. \ -If Gitea provides functionality that GitHub does not, a new endpoint can be created. \ -If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields. \ -Updating an existing API should not remove existing fields unless there is a really good reason to do so. \ -The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to API v2 (which is currently not planned). - -### Adding/Maintaining API routes - -All expected results (errors, success, fail messages) must be documented ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)). \ -All JSON input types must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91)) \ -and referenced in [routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go). \ -They can then be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318). \ -All JSON responses must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L36-L68)) \ -and referenced in its category in [routers/api/v1/swagger/](routers/api/v1/swagger/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16)) \ -They can be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L277-L279). - -### When to use what HTTP method - -In general, HTTP methods are chosen as follows: - -- **GET** endpoints return the requested object(s) and status **OK (200)** -- **DELETE** endpoints return the status **No Content (204)** and no content either -- **POST** endpoints are used to **create** new objects (e.g. a User) and return the status **Created (201)** and the created object -- **PUT** endpoints are used to **add/assign** existing Objects (e.g. a user to a team) and return the status **No Content (204)** and no content either -- **PATCH** endpoints are used to **edit/change** an existing object and return the changed object and the status **OK (200)** - -### Requirements for API routes - -All parameters of endpoints changing/editing an object must be optional (except the ones to identify the object, which are required). - -Endpoints returning lists must - -- support pagination (`page` & `limit` options in query) -- set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444)) - -### Knowledge - -- Partially database table migration must use `SyncWithOptions(IgnoreDrop...)` -- Template variables with "camelCase" or "snake_case" are used for restoring the form values from a submitted form diff --git a/docs/guideline-frontend.md b/docs/guideline-frontend.md deleted file mode 100644 index 80ebe82177..0000000000 --- a/docs/guideline-frontend.md +++ /dev/null @@ -1,17 +0,0 @@ -# Frontend development - -This document covers frontend-specific contribution expectations. For general contribution workflow, see [CONTRIBUTING.md](../CONTRIBUTING.md). - -## Dependencies - -For the frontend, we use [npm](https://www.npmjs.com/). - -The same restrictions apply for frontend dependencies as for [backend dependencies](guideline-backend.md#dependencies), with the exceptions that the files for it are `package.json` and `package-lock.json`, and that new versions must always reference an existing version. - -## Design guideline - -Depending on your change, please read the - -- [backend development guideline](https://docs.gitea.com/contributing/guidelines-backend) -- [frontend development guideline](https://docs.gitea.com/contributing/guidelines-frontend) -- [refactoring guideline](https://docs.gitea.com/contributing/guidelines-refactoring) diff --git a/docs/guidelines-backend.md b/docs/guidelines-backend.md new file mode 100644 index 0000000000..5342c1d971 --- /dev/null +++ b/docs/guidelines-backend.md @@ -0,0 +1,132 @@ +# Backend development guidelines + +This document covers backend-specific architecture and contribution expectations. +For the general workflow see [CONTRIBUTING.md](../CONTRIBUTING.md), and for building +and testing see [development.md](development.md) and [testing.md](testing.md). + +## Background + +The backend is written in Go. Web routing is handled by +[chi](https://github.com/go-chi/chi) and database access goes through the +[XORM](https://xorm.io/) ORM. Understanding how the packages depend on each other is +essential before contributing backend code. + +## Package design + +### Package layout + +The backend is split into top-level packages, each with a focused responsibility: + +- `build`: helper scripts used at compile time +- `cmd`: subcommands such as `web`, `serv`, `hooks`, `doctor`, and admin utilities +- `models`: data structures and database operations (XORM); keeps external + dependencies to a minimum + - `models/db`: core database operations + - `models/fixtures`: sample data used by tests + - `models/migrations`: schema migration scripts +- `modules`: standalone functionality with few dependencies + - `modules/setting`: configuration handling + - `modules/git`: interaction with the Git command line +- `routers`: request handlers, split into `api`, `web`, `install`, and `private` +- `services`: business logic that ties routers and models together +- `templates`: Go HTML templates +- `public`: compiled frontend assets +- `tests`: integration and end-to-end test helpers + +### Dependency direction + +Dependencies only flow in one direction: + +```text +cmd → routers → services → models → modules +``` + +A package on the left may import a package on its right, but never the reverse. + +### Naming conventions + +- Top-level packages use the plural form: `services`, `models`, `routers`. +- Subpackages use the singular form: `services/user`, `models/repository`. + +When packages from different layers share a name, use a snake_case import alias to +disambiguate: + +```go +import user_service "gitea.dev/services/user" +``` + +### Database transactions + +Operations that must roll back together should run inside `db.WithTx()` (or +`db.WithTx2()` when a value must be returned), defined in `models/db/context.go`. +Functions that participate in a transaction take a `context.Context` as their first +parameter so the transaction can be propagated. + +### XORM gotchas + +- Never call `x.Update(exemplar)` without an explicit `WHERE` clause — it updates + every row in the table. +- Partial table migrations must use `SyncWithOptions(IgnoreDrop...)` rather than a + plain `Sync`. +- When inserting rows with preset IDs, MSSQL requires `SET IDENTITY_INSERT` to be + enabled and PostgreSQL requires the sequence to be updated afterwards. + +## Dependencies + +Go dependencies are managed with [Go Modules](https://go.dev/ref/mod). + +Pull requests should only modify `go.mod` and `go.sum` where it relates to the +change at hand, be it a bug fix or a new feature. Otherwise, these files should only +be touched by pull requests whose sole purpose is updating dependencies. Run +`make tidy` after any change to `go.mod`. + +Any `go.mod` / `go.sum` update must be justified in the PR description and must be +verified by reviewers and the merger to reference an existing upstream commit. + +## API v1 + +The API is documented with [Swagger](https://gitea.com/api/swagger) and is modelled +on [the GitHub API](https://docs.github.com/en/rest). + +### GitHub API compatibility + +Gitea's API should use the same endpoints and fields as the GitHub API where +possible, unless there is a good reason to deviate. + +- If Gitea offers functionality GitHub does not, a new endpoint may be added. +- If Gitea exposes information the GitHub API does not, a new field may be added as + long as it does not collide with a GitHub field. +- Existing fields should not be removed unless there is a strong reason; the same + applies to status responses. + +If you notice a problem that would require a breaking change, leave a comment in the +code for a future refactor to API v2 (which is currently not planned) rather than +breaking v1. + +### Adding and maintaining API routes + +- All possible results (errors, success, and failure messages) must be documented in + the swagger comments on the route. +- Every JSON request body must be defined as a struct in `modules/structs/` and + registered in [`routers/api/v1/swagger/options.go`](../routers/api/v1/swagger/options.go). +- Every JSON response must be defined as a struct in `modules/structs/` and + registered with its category under [`routers/api/v1/swagger/`](../routers/api/v1/swagger). + +### HTTP methods and status codes + +In general, choose HTTP methods as follows: + +- **GET** returns the requested object(s) with status **200 OK**. +- **POST** creates a new object (e.g. a user) and returns **201 Created** with the + created object. +- **PUT** adds or assigns an existing object (e.g. a user to a team) and returns + **204 No Content** with no body. +- **PATCH** edits an existing object and returns the changed object with **200 OK**. +- **DELETE** removes an object and returns **204 No Content** with no body. + +### Requirements for API routes + +- All parameters of endpoints that edit an object must be optional, except those + needed to identify the object, which are required. +- Endpoints returning lists must support pagination (`page` and `limit` query + options) and set the `X-Total-Count` header via `ctx.SetTotalCountHeader(...)`. diff --git a/docs/guidelines-frontend.md b/docs/guidelines-frontend.md new file mode 100644 index 0000000000..61a801ba38 --- /dev/null +++ b/docs/guidelines-frontend.md @@ -0,0 +1,98 @@ +# Frontend development guidelines + +This document covers frontend-specific architecture and contribution expectations. +For the general workflow see [CONTRIBUTING.md](../CONTRIBUTING.md), and for building +and testing see [development.md](development.md) and [testing.md](testing.md). + +## Background + +The frontend uses [Vue 3](https://vuejs.org/), [Fomantic-UI](https://fomantic-ui.com/) (built on jQuery) +and [Tailwind CSS](https://tailwindcss.com/). Pages are rendered with Go HTML templates. +Source files live in: + +- `web_src/css/`: CSS styles +- `web_src/js/`: JavaScript and TypeScript +- `web_src/js/components/`: Vue components +- `web_src/js/features/`: feature modules wired up at page load +- `templates/`: Go HTML templates + +## Dependencies + +Frontend dependencies are managed with [pnpm](https://pnpm.io/). The same rules as +for [backend dependencies](guidelines-backend.md#dependencies) apply, except the +relevant files are `package.json` and `pnpm-lock.yaml`, and new versions must always +reference an existing published version. + +## Framework usage + +Mixing frameworks arbitrarily makes code hard to maintain. Recommended combinations: + +- Vue3 +- Vanilla JavaScript +- Fomantic-UI (jQuery), deprecated, we vendored a specific version with a lot of changes. + +Avoid combinations such as Vue with Fomantic-UI. +Vue components may reuse Fomantic-UI CSS classes for visual consistency. +Use Go templates for simple or SEO-relevant pages and Vue for complex, interactive pages. +Gitea uses Vue 3 **without** JSX to keep HTML and JavaScript separate. + +> [!NOTE] +> Fomantic-UI is not an accessibility-friendly framework. Gitea patches some ARIA +> behavior, but accessibility work is ongoing — prefer semantic HTML and test +> keyboard/screen-reader behavior where you can. + +## Gitea-specific conventions + +- Keep features in their own files or directories. +- Use kebab-case for HTML `id`s and classes, ideally with 2-3 feature keywords. +- Prefix classes to avoid short-name conflicts between different frameworks. +- Create a new class name when overriding framework styles instead of editing the framework's own classes, + or fix the framework's source to fix all cases. +- Prefer semantic elements such as `