From 184e068f376ce8c5f5bfe74ec17f3188d8ba9189 Mon Sep 17 00:00:00 2001 From: Danko Aleksejevs Date: Thu, 26 Jun 2025 20:06:21 +0200 Subject: [PATCH 01/30] feat: show more relevant results for 'dependencies' dropdown (#8003) - Fix issue dropdown breaking when currently selected issue is included in results. - Add `sort` parameter to `/issues/search` API. - Sort dropdown by relevance. - Make priority_repo_id work again. - Added E2E test. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8003 Reviewed-by: Shiny Nematoda Reviewed-by: Gusted Co-authored-by: Danko Aleksejevs Co-committed-by: Danko Aleksejevs --- models/fixtures/repository.yml | 2 +- models/issues/issue_search.go | 17 ++-- models/issues/pull_list.go | 2 +- modules/indexer/issues/bleve/bleve.go | 11 ++- modules/indexer/issues/db/db.go | 3 + modules/indexer/issues/db/options.go | 5 ++ .../issues/elasticsearch/elasticsearch.go | 6 +- modules/indexer/issues/internal/model.go | 5 +- .../indexer/issues/internal/tests/tests.go | 19 ++++ routers/api/v1/repo/issue.go | 15 +++- routers/web/repo/issue.go | 11 +-- templates/swagger/v1_json.tmpl | 18 ++++ tests/e2e/declare_repos_test.go | 81 ++++++++++++++--- tests/e2e/issue-sidebar.test.e2e.ts | 88 +++++++++++++++++++ tests/e2e/utils_e2e_test.go | 1 + tests/test_utils.go | 7 ++ web_src/js/features/repo-issue.js | 19 ++-- 17 files changed, 269 insertions(+), 41 deletions(-) diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index c383fa43ac..2f104eed65 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -32,7 +32,7 @@ created_unix: 1731254961 updated_unix: 1731254961 topics: '[]' - + - id: 2 owner_id: 2 diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 91a69c26a7..529f0c15d4 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -48,7 +48,9 @@ type IssuesOptions struct { //nolint UpdatedBeforeUnix int64 // prioritize issues from this repo PriorityRepoID int64 - IsArchived optional.Option[bool] + // if this issue index (not ID) exists and matches the filters, *and* priorityrepo sort is used, show it first + PriorityIssueIndex int64 + IsArchived optional.Option[bool] // If combined with AllPublic, then private as well as public issues // that matches the criteria will be returned, if AllPublic is false @@ -60,7 +62,7 @@ type IssuesOptions struct { //nolint // applySorts sort an issues-related session based on the provided // sortType string -func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { +func applySorts(sess *xorm.Session, sortType string, priorityRepoID, priorityIssueIndex int64) { switch sortType { case "oldest": sess.Asc("issue.created_unix").Asc("issue.id") @@ -97,8 +99,11 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { case "priorityrepo": sess.OrderBy("CASE "+ "WHEN issue.repo_id = ? THEN 1 "+ - "ELSE 2 END ASC", priorityRepoID). - Desc("issue.created_unix"). + "ELSE 2 END ASC", priorityRepoID) + if priorityIssueIndex != 0 { + sess.OrderBy("issue.index = ? DESC", priorityIssueIndex) + } + sess.Desc("issue.created_unix"). Desc("issue.id") case "project-column-sorting": sess.Asc("project_issue.sorting").Desc("issue.created_unix").Desc("issue.id") @@ -470,7 +475,7 @@ func Issues(ctx context.Context, opts *IssuesOptions) (IssueList, error) { Join("INNER", "repository", "`issue`.repo_id = `repository`.id") applyLimit(sess, opts) applyConditions(sess, opts) - applySorts(sess, opts.SortType, opts.PriorityRepoID) + applySorts(sess, opts.SortType, opts.PriorityRepoID, opts.PriorityIssueIndex) issues := IssueList{} if err := sess.Find(&issues); err != nil { @@ -494,7 +499,7 @@ func IssueIDs(ctx context.Context, opts *IssuesOptions, otherConds ...builder.Co } applyLimit(sess, opts) - applySorts(sess, opts.SortType, opts.PriorityRepoID) + applySorts(sess, opts.SortType, opts.PriorityRepoID, opts.PriorityIssueIndex) var res []int64 total, err := sess.Select("`issue`.id").Table(&Issue{}).FindAndCount(&res) diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index 8fc0491026..ddb813cf44 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -149,7 +149,7 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio } findSession := listPullRequestStatement(ctx, baseRepoID, opts) - applySorts(findSession, opts.SortType, 0) + applySorts(findSession, opts.SortType, 0, 0) findSession = db.SetSessionPagination(findSession, opts) prs := make([]*PullRequest, 0, opts.PageSize) found := findSession.Find(&prs) diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index 573d63a446..8549ba8dfc 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -170,7 +170,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if issueID, err := token.ParseIssueReference(); err == nil { idQuery := inner_bleve.NumericEqualityQuery(issueID, "index") - idQuery.SetBoost(5.0) + idQuery.SetBoost(20.0) innerQ.AddQuery(idQuery) } @@ -197,6 +197,15 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( queries = append(queries, bleve.NewDisjunctionQuery(repoQueries...)) } + if options.PriorityRepoID.Has() { + eq := inner_bleve.NumericEqualityQuery(options.PriorityRepoID.Value(), "repo_id") + eq.SetBoost(10.0) + meh := bleve.NewMatchAllQuery() + meh.SetBoost(0) + should := bleve.NewDisjunctionQuery(eq, meh) + queries = append(queries, should) + } + if options.IsPull.Has() { queries = append(queries, inner_bleve.BoolFieldQuery(options.IsPull.Value(), "is_pull")) } diff --git a/modules/indexer/issues/db/db.go b/modules/indexer/issues/db/db.go index 397daa3265..5f42bce9a1 100644 --- a/modules/indexer/issues/db/db.go +++ b/modules/indexer/issues/db/db.go @@ -53,6 +53,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( cond := builder.NewCond() + var priorityIssueIndex int64 if options.Keyword != "" { repoCond := builder.In("repo_id", options.RepoIDs) if len(options.RepoIDs) == 1 { @@ -82,6 +83,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( builder.Eq{"`index`": issueID}, cond, ) + priorityIssueIndex = issueID } } @@ -89,6 +91,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if err != nil { return nil, err } + opt.PriorityIssueIndex = priorityIssueIndex // If pagesize == 0, return total count only. It's a special case for search count. if options.Paginator != nil && options.Paginator.PageSize == 0 { diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 4411cc1c37..55a471fc8e 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -78,6 +78,11 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m User: nil, } + if options.PriorityRepoID.Has() { + opts.SortType = "priorityrepo" + opts.PriorityRepoID = options.PriorityRepoID.Value() + } + if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 { opts.MilestoneIDs = []int64{db.NoConditionID} } else { diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 9d2786e101..d632a22b2a 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -165,7 +165,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( } var eitherQ elastic.Query = innerQ if issueID, err := token.ParseIssueReference(); err == nil { - indexQ := elastic.NewTermQuery("index", issueID).Boost(15.0) + indexQ := elastic.NewTermQuery("index", issueID).Boost(20) eitherQ = elastic.NewDisMaxQuery().Query(indexQ).Query(innerQ).TieBreaker(0.5) } switch token.Kind { @@ -188,6 +188,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( } query.Must(q) } + if options.PriorityRepoID.Has() { + q := elastic.NewTermQuery("repo_id", options.PriorityRepoID.Value()).Boost(10) + query.Should(q) + } if options.IsPull.Has() { query.Must(elastic.NewTermQuery("is_pull", options.IsPull.Value())) diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index 6c55405179..cdd113212d 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -75,8 +75,9 @@ type SearchResult struct { type SearchOptions struct { Keyword string // keyword to search - RepoIDs []int64 // repository IDs which the issues belong to - AllPublic bool // if include all public repositories + RepoIDs []int64 // repository IDs which the issues belong to + AllPublic bool // if include all public repositories + PriorityRepoID optional.Option[int64] // issues from this repository will be prioritized when SortByScore IsPull optional.Option[bool] // if the issues is a pull request IsClosed optional.Option[bool] // if the issues is closed diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index ef75955a14..b63957ff84 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -742,6 +742,25 @@ var cases = []*testIndexerCase{ } }, }, + { + Name: "PriorityRepoID", + SearchOptions: &internal.SearchOptions{ + IsPull: optional.Some(false), + IsClosed: optional.Some(false), + PriorityRepoID: optional.Some(int64(3)), + Paginator: &db.ListOptionsAll, + SortBy: internal.SortByScore, + }, + Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { + for i, v := range result.Hits { + if i < 7 { + assert.Equal(t, int64(3), data[v.ID].RepoID) + } else { + assert.NotEqual(t, int64(3), data[v.ID].RepoID) + } + } + }, + }, } type testIndexerCase struct { diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 5495c4a6ba..442e109843 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -121,6 +121,12 @@ func SearchIssues(ctx *context.APIContext) { // description: Number of items per page // type: integer // minimum: 0 + // - name: sort + // in: query + // description: Type of sort + // type: string + // enum: [relevance, latest, oldest, recentupdate, leastupdate, mostcomment, leastcomment, nearduedate, farduedate] + // default: latest // responses: // "200": // "$ref": "#/responses/IssueList" @@ -276,7 +282,7 @@ func SearchIssues(ctx *context.APIContext) { IsClosed: isClosed, IncludedAnyLabelIDs: includedAnyLabels, MilestoneIDs: includedMilestones, - SortBy: issue_indexer.SortByCreatedDesc, + SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc), } if since != 0 { @@ -305,9 +311,10 @@ func SearchIssues(ctx *context.APIContext) { } } - // FIXME: It's unsupported to sort by priority repo when searching by indexer, - // it's indeed an regression, but I think it is worth to support filtering by indexer first. - _ = ctx.FormInt64("priority_repo_id") + priorityRepoID := ctx.FormInt64("priority_repo_id") + if priorityRepoID > 0 { + searchOpt.PriorityRepoID = optional.Some(priorityRepoID) + } ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) if err != nil { diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 5e228507c0..a34e3b7c78 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2775,7 +2775,7 @@ func SearchIssues(ctx *context.Context) { IncludedAnyLabelIDs: includedAnyLabels, MilestoneIDs: includedMilestones, ProjectID: projectID, - SortBy: issue_indexer.SortByCreatedDesc, + SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc), } if since != 0 { @@ -2804,9 +2804,10 @@ func SearchIssues(ctx *context.Context) { } } - // FIXME: It's unsupported to sort by priority repo when searching by indexer, - // it's indeed an regression, but I think it is worth to support filtering by indexer first. - _ = ctx.FormInt64("priority_repo_id") + priorityRepoID := ctx.FormInt64("priority_repo_id") + if priorityRepoID > 0 { + searchOpt.PriorityRepoID = optional.Some(priorityRepoID) + } ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) if err != nil { @@ -2944,7 +2945,7 @@ func ListIssues(ctx *context.Context) { IsPull: isPull, IsClosed: isClosed, ProjectID: projectID, - SortBy: issue_indexer.SortByCreatedDesc, + SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc), } if since != 0 { searchOpt.UpdatedAfterUnix = optional.Some(since) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 59c13cd9e6..0e8382b8ab 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -4524,6 +4524,24 @@ "description": "Number of items per page", "name": "limit", "in": "query" + }, + { + "enum": [ + "relevance", + "latest", + "oldest", + "recentupdate", + "leastupdate", + "mostcomment", + "leastcomment", + "nearduedate", + "farduedate" + ], + "type": "string", + "default": "latest", + "description": "Type of sort", + "name": "sort", + "in": "query" } ], "responses": { diff --git a/tests/e2e/declare_repos_test.go b/tests/e2e/declare_repos_test.go index 351f7821eb..93f69faf4c 100644 --- a/tests/e2e/declare_repos_test.go +++ b/tests/e2e/declare_repos_test.go @@ -9,16 +9,23 @@ import ( "testing" "time" + "forgejo.org/models/db" + issues_model "forgejo.org/models/issues" + repo_model "forgejo.org/models/repo" unit_model "forgejo.org/models/unit" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" "forgejo.org/modules/git" "forgejo.org/modules/indexer/stats" + "forgejo.org/modules/optional" + "forgejo.org/modules/timeutil" + issue_service "forgejo.org/services/issue" files_service "forgejo.org/services/repository/files" "forgejo.org/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/xorm/convert" ) // first entry represents filename @@ -29,19 +36,34 @@ type FileChanges struct { Versions []string } +// performs additional repo setup as needed +type SetupRepo func(*user_model.User, *repo_model.Repository) + // put your Git repo declarations in here // feel free to amend the helper function below or use the raw variant directly func DeclareGitRepos(t *testing.T) func() { + now := timeutil.TimeStampNow() + postIssue := func(repo *repo_model.Repository, user *user_model.User, age int64, title, content string) { + issue := &issues_model.Issue{ + RepoID: repo.ID, + PosterID: user.ID, + Title: title, + Content: content, + CreatedUnix: now.Add(-age), + } + require.NoError(t, issue_service.NewIssue(db.DefaultContext, repo, issue, nil, nil, nil)) + } + cleanupFunctions := []func(){ - newRepo(t, 2, "diff-test", []FileChanges{{ + newRepo(t, 2, "diff-test", nil, []FileChanges{{ Filename: "testfile", Versions: []string{"hello", "hallo", "hola", "native", "ubuntu-latest", "- runs-on: ubuntu-latest", "- runs-on: debian-latest"}, - }}), - newRepo(t, 2, "language-stats-test", []FileChanges{{ + }}, nil), + newRepo(t, 2, "language-stats-test", nil, []FileChanges{{ Filename: "main.rs", Versions: []string{"fn main() {", "println!(\"Hello World!\");", "}"}, - }}), - newRepo(t, 2, "mentions-highlighted", []FileChanges{ + }}, nil), + newRepo(t, 2, "mentions-highlighted", nil, []FileChanges{ { Filename: "history1.md", Versions: []string{""}, @@ -52,11 +74,34 @@ func DeclareGitRepos(t *testing.T) func() { Versions: []string{""}, CommitMsg: "Another commit which mentions @user1 in the title\nand @user2 in the text", }, - }), - newRepo(t, 2, "unicode-escaping", []FileChanges{{ + }, nil), + newRepo(t, 2, "unicode-escaping", nil, []FileChanges{{ Filename: "a-file", Versions: []string{"{a}{а}"}, - }}), + }}, nil), + newRepo(t, 11, "dependency-test", &tests.DeclarativeRepoOptions{ + UnitConfig: optional.Some(map[unit_model.Type]convert.Conversion{ + unit_model.TypeIssues: &repo_model.IssuesConfig{ + EnableDependencies: true, + }, + }), + }, []FileChanges{}, func(user *user_model.User, repo *repo_model.Repository) { + postIssue(repo, user, 500, "first issue here", "an issue created earlier") + postIssue(repo, user, 400, "second issue here (not 1)", "not the right issue, but in the right repo") + postIssue(repo, user, 300, "third issue here", "depends on things") + postIssue(repo, user, 200, "unrelated issue", "shrug emoji") + postIssue(repo, user, 100, "newest issue", "very new") + }), + newRepo(t, 11, "dependency-test-2", &tests.DeclarativeRepoOptions{ + UnitConfig: optional.Some(map[unit_model.Type]convert.Conversion{ + unit_model.TypeIssues: &repo_model.IssuesConfig{ + EnableDependencies: true, + }, + }), + }, []FileChanges{}, func(user *user_model.User, repo *repo_model.Repository) { + postIssue(repo, user, 450, "right issue", "an issue containing word right") + postIssue(repo, user, 150, "left issue", "an issue containing word left") + }), // add your repo declarations here } @@ -67,12 +112,18 @@ func DeclareGitRepos(t *testing.T) func() { } } -func newRepo(t *testing.T, userID int64, repoName string, fileChanges []FileChanges) func() { +func newRepo(t *testing.T, userID int64, repoName string, initOpts *tests.DeclarativeRepoOptions, fileChanges []FileChanges, setup SetupRepo) func() { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) - somerepo, _, cleanupFunc := tests.CreateDeclarativeRepo(t, user, repoName, - []unit_model.Type{unit_model.TypeCode, unit_model.TypeIssues}, nil, - nil, - ) + + opts := tests.DeclarativeRepoOptions{} + if initOpts != nil { + opts = *initOpts + } + opts.Name = optional.Some(repoName) + if !opts.EnabledUnits.Has() { + opts.EnabledUnits = optional.Some([]unit_model.Type{unit_model.TypeCode, unit_model.TypeIssues}) + } + somerepo, _, cleanupFunc := tests.CreateDeclarativeRepoWithOptions(t, user, opts) var lastCommitID string for _, file := range fileChanges { @@ -118,6 +169,10 @@ func newRepo(t *testing.T, userID int64, repoName string, fileChanges []FileChan } } + if setup != nil { + setup(user, somerepo) + } + err := stats.UpdateRepoIndexer(somerepo) require.NoError(t, err) diff --git a/tests/e2e/issue-sidebar.test.e2e.ts b/tests/e2e/issue-sidebar.test.e2e.ts index bc65b0842c..34885d0d5d 100644 --- a/tests/e2e/issue-sidebar.test.e2e.ts +++ b/tests/e2e/issue-sidebar.test.e2e.ts @@ -262,3 +262,91 @@ test('New Issue: Milestone', async ({page}, workerInfo) => { await expect(selectedMilestone).toContainText('No milestone'); await save_visual(page); }); + +test.describe('Dependency dropdown', () => { + test.use({user: 'user11'}); + test('Issue: Dependencies', async ({page}) => { + const response = await page.goto('/user11/dependency-test/issues/3'); + expect(response?.status()).toBe(200); + + const depsBlock = page.locator('.issue-content-right .depending'); + const deleteDepBtn = page.locator('.issue-content-right .depending .delete-dependency-button'); + + const input = page.locator('#new-dependency-drop-list .search'); + const current = page.locator('#new-dependency-drop-list .text').first(); + const menu = page.locator('#new-dependency-drop-list .menu'); + const items = page.locator('#new-dependency-drop-list .menu .item'); + + const confirmDelete = async () => { + const modal = page.locator('.modal.remove-dependency'); + await expect(modal).toBeVisible(); + await expect(modal).toContainText('This will remove the dependency from this issue'); + await modal.locator('button.ok').click(); + }; + + // A kludge to set the dropdown to the *wrong* value so it lets us select the correct one next. + const resetDropdown = async () => { + if (await current.textContent().then((s) => s.includes('#4'))) return; + await input.click(); + await input.fill('unrelated'); + await expect(items.first()).toContainText('unrelated'); + await items.first().click(); + await expect(current).toContainText('#4'); + await input.click(); + }; + + await expect(depsBlock).toBeVisible(); + while (await deleteDepBtn.first().isVisible()) { + await deleteDepBtn.first().click(); // wipe added dependencies from any previously failed tests + await confirmDelete(); + } + await expect(depsBlock).toContainText('No dependencies set'); + + await input.scrollIntoViewIfNeeded(); + await input.click(); + + const first = 'first issue here'; + const second = 'second issue here'; + const newest = 'newest issue'; + + // Without query, it should show issues in the same repo, sorted by date, except current one. + await expect(menu).toBeVisible(); + await expect(items).toHaveCount(4); // 5 issues in this repo, minus current one + await expect(items.first()).toContainText(newest); + await expect(items.last()).toContainText(first); + await resetDropdown(); + + // With query, it should search all repos, but show current repo issues first. + await input.fill('right'); + await expect(items.first()).toContainText(second); + await expect.poll(() => items.count()).toBeGreaterThan(1); // there is an issue in user11/dependency-test-2 containing the word "right" + await resetDropdown(); + + // When entering an issue number, it should always show that one first, then all text matches. + await input.fill('1'); + await expect(items.first()).toContainText(first); + await expect(items.nth(1)).toBeVisible(); + await resetDropdown(); + + // Should behave the same with a prefix + await input.fill('#1'); + await expect(items.first()).toContainText(first); + + // Selecting an issue + await items.first().click(); + await expect(current).toContainText(first); + + // Add dependency + const link = page.locator('.issue-content-right .depending .dependency a.title'); + await page.locator('.issue-content-right .depending button').click(); + await expect(link).toHaveAttribute('href', '/user11/dependency-test/issues/1'); + + // Remove dependency + await expect(deleteDepBtn).toBeVisible(); + await deleteDepBtn.click(); + + await confirmDelete(); + + await expect(depsBlock).toContainText('No dependencies set'); + }); +}); diff --git a/tests/e2e/utils_e2e_test.go b/tests/e2e/utils_e2e_test.go index e121c604c3..efa1657cee 100644 --- a/tests/e2e/utils_e2e_test.go +++ b/tests/e2e/utils_e2e_test.go @@ -93,6 +93,7 @@ func createSessions(t testing.TB) { users := []string{ "user1", "user2", + "user11", "user12", "user18", "user29", diff --git a/tests/test_utils.go b/tests/test_utils.go index 75d1f98914..b53159ae2c 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -42,6 +42,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/xorm/convert" ) func exitf(format string, args ...any) { @@ -342,6 +343,7 @@ type DeclarativeRepoOptions struct { Name optional.Option[string] EnabledUnits optional.Option[[]unit_model.Type] DisabledUnits optional.Option[[]unit_model.Type] + UnitConfig optional.Option[map[unit_model.Type]convert.Conversion] Files optional.Option[[]*files_service.ChangeRepoFile] WikiBranch optional.Option[string] AutoInit optional.Option[bool] @@ -390,9 +392,14 @@ func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts enabledUnits = make([]repo_model.RepoUnit, len(units)) for i, unitType := range units { + var config convert.Conversion + if cfg, ok := opts.UnitConfig.Value()[unitType]; ok { + config = cfg + } enabledUnits[i] = repo_model.RepoUnit{ RepoID: repo.ID, Type: unitType, + Config: config, } } } diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 297329d816..bf76453428 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -125,16 +125,21 @@ function excludeLabel(item) { export function initRepoIssueSidebarList() { const repolink = $('#repolink').val(); const repoId = $('#repoId').val(); - const crossRepoSearch = $('#crossRepoSearch').val(); + const crossRepoSearch = $('#crossRepoSearch').val() === 'true'; const tp = $('#type').val(); - let issueSearchUrl = `${appSubUrl}/${repolink}/issues/search?q={query}&type=${tp}`; - if (crossRepoSearch === 'true') { - issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`; - } $('#new-dependency-drop-list') .dropdown({ apiSettings: { - url: issueSearchUrl, + beforeSend(settings) { + if (!settings.urlData.query.trim()) { + settings.url = `${appSubUrl}/${repolink}/issues/search?q={query}&type=${tp}&sort=updated`; + } else if (crossRepoSearch) { + settings.url = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}&sort=relevance`; + } else { + settings.url = `${appSubUrl}/${repolink}/issues/search?q={query}&type=${tp}&sort=relevance`; + } + return settings; + }, onResponse(response) { const filteredResponse = {success: true, results: []}; const currIssueId = $('#new-dependency-drop-list').data('issue-id'); @@ -142,7 +147,7 @@ export function initRepoIssueSidebarList() { for (const [_, issue] of Object.entries(response)) { // Don't list current issue in the dependency list. if (issue.id === currIssueId) { - return; + continue; } filteredResponse.results.push({ name: `#${issue.number} ${issueTitleHTML(htmlEscape(issue.title)) From 7ad20a2730be25112e51f79d982ec5d34fa386bc Mon Sep 17 00:00:00 2001 From: oliverpool Date: Fri, 27 Jun 2025 11:22:10 +0200 Subject: [PATCH 02/30] git/blob: GetContentBase64 with fewer allocations and no goroutine (#8297) See #8222 for context.i `GetBlobContentBase64` was using a pipe and a goroutine to read the blob content as base64. This can be replace by a pre-allocated buffer and a direct copy. Note that although similar to `GetBlobContent`, it does not truncate the content if the blob size is over the limit (but returns an error). I think that `GetBlobContent` should adopt the same behavior at some point (error instead of truncating). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] I do not want this change to show in the release notes. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8297 Reviewed-by: Earl Warren Co-authored-by: oliverpool Co-committed-by: oliverpool --- modules/git/blob.go | 51 +++++++++++++++++----------- modules/git/blob_test.go | 18 ++++++++++ routers/api/v1/repo/wiki.go | 8 ++--- services/repository/files/content.go | 11 +++--- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/modules/git/blob.go b/modules/git/blob.go index 30615afe32..14ca2b1445 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "encoding/base64" + "fmt" "io" "forgejo.org/modules/log" @@ -172,33 +173,43 @@ func (b *Blob) GetBlobContent(limit int64) (string, error) { return string(buf), err } -// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string -func (b *Blob) GetBlobContentBase64() (string, error) { - dataRc, err := b.DataAsync() - if err != nil { - return "", err - } - defer dataRc.Close() +type BlobTooLargeError struct { + Size, Limit int64 +} - pr, pw := io.Pipe() - encoder := base64.NewEncoder(base64.StdEncoding, pw) +func (b BlobTooLargeError) Error() string { + return fmt.Sprintf("blob: content larger than limit (%d > %d)", b.Size, b.Limit) +} - go func() { - _, err := io.Copy(encoder, dataRc) - _ = encoder.Close() - - if err != nil { - _ = pw.CloseWithError(err) - } else { - _ = pw.Close() +// GetContentBase64 Reads the content of the blob and returns it as base64 encoded string. +// Returns [BlobTooLargeError] if the (unencoded) content is larger than the limit. +func (b *Blob) GetContentBase64(limit int64) (string, error) { + if b.Size() > limit { + return "", BlobTooLargeError{ + Size: b.Size(), + Limit: limit, } - }() + } - out, err := io.ReadAll(pr) + rc, size, err := b.NewTruncatedReader(limit) if err != nil { return "", err } - return string(out), nil + defer rc.Close() + + encoding := base64.StdEncoding + buf := bytes.NewBuffer(make([]byte, 0, encoding.EncodedLen(int(size)))) + + encoder := base64.NewEncoder(encoding, buf) + + if _, err := io.Copy(encoder, rc); err != nil { + return "", err + } + if err := encoder.Close(); err != nil { + return "", err + } + + return buf.String(), nil } // GuessContentType guesses the content type of the blob. diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 54115013d3..a4b8033941 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -63,6 +63,24 @@ func TestBlob(t *testing.T) { require.Equal(t, "file2\n", r) }) + t.Run("GetContentBase64", func(t *testing.T) { + r, err := testBlob.GetContentBase64(100) + require.NoError(t, err) + require.Equal(t, "ZmlsZTIK", r) + + r, err = testBlob.GetContentBase64(-1) + require.ErrorAs(t, err, &BlobTooLargeError{}) + require.Empty(t, r) + + r, err = testBlob.GetContentBase64(4) + require.ErrorAs(t, err, &BlobTooLargeError{}) + require.Empty(t, r) + + r, err = testBlob.GetContentBase64(6) + require.NoError(t, err) + require.Equal(t, "ZmlsZTIK", r) + }) + t.Run("NewTruncatedReader", func(t *testing.T) { // read fewer than available rc, size, err := testBlob.NewTruncatedReader(100) diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index bb4cf0f211..7b6a00408a 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -5,6 +5,7 @@ package repo import ( "encoding/base64" + "errors" "fmt" "net/http" "net/url" @@ -506,11 +507,8 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) // given tree entry, encoded with base64. Writes to ctx if an error occurs. func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string { blob := entry.Blob() - if blob.Size() > setting.API.DefaultMaxBlobSize { - return "" - } - content, err := blob.GetBlobContentBase64() - if err != nil { + content, err := blob.GetContentBase64(setting.API.DefaultMaxBlobSize) + if err != nil && !errors.As(err, &git.BlobTooLargeError{}) { ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err) return "" } diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 3d2217df18..dfdee1d1df 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -5,6 +5,7 @@ package files import ( "context" + "errors" "fmt" "net/url" "path" @@ -273,13 +274,11 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git if err != nil { return nil, err } - content := "" - if gitBlob.Size() <= setting.API.DefaultMaxBlobSize { - content, err = gitBlob.GetBlobContentBase64() - if err != nil { - return nil, err - } + content, err := gitBlob.GetContentBase64(setting.API.DefaultMaxBlobSize) + if err != nil && !errors.As(err, &git.BlobTooLargeError{}) { + return nil, err } + return &api.GitBlob{ SHA: gitBlob.ID.String(), URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()), From a2e7446fe7785b2299118b6baf4b89a5821001b8 Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 27 Jun 2025 11:44:59 +0200 Subject: [PATCH 03/30] chore: use eventually for mysql collation test (#8301) - Regression of removing `time.Sleep(5 * time.Second)` in forgejo/forgejo#7917. - Ref: https://codeberg.org/forgejo/forgejo/issues/8221#issuecomment-5532035 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8301 Reviewed-by: Earl Warren Co-authored-by: Gusted Co-committed-by: Gusted --- tests/integration/db_collation_test.go | 29 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/integration/db_collation_test.go b/tests/integration/db_collation_test.go index bf55bdd8ee..c41209f1fe 100644 --- a/tests/integration/db_collation_test.go +++ b/tests/integration/db_collation_test.go @@ -7,6 +7,7 @@ package integration import ( "net/http" "testing" + "time" "forgejo.org/models/db" "forgejo.org/modules/setting" @@ -97,9 +98,13 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_bin")() require.NoError(t, db.ConvertDatabaseTable()) - r, err := db.CheckCollations(x) - require.NoError(t, err) - assert.Equal(t, "utf8mb4_bin", r.DatabaseCollation) + var r *db.CheckCollationsResult + assert.Eventually(t, func() bool { + r, err = db.CheckCollations(x) + require.NoError(t, err) + + return r.DatabaseCollation == "utf8mb4_bin" + }, time.Second*30, time.Second) assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) assert.Empty(t, r.InconsistentCollationColumns) @@ -117,9 +122,13 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_general_ci")() require.NoError(t, db.ConvertDatabaseTable()) - r, err := db.CheckCollations(x) - require.NoError(t, err) - assert.Equal(t, "utf8mb4_general_ci", r.DatabaseCollation) + var r *db.CheckCollationsResult + assert.Eventually(t, func() bool { + r, err = db.CheckCollations(x) + require.NoError(t, err) + + return r.DatabaseCollation == "utf8mb4_general_ci" + }, time.Second*30, time.Second) assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) assert.Empty(t, r.InconsistentCollationColumns) @@ -137,9 +146,15 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "")() require.NoError(t, db.ConvertDatabaseTable()) + var r *db.CheckCollationsResult r, err := db.CheckCollations(x) require.NoError(t, err) - assert.True(t, r.IsCollationCaseSensitive(r.DatabaseCollation)) + assert.Eventually(t, func() bool { + r, err = db.CheckCollations(x) + require.NoError(t, err) + + return r.IsCollationCaseSensitive(r.DatabaseCollation) + }, time.Second*30, time.Second) assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) assert.Empty(t, r.InconsistentCollationColumns) }) From aee161ff255f29ce57155fddca944360779c8a36 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 27 Jun 2025 13:59:07 +0200 Subject: [PATCH 04/30] [gitea] week 2025-19 cherry pick (gitea/main -> forgejo) (#7909) ## Checklist - [x] go to the last cherry-pick PR (forgejo/forgejo#7804) to figure out how far it went: [gitea@a2024953c5](https://github.com/go-gitea/gitea/commit/a2024953c5914c5a7d59d236262f9bd94b65b996) - [x] cherry-pick and open PR (forgejo/forgejo#7909) - [ ] have the PR pass the CI - end-to-end (specially important if there are actions related changes) - [ ] add `run-end-to-end` label - [ ] check the result - [ ] write release notes - [ ] assign reviewers - [ ] 48h later, last call - merge 1 hour after the last call ## Legend - :question: - No decision about the commit has been made. - :cherries: - The commit has been cherry picked. - :fast_forward: - The commit has been skipped. - :bulb: - The commit has been skipped, but should be ported to Forgejo. - :writing_hand: - The commit has been skipped, and a port to Forgejo already exists. ## Commits - :cherries: [`gitea`](https://github.com/go-gitea/gitea/commit/e92c4f18083ed312b69591ebb77e0f504ee77025) -> [`forgejo`](https://codeberg.org/forgejo/forgejo/commit/56fa2caef32c4b0e5017f4b09188ad1dfc8d3603) Add missing setting load in dump-repo command ([gitea#34479](https://github.com/go-gitea/gitea/pull/34479)) - :cherries: [`gitea`](https://github.com/go-gitea/gitea/commit/7b518bc6c79035a53c0b752680d833fce5e1a2fe) -> [`forgejo`](https://codeberg.org/forgejo/forgejo/commit/6e5299606a1bd42cb45ed472a84ba797cf2fa790) Change "rejected" to "changes requested" in 3rd party PR review notification ([gitea#34481](https://github.com/go-gitea/gitea/pull/34481)) ## TODO - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/972381097c6b3488bf9d5c3c4fde04775b2e3a3c) Fix url validation in webhook add/edit API ([gitea#34492](https://github.com/go-gitea/gitea/pull/34492)) Relevant input validation but test needs more backport. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/59df03b5542f05a1a927996fbf6483f7da363e03) Fix get / delete runner to use consistent http 404 and 500 status ([gitea#34480](https://github.com/go-gitea/gitea/pull/34480)) It may be relevant to Forgejo as well ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/1e2f3514b9afd903e19a0413e5d925515e13abf8) Add endpoint deleting workflow run ([gitea#34337](https://github.com/go-gitea/gitea/pull/34337)) Actions, it would be worth having in Forgejo as well. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/5cb4cbf044e2f0483afc92516bb4b9aff6ea2b9a) Fix repo broken check ([gitea#34444](https://github.com/go-gitea/gitea/pull/34444)) Check wether this is relevant to us, port if yes. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/355e9a9d544aa2d3f3a17b06cdb2bf1ceb290fd7) Add a webhook push test for dev branch ([gitea#34421](https://github.com/go-gitea/gitea/pull/34421)) Enhances webhook integration tests. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/34281bc198a5ad9a1faa5285d4648b05d7218aaa) Fix bug webhook milestone is not right. ([gitea#34419](https://github.com/go-gitea/gitea/pull/34419)) Testcode diverged, port required. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/780e92ea99646dfefbe11734a4845fbf304be83c) Only git operations should update `last changed` of a repository ([gitea#34388](https://github.com/go-gitea/gitea/pull/34388)) Port required, would benefit from additional tests. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/b07e03956af8f29464067b19cb5cacee358b592f) When updating comment, if the content is the same, just return and not update the databse ([gitea#34422](https://github.com/go-gitea/gitea/pull/34422)) Codebase diverged, port required. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/71a11872091634f1370374ef123d32798ec0447d) Fix incorrect divergence cache after switching default branch ([gitea#34370](https://github.com/go-gitea/gitea/pull/34370)) Depends on previous gitea changes, port needed. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/4c611bf280c501c22c6a58e94d9e3ce6a73214df) Add a button editing action secret ([gitea#34348](https://github.com/go-gitea/gitea/pull/34348)) This is an interesting feature and it has tests as well. Feature request covering this: https://codeberg.org/forgejo/forgejo/issues/7882 ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/2fbc8f9e87fc37f21997bf32d9b29fc16e92780c) Fix LFS file not stored in LFS when uploaded/edited via API or web UI ([gitea#34367](https://github.com/go-gitea/gitea/pull/34367)) Our code diverged - pls. check relevance & maybe port. ------ - :bulb: [`gitea`](https://github.com/go-gitea/gitea/commit/020e774b915512815637aac743ddbe595c5eede5) feat: add label 'state' to metric 'gitea_users' ([gitea#34326](https://github.com/go-gitea/gitea/pull/34326)) Adjust our existing tests while porting this. ------ ## Skipped - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/ec10c6ba5a6c4c3a5ab9bdf47616d6058c6fb59c) [skip ci] Updated translations via Crowdin ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/d89eed998f9e4dc84d0ef02451703590d7a8ef51) Fix edithook api can not update package, status and workflow_job events ([gitea#34495](https://github.com/go-gitea/gitea/pull/34495)) - gitea actions specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/b6c066747400927407a6b4807165d2de40c7495b) Add R-HNF to the TRANSLATORS file ([gitea#34494](https://github.com/go-gitea/gitea/pull/34494)) - gitea translators update specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/6fbf0e67383dd2970d8e6c309ebc5c634732866c) nix flake update ([gitea#34476](https://github.com/go-gitea/gitea/pull/34476)) - gitea dependency update specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/c24f4b3d2912224164ee3a75850a6a31bdd041f1) Add migrations tests ([gitea#34456](https://github.com/go-gitea/gitea/pull/34456)) ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/bf338bb9e231a8f9ccef7de2e13a0fdf871fc680) Fix project board view ([gitea#34470](https://github.com/go-gitea/gitea/pull/34470)) - gitea ui specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/319d03fbc049de34a6fa0bc3019107ec5b724ff2) [skip ci] Updated translations via Crowdin ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/dd500ce5598848e4f50999b627155ec8520932d3) Fix Workflow run Not Found page ([gitea#34459](https://github.com/go-gitea/gitea/pull/34459)) - gitea actions specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/b6bf128f1e1a943df85c1ce276d0317c8689ffca) [skip ci] Updated translations via Crowdin ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/a0595add72db4a5fb421579b9c6bb7dae1392c86) Fix remove org user failure on mssql ([gitea#34449](https://github.com/go-gitea/gitea/pull/34449)) ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/b5fd3e7210cfbcb87015f3a5b8c3f25eab2a5715) Fix comment textarea scroll issue in Firefox ([gitea#34438](https://github.com/go-gitea/gitea/pull/34438)) - gitea ui specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/4011e2245bcd96f53077f73b7a33b1a754f7151f) Fix releases sidebar navigation link ([gitea#34436](https://github.com/go-gitea/gitea/pull/34436)) - gitea ui specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/0902d42fc753cd5f266046f003307285fe9507d5) [skip ci] Updated translations via Crowdin ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/4a98ab05403ef1900937487d434bc075812b0303) Remove legacy template helper functions ([gitea#34426](https://github.com/go-gitea/gitea/pull/34426)) - gitea specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/9b8609e017aef8376eb59d9fd3e428e35f9caeda) Fix GetUsersByEmails ([gitea#34423](https://github.com/go-gitea/gitea/pull/34423)) - gitea specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/0f63a5ef48b23c6ab26a4b13cfd26edbe4efbfa3) [skip ci] Updated translations via Crowdin ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/ad271444e912ddf44591451292b39b0d6b859955) Fix a bug when uploading file via lfs ssh command ([gitea#34408](https://github.com/go-gitea/gitea/pull/34408)) :skiP: present with PR #7752 ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/8b16ab719cab24805beb2189af7ee960ca94d524) Merge and tweak markup editor expander CSS ([gitea#34409](https://github.com/go-gitea/gitea/pull/34409)) - gitea ui specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/2ecd73d2e586cd4ff2001246d54c4affe0e1ccec) Bump `@github/relative-time-element` to v4.4.8 ([gitea#34413](https://github.com/go-gitea/gitea/pull/34413)) - gitea dependency update specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/179068fddbb463f3a34162730649d82acec522d3) Refactor commit message rendering and fix bugs ([gitea#34412](https://github.com/go-gitea/gitea/pull/34412)) - gitea ui specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/44aadc37c9c0810f3a41189929ae21c613b6bc98) [skip ci] Updated translations via Crowdin ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/f63822fe64b0759dc7e38b467eaa7c41b71d8c5d) Fix autofocus behavior ([gitea#34397](https://github.com/go-gitea/gitea/pull/34397)) - gitea ui specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/82071ee7300d478f56519ec30be0213b18a7882c) [skip ci] Updated translations via Crowdin ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/bbfc21e74f69a9b1ac83271923210c731edd2873) Fix "The sidebar of the repository file list does not have a fixed height #34298" ([gitea#34321](https://github.com/go-gitea/gitea/pull/34321)) - gitea ui specific specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/dd886d729f6206ad878aa5e0f0f3d4d20ea9a208) Update JS and PY dependencies ([gitea#34391](https://github.com/go-gitea/gitea/pull/34391)) - gitea dependency update specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/2a660b4a1b6913f80981d8840b3dc129a4eb7f26) Upgrade go-github v61 -> v71 ([gitea#34385](https://github.com/go-gitea/gitea/pull/34385)) - gitea dependency update specific ------ - :fast_forward: [`gitea`](https://github.com/go-gitea/gitea/commit/6bd8fe53537172fb4df976962115f1b928bf2993) Bump `@github/relative-time-element` to v4.4.7 ([gitea#34384](https://github.com/go-gitea/gitea/pull/34384)) - gitea dependency update specific ------

Stats


Between [`gitea@a2024953c5`](https://github.com/go-gitea/gitea/commit/a2024953c5914c5a7d59d236262f9bd94b65b996) and [`gitea@ec10c6ba5a`](https://github.com/go-gitea/gitea/commit/ec10c6ba5a6c4c3a5ab9bdf47616d6058c6fb59c), **41** commits have been reviewed. We picked **2**, skipped **27**, and decided to port **12**.
Co-authored-by: Sebastian Weigand Co-authored-by: Lunny Xiao Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7909 Reviewed-by: Earl Warren Co-authored-by: Michael Jerger Co-committed-by: Michael Jerger --- cmd/dump_repo.go | 5 +++++ services/webhook/discord.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index eb89273e7f..7159d55e99 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -82,6 +82,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme } func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error { + setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr) + + // setting.DisableLoggerInit() + setting.LoadSettings() // cannot access skip_tls_verify settings otherwise + stdCtx, cancel := installSignals(stdCtx) defer cancel() diff --git a/services/webhook/discord.go b/services/webhook/discord.go index db98d40583..7259c4a995 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -350,7 +350,7 @@ func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, case webhook_module.HookEventPullRequestReviewApproved: return "approved", nil case webhook_module.HookEventPullRequestReviewRejected: - return "rejected", nil + return "requested changes", nil case webhook_module.HookEventPullRequestReviewComment: return "comment", nil default: From 3fb6e171051c4946b93a38bbb5212d3245d485ef Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Fri, 27 Jun 2025 15:29:44 +0200 Subject: [PATCH 05/30] fix: add missing trust status to pull review commits (#8296) Closes: #8293 ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8296 Reviewed-by: Earl Warren Co-authored-by: Lucas Schwiderski Co-committed-by: Lucas Schwiderski --- routers/web/repo/pull.go | 7 ++ tests/integration/pull_commit_test.go | 93 +++++++++++++++++++++++++++ tests/integration/repo_test.go | 78 ++++++++++++++++++++++ 3 files changed, 178 insertions(+) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index fd18646211..4e365f24ea 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -996,6 +996,13 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi ctx.Data["Verification"] = verification ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, curCommit) + if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { + return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID) + }, nil); err != nil { + ctx.ServerError("CalculateTrustStatus", err) + return + } + note := &git.Note{} err = git.GetNote(ctx, ctx.Repo.GitRepo, specifiedEndCommit, note) if err == nil { diff --git a/tests/integration/pull_commit_test.go b/tests/integration/pull_commit_test.go index 8ca78f8147..1de437ef46 100644 --- a/tests/integration/pull_commit_test.go +++ b/tests/integration/pull_commit_test.go @@ -4,13 +4,26 @@ package integration import ( + "context" + "encoding/base64" + "fmt" "net/http" + "net/url" + "os" "testing" + auth_model "forgejo.org/models/auth" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + "forgejo.org/modules/git" + "forgejo.org/modules/setting" + api "forgejo.org/modules/structs" + "forgejo.org/modules/test" pull_service "forgejo.org/services/pull" "forgejo.org/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestListPullCommits(t *testing.T) { @@ -48,3 +61,83 @@ func TestPullCommitLinks(t *testing.T) { commitLinkHref, _ := commitLink.Attr("href") assert.Equal(t, "/user2/repo1/pulls/3/commits/5f22f7d0d95d614d25a5b68592adb345a4b5c7fd", commitLinkHref) } + +func TestPullCommitSignature(t *testing.T) { + t.Cleanup(func() { + // Cannot use t.Context(), it is in the done state. + require.NoError(t, git.InitFull(context.Background())) //nolint:usetesting + }) + + defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")() + defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"always"})() + defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"always"})() + + filePath := "signed.txt" + fromBranch := "master" + toBranch := "branch-signed" + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + // Use a new GNUPGPHOME to avoid messing with the existing GPG keyring. + tmpDir := t.TempDir() + require.NoError(t, os.Chmod(tmpDir, 0o700)) + t.Setenv("GNUPGHOME", tmpDir) + + rootKeyPair, err := importTestingKey() + require.NoError(t, err) + defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())() + defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")() + + // Ensure the git config is updated with the new signing format. + require.NoError(t, git.InitFull(t.Context())) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + testCtx := NewAPITestContext(t, user.Name, "pull-request-commit-header-signed", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + u.Path = testCtx.GitPath() + + t.Run("Create repository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) + + t.Run("Create commit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + options := &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: fromBranch, + NewBranchName: toBranch, + Message: fmt.Sprintf("from:%s to:%s path:%s", fromBranch, toBranch, filePath), + Author: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + Committer: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString(fmt.Appendf(nil, "This is new text for %s", filePath)), + } + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCtx.Username, testCtx.Reponame, filePath), &options). + AddTokenAuth(testCtx.Token) + resp := testCtx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.FileResponse + DecodeJSON(t, resp, &contents) + + assert.True(t, contents.Verification.Verified) + }) + + t.Run("Create pull request", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, fromBranch, toBranch)(t) + require.NoError(t, err) + + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits/%s", testCtx.Username, testCtx.Reponame, pr.Index, pr.Head.Sha)) + resp := testCtx.Session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + htmlDoc.AssertElement(t, "#diff-commit-header .commit-header-row.message.isSigned.isVerified", true) + }) + }) +} diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index b66726a3e6..329a31ace8 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -5,15 +5,19 @@ package integration import ( + "context" + "encoding/base64" "fmt" "net/http" "net/url" + "os" "path" "regexp" "strings" "testing" "time" + auth_model "forgejo.org/models/auth" "forgejo.org/models/db" repo_model "forgejo.org/models/repo" unit_model "forgejo.org/models/unit" @@ -22,6 +26,7 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/optional" "forgejo.org/modules/setting" + api "forgejo.org/modules/structs" "forgejo.org/modules/test" "forgejo.org/modules/translation" repo_service "forgejo.org/services/repository" @@ -682,6 +687,79 @@ func TestViewCommit(t *testing.T) { assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "non-existing commit should render 404 page") } +func TestViewCommitSignature(t *testing.T) { + t.Cleanup(func() { + // Cannot use t.Context(), it is in the done state. + require.NoError(t, git.InitFull(context.Background())) //nolint:usetesting + }) + + defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")() + defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"always"})() + defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"always"})() + + filePath := "signed.txt" + fromBranch := "master" + toBranch := "branch-signed" + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + // Use a new GNUPGPHOME to avoid messing with the existing GPG keyring. + tmpDir := t.TempDir() + require.NoError(t, os.Chmod(tmpDir, 0o700)) + t.Setenv("GNUPGHOME", tmpDir) + + rootKeyPair, err := importTestingKey() + require.NoError(t, err) + defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())() + defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")() + + // Ensure the git config is updated with the new signing format. + require.NoError(t, git.InitFull(t.Context())) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + testCtx := NewAPITestContext(t, user.Name, "commit-header-signed", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + u.Path = testCtx.GitPath() + + t.Run("Create repository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) + + t.Run("Create commit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + options := &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: fromBranch, + NewBranchName: toBranch, + Message: fmt.Sprintf("from:%s to:%s path:%s", fromBranch, toBranch, filePath), + Author: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + Committer: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString(fmt.Appendf(nil, "This is new text for %s", filePath)), + } + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCtx.Username, testCtx.Reponame, filePath), &options). + AddTokenAuth(testCtx.Token) + resp := testCtx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.FileResponse + DecodeJSON(t, resp, &contents) + + assert.True(t, contents.Verification.Verified) + + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/commit/%s", testCtx.Username, testCtx.Reponame, contents.Commit.SHA)) + resp = testCtx.Session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + htmlDoc.AssertElement(t, ".commit-header-row.message.isSigned.isVerified", true) + }) + }) +} + func TestCommitView(t *testing.T) { defer tests.PrepareTestEnv(t)() From c085d6c9ac7afe822a59eccba589c67eb1ac9b1c Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 27 Jun 2025 15:43:31 +0200 Subject: [PATCH 06/30] fix: pass doer's ID for CRUD instance signing (#8304) - When doing CRUD actions, the commiter and author are reconstructed and do not contain the doer's ID. Make sure to pass this ID along so it can be used to verify the rules of instance signing for CRUD actions. - Regression of forgejo/forgejo#7693. It seems that previously this didn't work correctly as it would not care about a empty ID. - Resolves forgejo/forgejo#8278 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8304 Reviewed-by: Michael Kriese Reviewed-by: Beowulf Reviewed-by: Earl Warren Co-authored-by: Gusted Co-committed-by: Gusted --- services/repository/files/file.go | 51 +++++++++++----------- tests/integration/signing_git_test.go | 61 ++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/services/repository/files/file.go b/services/repository/files/file.go index ef9a87dbcf..5b93258840 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -104,36 +104,35 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m // then we use bogus User objects for them to store their FullName and Email. // If only one of the two are provided, we set both of them to it. // If neither are provided, both are the doer. - if committer != nil && committer.Email != "" { - if doer != nil && strings.EqualFold(doer.Email, committer.Email) { - committerUser = doer // the committer is the doer, so will use their user object - if committer.Name != "" { - committerUser.FullName = committer.Name + getUser := func(identity *IdentityOptions) *user_model.User { + if identity == nil || identity.Email == "" { + return nil + } + + if doer != nil && strings.EqualFold(doer.Email, identity.Email) { + user := doer // the committer is the doer, so will use their user object + if identity.Name != "" { + user.FullName = identity.Name } // Use the provided email and not revert to placeholder mail. - committerUser.KeepEmailPrivate = false - } else { - committerUser = &user_model.User{ - FullName: committer.Name, - Email: committer.Email, - } - } - } - if author != nil && author.Email != "" { - if doer != nil && strings.EqualFold(doer.Email, author.Email) { - authorUser = doer // the author is the doer, so will use their user object - if authorUser.Name != "" { - authorUser.FullName = author.Name - } - // Use the provided email and not revert to placeholder mail. - authorUser.KeepEmailPrivate = false - } else { - authorUser = &user_model.User{ - FullName: author.Name, - Email: author.Email, - } + user.KeepEmailPrivate = false + return user + } + + var id int64 + if doer != nil { + id = doer.ID + } + return &user_model.User{ + ID: id, // Needed to ensure the doer is checked to pass rules for instance signing of CRUD actions. + FullName: identity.Name, + Email: identity.Email, } } + + committerUser = getUser(committer) + authorUser = getUser(author) + if authorUser == nil { if committerUser != nil { authorUser = committerUser // No valid author was given so use the committer diff --git a/tests/integration/signing_git_test.go b/tests/integration/signing_git_test.go index 9d69306e0a..c4759aaddb 100644 --- a/tests/integration/signing_git_test.go +++ b/tests/integration/signing_git_test.go @@ -235,7 +235,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O })) }) - t.Run("No publickey", func(t *testing.T) { + t.Run("No 2fa", func(t *testing.T) { defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, "user4", "initial-no-2fa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) @@ -287,6 +287,65 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O })) }) + t.Run("AlwaysSign-Initial-CRUD-Pubkey", func(t *testing.T) { + setting.Repository.Signing.CRUDActions = []string{"pubkey"} + + t.Run("Has publickey", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + testCtx := NewAPITestContext(t, username, "initial-always-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( + t, testCtx, user, "master", "pubkey", "signed-pubkey.txt", func(t *testing.T, response api.FileResponse) { + assert.True(t, response.Verification.Verified) + assert.Equal(t, "fox@example.com", response.Verification.Signer.Email) + })) + }) + + t.Run("No publickey", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + testCtx := NewAPITestContext(t, "user4", "initial-always-no-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( + t, testCtx, user, "master", "pubkey", "unsigned-pubkey.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }) + + t.Run("AlwaysSign-Initial-CRUD-Twofa", func(t *testing.T) { + setting.Repository.Signing.CRUDActions = []string{"twofa"} + + t.Run("Has 2fa", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Cleanup(func() { + unittest.AssertSuccessfulDelete(t, &auth_model.WebAuthnCredential{UserID: user.ID}) + }) + + testCtx := NewAPITestContext(t, username, "initial-always-twofa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + unittest.AssertSuccessfulInsert(t, &auth_model.WebAuthnCredential{UserID: user.ID}) + t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateCRUDFile-Twofa", crudActionCreateFile( + t, testCtx, user, "master", "twofa", "signed-twofa.txt", func(t *testing.T, response api.FileResponse) { + assert.True(t, response.Verification.Verified) + assert.Equal(t, "fox@example.com", response.Verification.Signer.Email) + })) + }) + + t.Run("No 2fa", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + testCtx := NewAPITestContext(t, "user4", "initial-always-no-twofa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( + t, testCtx, user, "master", "twofa", "unsigned-twofa.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }) + t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) { defer tests.PrintCurrentTest(t)() setting.Repository.Signing.CRUDActions = []string{"always"} From 69d374435b0aa9e4c6891e0072ba8e6241696a8e Mon Sep 17 00:00:00 2001 From: floss4good Date: Fri, 27 Jun 2025 15:47:58 +0200 Subject: [PATCH 07/30] fix: abuse reports string data types (#8267) Follow-up of !6977 I was fooled by the fact that for SQLite the columns corresponding to `string` fields were created as `TEXT`; but this is not the case for PostgreSQL and MariaDB/MySQL. According to XORM default mapping rules[^1] _String is corresponding to varchar(255)_. Therefore `abuse_report`.`remarks` should be of type `VARCHAR(500)` and `abuse_report_shadow_copy`.`raw_value` of type `LONGTEXT`. ### Testing I have dropped the affected columns (or the entire tables) and checked that for PostgreSQL and MariaDB they are created with the correct type and also manually tested the abusive content reporting functionality in order to make sure that no DB error will be returned if for 'Remarks' a text longer than 255 characters is submitted or when a (big) shadow copy is created. [^1]: https://xorm.io/docs/chapter-02/4.columns/ Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8267 Reviewed-by: Gusted Co-authored-by: floss4good Co-committed-by: floss4good --- models/moderation/abuse_report.go | 2 +- models/moderation/shadow_copy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/moderation/abuse_report.go b/models/moderation/abuse_report.go index dadd61a95e..3a6244ef4c 100644 --- a/models/moderation/abuse_report.go +++ b/models/moderation/abuse_report.go @@ -100,7 +100,7 @@ type AbuseReport struct { // The abuse category selected by the reporter. Category AbuseCategoryType `xorm:"INDEX NOT NULL"` // Remarks provided by the reporter. - Remarks string + Remarks string `xorm:"VARCHAR(500)"` // The ID of the corresponding shadow-copied content when exists; otherwise null. ShadowCopyID sql.NullInt64 `xorm:"DEFAULT NULL"` CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` diff --git a/models/moderation/shadow_copy.go b/models/moderation/shadow_copy.go index cdd8f69c52..d363610a48 100644 --- a/models/moderation/shadow_copy.go +++ b/models/moderation/shadow_copy.go @@ -17,7 +17,7 @@ import ( type AbuseReportShadowCopy struct { ID int64 `xorm:"pk autoincr"` - RawValue string `xorm:"NOT NULL"` + RawValue string `xorm:"LONGTEXT NOT NULL"` // A JSON with relevant fields from user, repository, issue or comment table. CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` } From 6b27fa66b9876390323169089e2136d1cdd8dd6e Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 27 Jun 2025 17:37:29 +0200 Subject: [PATCH 08/30] chore: sort blocked users list for determistic results (#8320) - Ref https://codeberg.org/forgejo/forgejo/issues/8221#issuecomment-5515478 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8320 Reviewed-by: Earl Warren Co-authored-by: Gusted Co-committed-by: Gusted --- tests/integration/api_block_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/api_block_test.go b/tests/integration/api_block_test.go index 8b25ce9283..0e2cf7ee25 100644 --- a/tests/integration/api_block_test.go +++ b/tests/integration/api_block_test.go @@ -4,8 +4,10 @@ package integration import ( + "cmp" "fmt" "net/http" + "slices" "testing" auth_model "forgejo.org/models/auth" @@ -46,6 +48,7 @@ func TestAPIUserBlock(t *testing.T) { var blockedUsers []api.BlockedUser DecodeJSON(t, resp, &blockedUsers) assert.Len(t, blockedUsers, 2) + slices.SortFunc(blockedUsers, func(a, b api.BlockedUser) int { return cmp.Compare(a.BlockID, b.BlockID) }) assert.EqualValues(t, 1, blockedUsers[0].BlockID) assert.EqualValues(t, 2, blockedUsers[1].BlockID) }) From 225a0f7026e885cec6605ddcf27aeacfb1a90221 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Fri, 27 Jun 2025 23:10:09 +0200 Subject: [PATCH 09/30] git/TreeEntry: LinkTarget simplification (#8323) See #8222 for context. This PR removes a call to `Blob.GetBlobContent` and `Blob.DataAsync` and unifies symlink resolution: - length was unlimited in one case - length was truncated to 1024 chars in the other case Now it is hard-limited to 4096 chars (ie error if larger), which is a length which seems appropriate according to https://stackoverflow.com/a/22575737. ### Tests - Tests are already present in `tests/integration/repo_test.go:972`: `TestRepoFollowSymlink` (it caught a cap/len stupid mistake). - [x] I did not document these changes and I do not expect someone else to do it. - [x] I do not want this change to show in the release notes. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8323 Reviewed-by: Earl Warren Co-authored-by: oliverpool Co-committed-by: oliverpool --- modules/git/tree_entry.go | 43 ++++++++++++++++------------ services/repository/files/content.go | 2 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index d51b7992fe..ec5c632ca0 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -116,32 +116,37 @@ func (te *TreeEntry) Type() string { } } +// LinkTarget returns the target of the symlink as string. +func (te *TreeEntry) LinkTarget() (string, error) { + if !te.IsLink() { + return "", ErrBadLink{te.Name(), "not a symlink"} + } + + const symlinkLimit = 4096 // according to git config core.longpaths https://stackoverflow.com/a/22575737 + blob := te.Blob() + if blob.Size() > symlinkLimit { + return "", ErrBadLink{te.Name(), "symlink too large"} + } + + rc, size, err := blob.NewTruncatedReader(symlinkLimit) + if err != nil { + return "", err + } + defer rc.Close() + + buf := make([]byte, int(size)) + _, err = io.ReadFull(rc, buf) + return string(buf), err +} + // FollowLink returns the entry pointed to by a symlink func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) { - if !te.IsLink() { - return nil, "", ErrBadLink{te.Name(), "not a symlink"} - } - // read the link - r, err := te.Blob().DataAsync() + lnk, err := te.LinkTarget() if err != nil { return nil, "", err } - closed := false - defer func() { - if !closed { - _ = r.Close() - } - }() - buf := make([]byte, te.Size()) - _, err = io.ReadFull(r, buf) - if err != nil { - return nil, "", err - } - _ = r.Close() - closed = true - lnk := string(buf) t := te.ptree // traverse up directories diff --git a/services/repository/files/content.go b/services/repository/files/content.go index dfdee1d1df..5a6006e9f2 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -206,7 +206,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref } else if entry.IsLink() { contentsResponse.Type = string(ContentTypeLink) // The target of a symlink file is the content of the file - targetFromContent, err := entry.Blob().GetBlobContent(1024) + targetFromContent, err := entry.LinkTarget() if err != nil { return nil, err } From d6e4342353a19d8579687743f020f9bd9bc99435 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sat, 28 Jun 2025 23:28:12 +0200 Subject: [PATCH 10/30] fix: make API /repos/{owner}/{repo}/compare/{basehead} work with forks (#8326) - fix: API must use headGitRepo instead of ctx.Repo.GitRepo for comparing - fix: make API /repos/{owner}/{repo}/compare/{basehead} work with forks - add test coverage for both fixes and the underlying function `parseCompareInfo` - refactor and improve part of the helpers from `tests/integration/api_helper_for_declarative_test.go` - remove a few wrong or misleading comments Refs forgejo/forgejo#7978 ## Note on the focus of the PR It was initially created to address a regression introduced in v12. But the tests that verify it is fixed discovered a v11.0 bug. They cannot conveniently be separated because they both relate to the same area of code that was previously not covered by any test. ## Note on v11.0 backport It must be manually done by cherry-picking all commits up to and not including `fix: API must use headGitRepo instead of ctx.Repo.GitRepo for comparing` because it is v12 specific. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. ### Documentation - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] I do not want this change to show in the release notes. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8326 Reviewed-by: Otto Co-authored-by: Earl Warren Co-committed-by: Earl Warren --- routers/api/v1/repo/compare.go | 4 +- routers/api/v1/repo/pull.go | 9 +- tests/integration/api_branch_test.go | 2 +- .../api_helper_for_declarative_test.go | 67 ++++-- tests/integration/api_repo_compare_test.go | 224 ++++++++++++++---- .../integration/api_repo_file_create_test.go | 9 +- tests/integration/api_repo_file_get_test.go | 2 +- tests/integration/api_repo_lfs_test.go | 2 +- tests/integration/api_repo_test.go | 4 +- tests/integration/git_test.go | 37 +-- tests/integration/pull_commit_test.go | 2 +- tests/integration/repo_test.go | 2 +- tests/integration/signing_git_test.go | 71 +++--- tests/integration/ssh_key_test.go | 14 +- 14 files changed, 311 insertions(+), 138 deletions(-) diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go index 9c941ea07f..7fc59ea171 100644 --- a/routers/api/v1/repo/compare.go +++ b/routers/api/v1/repo/compare.go @@ -64,7 +64,7 @@ func CompareDiff(ctx *context.APIContext) { } } - _, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ + headRepository, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ Base: infos[0], Head: infos[1], }) @@ -80,7 +80,7 @@ func CompareDiff(ctx *context.APIContext) { apiFiles := []*api.CommitAffectedFiles{} userCache := make(map[string]*user_model.User) for i := 0; i < len(ci.Commits); i++ { - apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache, + apiCommit, err := convert.ToCommit(ctx, headRepository, headGitRepo, ci.Commits[i], userCache, convert.ToCommitOptions{ Stat: true, Verification: verification, diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index c9dda124de..9360ff1335 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1125,7 +1125,6 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } } - // Check if current user has fork of repository or in the same repository. headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID) if headRepo == nil && !isSameRepo { err := baseRepo.GetBaseRepo(ctx) @@ -1134,13 +1133,11 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) return nil, nil, nil, "", "" } - // Check if baseRepo's base repository is the same as headUser's repository. if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID { log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) ctx.NotFound("GetBaseRepo") return nil, nil, nil, "", "" } - // Assign headRepo so it can be used below. headRepo = baseRepo.BaseRepo } @@ -1202,9 +1199,9 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } // Check if head branch is valid. - headIsCommit := ctx.Repo.GitRepo.IsCommitExist(headBranch) - headIsBranch := ctx.Repo.GitRepo.IsBranchExist(headBranch) - headIsTag := ctx.Repo.GitRepo.IsTagExist(headBranch) + headIsCommit := headGitRepo.IsCommitExist(headBranch) + headIsBranch := headGitRepo.IsBranchExist(headBranch) + headIsTag := headGitRepo.IsTagExist(headBranch) if !headIsCommit && !headIsBranch && !headIsTag { // Check if headBranch is short sha commit hash if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil { diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 8e88501596..d8800217d3 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -116,7 +116,7 @@ func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { ctx := NewAPITestContext(t, "user2", "my-noo-repo-"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() - t.Run("CreateRepo", doAPICreateRepository(ctx, false, objectFormat)) + t.Run("CreateRepo", doAPICreateRepository(ctx, nil, objectFormat)) testCases := []struct { OldBranch string NewBranch string diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index c9b6f84f4f..ada6a2c311 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -48,20 +48,22 @@ func (ctx APITestContext) GitPath() string { return fmt.Sprintf("%s/%s.git", ctx.Username, ctx.Reponame) } -func doAPICreateRepository(ctx APITestContext, empty bool, objectFormat git.ObjectFormat, callback ...func(*testing.T, api.Repository)) func(*testing.T) { +func doAPICreateRepository(ctx APITestContext, opts *api.CreateRepoOption, objectFormat git.ObjectFormat, callback ...func(*testing.T, api.Repository)) func(*testing.T) { return func(t *testing.T) { - createRepoOption := &api.CreateRepoOption{ - AutoInit: !empty, - Description: "Temporary repo", - Name: ctx.Reponame, - Private: true, - Template: true, - Gitignores: "", - License: "WTFPL", - Readme: "Default", - ObjectFormatName: objectFormat.Name(), + if opts == nil { + opts = &api.CreateRepoOption{ + AutoInit: true, + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Template: true, + Gitignores: "", + License: "WTFPL", + Readme: "Default", + } } - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", createRepoOption). + opts.ObjectFormatName = objectFormat.Name() + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", opts). AddTokenAuth(ctx.Token) if ctx.ExpectedCode != 0 { ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) @@ -237,8 +239,8 @@ func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBra } } -func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) { - return func(t *testing.T) (api.PullRequest, error) { +func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) api.PullRequest { + return func(t *testing.T) api.PullRequest { req := NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner, repo, index)). AddTokenAuth(ctx.Token) @@ -248,10 +250,9 @@ func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) fu } resp := ctx.Session.MakeRequest(t, req, expected) - decoder := json.NewDecoder(resp.Body) pr := api.PullRequest{} - err := decoder.Decode(&pr) - return pr, err + DecodeJSON(t, resp, &pr) + return pr } } @@ -347,20 +348,40 @@ func doAPICancelAutoMergePullRequest(ctx APITestContext, owner, repo string, ind } } -func doAPIGetBranch(ctx APITestContext, branch string, callback ...func(*testing.T, api.Branch)) func(*testing.T) { - return func(t *testing.T) { +func doAPIGetBranch(ctx APITestContext, branch string) func(*testing.T) api.Branch { + return func(t *testing.T) api.Branch { req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, branch). AddTokenAuth(ctx.Token) + expected := http.StatusOK + if ctx.ExpectedCode != 0 { + expected = ctx.ExpectedCode + } + resp := ctx.Session.MakeRequest(t, req, expected) + + branch := api.Branch{} + DecodeJSON(t, resp, &branch) + return branch + } +} + +func doAPICreateTag(ctx APITestContext, tag, target, message string, callback ...func(*testing.T, api.Tag)) func(*testing.T) { + return func(t *testing.T) { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/tags", ctx.Username, ctx.Reponame), &api.CreateTagOption{ + TagName: tag, + Message: message, + Target: target, + }). + AddTokenAuth(ctx.Token) if ctx.ExpectedCode != 0 { ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) return } - resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) - var branch api.Branch - DecodeJSON(t, resp, &branch) + var tag api.Tag + DecodeJSON(t, resp, &tag) if len(callback) > 0 { - callback[0](t, branch) + callback[0](t, tag) } } } diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index 35f0a21d82..e4e85fc742 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -4,56 +4,200 @@ package integration import ( + "encoding/base64" + "fmt" "net/http" + "net/url" "testing" + "time" auth_model "forgejo.org/models/auth" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" + "forgejo.org/modules/git" api "forgejo.org/modules/structs" - "forgejo.org/tests" "github.com/stretchr/testify/assert" ) -func TestAPICompareBranches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - repoName := "repo20" - - req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - var apiResp *api.Compare - DecodeJSON(t, resp, &apiResp) - - assert.Equal(t, 2, apiResp.TotalCommits) - assert.Len(t, apiResp.Commits, 2) - assert.Len(t, apiResp.Files, 3) -} - func TestAPICompareCommits(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/c8e31bc...8babce9"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - var apiResp *api.Compare - DecodeJSON(t, resp, &apiResp) - - assert.Equal(t, 2, apiResp.TotalCommits) - assert.Len(t, apiResp.Commits, 2) - assert.Len(t, apiResp.Files, 3) + forEachObjectFormat(t, testAPICompareCommits) +} + +func testAPICompareCommits(t *testing.T, objectFormat git.ObjectFormat) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + newBranchAndFile := func(ctx APITestContext, user *user_model.User, branch, filename string) func(*testing.T) { + return func(t *testing.T) { + doAPICreateFile(ctx, filename, &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: branch, + Message: "create " + filename, + Author: api.Identity{ + Name: user.Name, + Email: user.Email, + }, + Committer: api.Identity{ + Name: user.Name, + Email: user.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("content " + filename)), + })(t) + } + } + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2repo := "repoA" + user2Ctx := NewAPITestContext(t, user2.Name, user2repo, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateUser2Repository", doAPICreateRepository(user2Ctx, &api.CreateRepoOption{ + AutoInit: true, + Description: "Temporary repo", + Name: user2Ctx.Reponame, + }, objectFormat)) + user2branchName := "user2branch" + t.Run("CreateUser2RepositoryBranch", newBranchAndFile(user2Ctx, user2, user2branchName, "user2branchfilename.txt")) + user2branch := doAPIGetBranch(user2Ctx, user2branchName)(t) + user2master := doAPIGetBranch(user2Ctx, "master")(t) + user2tag1 := "tag1" + t.Run("CreateUser2RepositoryTag1", doAPICreateTag(user2Ctx, user2tag1, "master", "user2branchtag1")) + user2tag2 := "tag2" + t.Run("CreateUser2RepositoryTag1", doAPICreateTag(user2Ctx, user2tag2, user2branchName, "user2branchtag2")) + + shortCommitLength := 7 + + for _, testCase := range []struct { + name string + a string + b string + }{ + { + name: "Commits", + a: user2master.Commit.ID, + b: user2branch.Commit.ID, + }, + { + name: "ShortCommits", + a: user2master.Commit.ID[:shortCommitLength], + b: user2branch.Commit.ID[:shortCommitLength], + }, + { + name: "Branches", + a: "master", + b: user2branchName, + }, + { + name: "Tags", + a: user2tag1, + b: user2tag2, + }, + } { + t.Run("SameRepo"+testCase.name, func(t *testing.T) { + // a...b + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s", user2.Name, user2repo, testCase.a, testCase.b). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + assert.Len(t, apiResp.Files, 1) + + // b...a + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s", user2.Name, user2repo, testCase.b, testCase.a). + AddTokenAuth(user2Ctx.Token) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 0, apiResp.TotalCommits) + assert.Empty(t, apiResp.Commits) + assert.Empty(t, apiResp.Files) + }) + } + + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user4Ctx := NewAPITestContext(t, user4.Name, user2repo, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("User4ForksUser2Repository", doAPIForkRepository(user4Ctx, user2.Name)) + user4branchName := "user4branch" + t.Run("CreateUser4RepositoryBranch", newBranchAndFile(user4Ctx, user4, user4branchName, "user4branchfilename.txt")) + user4branch := doAPIGetBranch(user4Ctx, user4branchName)(t) + user4tag4 := "tag4" + t.Run("CreateUser4RepositoryTag4", doAPICreateTag(user4Ctx, user4tag4, user4branchName, "user4branchtag4")) + + t.Run("FromTheForkedRepo", func(t *testing.T) { + // user4/repoA is a fork of user2/repoA and when evaluating + // + // user4/repoA/compare/master...user2:user2branch + // + // user2/repoA is not explicitly specified, it is implicitly the repository + // from which user4/repoA was forked + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s:%s", user4.Name, user2repo, "master", user2.Name, user2branchName). + AddTokenAuth(user4Ctx.Token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + assert.Len(t, apiResp.Files, 1) + }) + + for _, testCase := range []struct { + name string + a string + b string + }{ + { + name: "Commits", + a: user2master.Commit.ID, + b: fmt.Sprintf("%s:%s", user4.Name, user4branch.Commit.ID), + }, + { + name: "ShortCommits", + a: user2master.Commit.ID[:shortCommitLength], + b: fmt.Sprintf("%s:%s", user4.Name, user4branch.Commit.ID[:shortCommitLength]), + }, + { + name: "Branches", + a: "master", + b: fmt.Sprintf("%s:%s", user4.Name, user4branchName), + }, + { + name: "Tags", + a: user2tag1, + b: fmt.Sprintf("%s:%s", user4.Name, user4tag4), + }, + { + name: "SameRepo", + a: "master", + b: fmt.Sprintf("%s:%s", user2.Name, user2branchName), + }, + } { + t.Run("ForkedRepo"+testCase.name, func(t *testing.T) { + // user2/repoA is forked into user4/repoA and when evaluating + // + // user2/repoA/compare/a...user4:b + // + // user4/repoA is not explicitly specified, it is implicitly the repository + // owned by user4 which is a fork of repoA + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s", user2.Name, user2repo, testCase.a, testCase.b). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + assert.Len(t, apiResp.Files, 1) + }) + } + }) } diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index c112653e11..4916ef97ef 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -287,7 +287,14 @@ func TestAPICreateFile(t *testing.T) { // Test creating a file in an empty repository forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { reponame := "empty-repo-" + objectFormat.Name() - doAPICreateRepository(NewAPITestContext(t, "user2", reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true, objectFormat)(t) + ctx := NewAPITestContext(t, "user2", reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + opts := &api.CreateRepoOption{ + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Template: true, + } + doAPICreateRepository(ctx, opts, objectFormat)(t) createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) diff --git a/tests/integration/api_repo_file_get_test.go b/tests/integration/api_repo_file_get_test.go index 7bd7393b01..408c630a1c 100644 --- a/tests/integration/api_repo_file_get_test.go +++ b/tests/integration/api_repo_file_get_test.go @@ -27,7 +27,7 @@ func TestAPIGetRawFileOrLFS(t *testing.T) { // Test with LFS onGiteaRun(t, func(t *testing.T, u *url.URL) { httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository) - doAPICreateRepository(httpContext, false, git.Sha1ObjectFormat, func(t *testing.T, repository api.Repository) { // FIXME: use forEachObjectFormat + doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat, func(t *testing.T, repository api.Repository) { // FIXME: use forEachObjectFormat u.Path = httpContext.GitPath() dstPath := t.TempDir() diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go index b8ba54f876..b0edf7b854 100644 --- a/tests/integration/api_repo_lfs_test.go +++ b/tests/integration/api_repo_lfs_test.go @@ -64,7 +64,7 @@ func TestAPILFSMediaType(t *testing.T) { func createLFSTestRepository(t *testing.T, name string) *repo_model.Repository { ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepo", doAPICreateRepository(ctx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepo", doAPICreateRepository(ctx, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs-"+name+"-repo") require.NoError(t, err) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index fd62670eb3..e81f4307ee 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -415,7 +415,7 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { httpContext := baseAPITestContext httpContext.Reponame = "repo-tmp-17" - t.Run("CreateRepo", doAPICreateRepository(httpContext, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepo", doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat user, err := user_model.GetUserByName(db.DefaultContext, httpContext.Username) require.NoError(t, err) @@ -498,7 +498,7 @@ func testAPIRepoCreateConflict(t *testing.T, u *url.URL) { httpContext := baseAPITestContext httpContext.Reponame = "repo-tmp-17" - t.Run("CreateRepo", doAPICreateRepository(httpContext, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepo", doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index e79f6fe802..9a66781024 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -69,7 +69,7 @@ func testGit(t *testing.T, u *url.URL) { dstPath := t.TempDir() - t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat)) + t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, nil, objectFormat)) t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead)) t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username)) @@ -110,7 +110,7 @@ func testGit(t *testing.T, u *url.URL) { sshContext.Reponame = "repo-tmp-18-" + objectFormat.Name() keyname := "my-testing-key" forkedUserCtx.Reponame = sshContext.Reponame - t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat)) + t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, nil, objectFormat)) t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead)) t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username)) @@ -529,8 +529,7 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun t.Run("EnsureCanSeePull", doEnsureCanSeePull(headCtx, pr, false)) t.Run("CheckPR", func(t *testing.T) { oldMergeBase := pr.MergeBase - pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr2 := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.Equal(t, oldMergeBase, pr2.MergeBase) }) t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) @@ -730,24 +729,21 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { // Check pr status ctx.ExpectedCode = 0 - pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.False(t, pr.HasMerged) // Call API to add Failure status for commit t.Run("CreateStatus", addCommitStatus(api.CommitStatusFailure)) // Check pr status - pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.False(t, pr.HasMerged) // Call API to add Success status for commit t.Run("CreateStatus", addCommitStatus(api.CommitStatusSuccess)) // test pr status - pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.True(t, pr.HasMerged) } } @@ -836,8 +832,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.Equal(t, 1, pr1.CommitsAhead) assert.Equal(t, 0, pr1.CommitsBehind) - prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) - require.NoError(t, err) + prMsg := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch) assert.False(t, prMsg.HasMerged) @@ -858,8 +853,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string } assert.Equal(t, 1, pr2.CommitsAhead) assert.Equal(t, 0, pr2.CommitsBehind) - prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) - require.NoError(t, err) + prMsg = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch) assert.False(t, prMsg.HasMerged) @@ -910,8 +904,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) - prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) - require.NoError(t, err) + prMsg := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) assert.False(t, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) @@ -928,8 +921,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) - prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) - require.NoError(t, err) + prMsg = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) assert.False(t, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) @@ -953,8 +945,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string err := pr3.LoadIssue(db.DefaultContext) require.NoError(t, err) - _, err2 := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr3.Index)(t) - require.NoError(t, err2) + doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr3.Index)(t) assert.Equal(t, "Testing commit 2", pr3.Issue.Title) assert.Contains(t, pr3.Issue.Content, "Longer description.") @@ -975,8 +966,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string err := pr.LoadIssue(db.DefaultContext) require.NoError(t, err) - _, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) - require.NoError(t, err) + doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) assert.Equal(t, "my-shiny-title", pr.Issue.Title) assert.Contains(t, pr.Issue.Content, "Longer description.") @@ -998,8 +988,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string err := pr.LoadIssue(db.DefaultContext) require.NoError(t, err) - _, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) - require.NoError(t, err) + doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) assert.Equal(t, "Testing commit 2", pr.Issue.Title) assert.Contains(t, pr.Issue.Content, "custom") diff --git a/tests/integration/pull_commit_test.go b/tests/integration/pull_commit_test.go index 1de437ef46..f82fc08df4 100644 --- a/tests/integration/pull_commit_test.go +++ b/tests/integration/pull_commit_test.go @@ -95,7 +95,7 @@ func TestPullCommitSignature(t *testing.T) { testCtx := NewAPITestContext(t, user.Name, "pull-request-commit-header-signed", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) u.Path = testCtx.GitPath() - t.Run("Create repository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) + t.Run("Create repository", doAPICreateRepository(testCtx, nil, git.Sha1ObjectFormat)) t.Run("Create commit", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 329a31ace8..7370b63dcd 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -720,7 +720,7 @@ func TestViewCommitSignature(t *testing.T) { testCtx := NewAPITestContext(t, user.Name, "commit-header-signed", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) u.Path = testCtx.GitPath() - t.Run("Create repository", doAPICreateRepository(testCtx, false, git.Sha1ObjectFormat)) + t.Run("Create repository", doAPICreateRepository(testCtx, nil, git.Sha1ObjectFormat)) t.Run("Create commit", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/signing_git_test.go b/tests/integration/signing_git_test.go index c4759aaddb..e4c0d6049b 100644 --- a/tests/integration/signing_git_test.go +++ b/tests/integration/signing_git_test.go @@ -109,13 +109,14 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) assert.NotNil(t, branch.Commit) assert.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) assert.Empty(t, branch.Commit.Verification.Signature) - })) + }) t.Run("CreateCRUDFile-Never", crudActionCreateFile( t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -191,25 +192,27 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, username, "initial-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "fox@example.com", branch.Commit.Verification.Signer.Email) - })) + }) }) t.Run("No publickey", func(t *testing.T) { defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, "user4", "initial-no-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) - })) + }) }) }) @@ -226,25 +229,27 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O testCtx := NewAPITestContext(t, username, "initial-2fa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) unittest.AssertSuccessfulInsert(t, &auth_model.WebAuthnCredential{UserID: user.ID}) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "fox@example.com", branch.Commit.Verification.Signer.Email) - })) + }) }) t.Run("No 2fa", func(t *testing.T) { defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, "user4", "initial-no-2fa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) - })) + }) }) }) @@ -253,13 +258,14 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O setting.Repository.Signing.InitialCommit = []string{"always"} testCtx := NewAPITestContext(t, username, "initial-always"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "fox@example.com", branch.Commit.Verification.Signer.Email) - })) + }) }) t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) { @@ -267,7 +273,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O setting.Repository.Signing.CRUDActions = []string{"never"} testCtx := NewAPITestContext(t, username, "initial-always-never"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Never", crudActionCreateFile( t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -279,7 +285,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O setting.Repository.Signing.CRUDActions = []string{"parentsigned"} testCtx := NewAPITestContext(t, username, "initial-always-parent"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { assert.True(t, response.Verification.Verified) @@ -294,7 +300,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, username, "initial-always-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( t, testCtx, user, "master", "pubkey", "signed-pubkey.txt", func(t *testing.T, response api.FileResponse) { assert.True(t, response.Verification.Verified) @@ -306,7 +312,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, "user4", "initial-always-no-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( t, testCtx, user, "master", "pubkey", "unsigned-pubkey.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -326,7 +332,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O testCtx := NewAPITestContext(t, username, "initial-always-twofa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) unittest.AssertSuccessfulInsert(t, &auth_model.WebAuthnCredential{UserID: user.ID}) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Twofa", crudActionCreateFile( t, testCtx, user, "master", "twofa", "signed-twofa.txt", func(t *testing.T, response api.FileResponse) { assert.True(t, response.Verification.Verified) @@ -338,7 +344,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, "user4", "initial-always-no-twofa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( t, testCtx, user, "master", "twofa", "unsigned-twofa.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -351,7 +357,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O setting.Repository.Signing.CRUDActions = []string{"always"} testCtx := NewAPITestContext(t, username, "initial-always-always"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { assert.True(t, response.Verification.Verified) @@ -369,12 +375,13 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O require.NoError(t, err) t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) }) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) assert.Empty(t, branch.Commit.Verification.Signature) - })) + }) }) t.Run("BaseSignedMerging", func(t *testing.T) { @@ -387,12 +394,13 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O require.NoError(t, err) t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) }) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) assert.Empty(t, branch.Commit.Verification.Signature) - })) + }) }) t.Run("CommitsSignedMerging", func(t *testing.T) { @@ -405,11 +413,12 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O require.NoError(t, err) t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) }) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) - })) + }) }) } diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go index aece9c3fd9..a92694d2fa 100644 --- a/tests/integration/ssh_key_test.go +++ b/tests/integration/ssh_key_test.go @@ -55,7 +55,13 @@ func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { keyname := fmt.Sprintf("%s-push", ctx.Reponame) u.Path = ctx.GitPath() - t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, true, objectFormat)) + opts := &api.CreateRepoOption{ + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Template: true, + } + t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, opts, objectFormat)) t.Run("CheckIsEmpty", doCheckRepositoryEmptyStatus(ctx, true)) @@ -105,8 +111,8 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { failCtx := ctx failCtx.ExpectedCode = http.StatusUnprocessableEntity - t.Run("CreateRepository", doAPICreateRepository(ctx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat - t.Run("CreateOtherRepository", doAPICreateRepository(otherCtx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepository", doAPICreateRepository(ctx, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateOtherRepository", doAPICreateRepository(otherCtx, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat withKeyFile(t, keyname, func(keyFile string) { var userKeyPublicKeyID int64 @@ -180,7 +186,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { t.Run("DeleteOtherRepository", doAPIDeleteRepository(otherCtxWithDeleteRepo)) - t.Run("RecreateRepository", doAPICreateRepository(ctxWithDeleteRepo, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("RecreateRepository", doAPICreateRepository(ctxWithDeleteRepo, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat t.Run("CreateUserKey", doAPICreateUserKey(ctx, keyname, keyFile, func(t *testing.T, publicKey api.PublicKey) { userKeyPublicKeyID = publicKey.ID From a300c0b9fd23e9c116c571a4eab17ab6c99376f9 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sat, 28 Jun 2025 18:43:06 +0200 Subject: [PATCH 11/30] chore(cleanup): GitRepo.GetCommit is equivalent to GitRepo.IsBranchExist Since go-git was dropped in a21128a734636c2a18431cfc1742e8a7cf165f58, IsBranchExist relies on git-cat-file to figure out if a commit ID exists. There is no need to fallback to GetCommit if IsBranchExist determines the commit does not exist and it will come to the same conclusion. --- routers/api/v1/repo/pull.go | 19 ++++--------------- routers/web/repo/compare.go | 21 ++++----------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 9360ff1335..cb66b4d09d 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1116,13 +1116,8 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch) baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch) if !baseIsCommit && !baseIsBranch && !baseIsTag { - // Check for short SHA usage - if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(baseBranch); baseCommit != nil { - baseBranch = baseCommit.ID.String() - } else { - ctx.NotFound("BaseNotExist") - return nil, nil, nil, "", "" - } + ctx.NotFound("BaseNotExist") + return nil, nil, nil, "", "" } headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID) @@ -1203,14 +1198,8 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headIsBranch := headGitRepo.IsBranchExist(headBranch) headIsTag := headGitRepo.IsTagExist(headBranch) if !headIsCommit && !headIsBranch && !headIsTag { - // Check if headBranch is short sha commit hash - if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil { - headBranch = headCommit.ID.String() - } else { - headGitRepo.Close() - ctx.NotFound("IsRefExist", nil) - return nil, nil, nil, "", "" - } + ctx.NotFound("IsRefExist", nil) + return nil, nil, nil, "", "" } headBranchRef := headBranch diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index de2e29ab9f..59538d8a0e 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -312,22 +312,16 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch) if !baseIsCommit && !baseIsBranch && !baseIsTag { - // Check if baseBranch is short sha commit hash - if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil { - ci.BaseBranch = baseCommit.ID.String() - ctx.Data["BaseBranch"] = ci.BaseBranch - baseIsCommit = true - } else if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() { + if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() { if isSameRepo { ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch)) } else { ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadRepo.FullName()) + ":" + util.PathEscapeSegments(ci.HeadBranch)) } - return nil } else { ctx.NotFound("IsRefExist", nil) - return nil } + return nil } ctx.Data["BaseIsCommit"] = baseIsCommit ctx.Data["BaseIsBranch"] = baseIsBranch @@ -514,15 +508,8 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { headIsBranch := ci.HeadGitRepo.IsBranchExist(ci.HeadBranch) headIsTag := ci.HeadGitRepo.IsTagExist(ci.HeadBranch) if !headIsCommit && !headIsBranch && !headIsTag { - // Check if headBranch is short sha commit hash - if headCommit, _ := ci.HeadGitRepo.GetCommit(ci.HeadBranch); headCommit != nil { - ci.HeadBranch = headCommit.ID.String() - ctx.Data["HeadBranch"] = ci.HeadBranch - headIsCommit = true - } else { - ctx.NotFound("IsRefExist", nil) - return nil - } + ctx.NotFound("IsRefExist", nil) + return nil } ctx.Data["HeadIsCommit"] = headIsCommit ctx.Data["HeadIsBranch"] = headIsBranch From b8e66a5552a10dd13ef91d6806111bf0d946a93a Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sat, 28 Jun 2025 18:48:37 +0200 Subject: [PATCH 12/30] feat: improve API /repos/{owner}/{repo}/compare/{basehead} error messages --- routers/api/v1/repo/pull.go | 11 ++-- tests/integration/api_repo_compare_test.go | 68 ++++++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index cb66b4d09d..20f718d6c2 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1084,7 +1084,6 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) err error ) - // If there is no head repository, it means pull request between same repository. headInfos := strings.Split(form.Head, ":") if len(headInfos) == 1 { isSameRepo = true @@ -1094,7 +1093,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headUser, err = user_model.GetUserByName(ctx, headInfos[0]) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName") + ctx.NotFound(fmt.Errorf("the owner %s does not exist", headInfos[0])) } else { ctx.Error(http.StatusInternalServerError, "GetUserByName", err) } @@ -1104,7 +1103,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) // The head repository can also point to the same repo isSameRepo = ctx.Repo.Owner.ID == headUser.ID } else { - ctx.NotFound() + ctx.NotFound(fmt.Errorf("the head part of {basehead} %s must contain zero or one colon (:) but contains %d", form.Head, len(headInfos)-1)) return nil, nil, nil, "", "" } @@ -1116,7 +1115,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch) baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch) if !baseIsCommit && !baseIsBranch && !baseIsTag { - ctx.NotFound("BaseNotExist") + ctx.NotFound(fmt.Errorf("could not find '%s' to be a commit, branch or tag in the base repository %s/%s", baseBranch, baseRepo.Owner.Name, baseRepo.Name)) return nil, nil, nil, "", "" } @@ -1130,7 +1129,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID { log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) - ctx.NotFound("GetBaseRepo") + ctx.NotFound(fmt.Errorf("%[1]s does not have a fork of %[2]s/%[3]s and %[2]s/%[3]s is not a fork of a repository from %[1]s", headUser.Name, baseRepo.Owner.Name, baseRepo.Name)) return nil, nil, nil, "", "" } headRepo = baseRepo.BaseRepo @@ -1198,7 +1197,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headIsBranch := headGitRepo.IsBranchExist(headBranch) headIsTag := headGitRepo.IsTagExist(headBranch) if !headIsCommit && !headIsBranch && !headIsTag { - ctx.NotFound("IsRefExist", nil) + ctx.NotFound(fmt.Errorf("could not find '%s' to be a commit, branch or tag in the head repository %s/%s", headBranch, headRepo.Owner.Name, headRepo.Name)) return nil, nil, nil, "", "" } diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index e4e85fc742..1724924fdc 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -7,7 +7,9 @@ import ( "encoding/base64" "fmt" "net/http" + "net/http/httptest" "net/url" + "strings" "testing" "time" @@ -50,6 +52,28 @@ func testAPICompareCommits(t *testing.T, objectFormat git.ObjectFormat) { } } + requireErrorContains := func(t *testing.T, resp *httptest.ResponseRecorder, expected string) { + t.Helper() + + type response struct { + Message string `json:"message"` + Errors []string `json:"errors"` + } + var bodyResp response + DecodeJSON(t, resp, &bodyResp) + + if strings.Contains(bodyResp.Message, expected) { + return + } + for _, error := range bodyResp.Errors { + if strings.Contains(error, expected) { + return + } + } + t.Log(fmt.Sprintf("expected %s in %+v", expected, bodyResp)) + t.Fail() + } + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user2repo := "repoA" user2Ctx := NewAPITestContext(t, user2.Name, user2repo, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) @@ -123,6 +147,14 @@ func testAPICompareCommits(t *testing.T, objectFormat git.ObjectFormat) { user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user4Ctx := NewAPITestContext(t, user4.Name, user2repo, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + t.Run("ForkNotFound", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s:%s", user2.Name, user2repo, "master", user4.Name, user2branchName). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, "user4 does not have a fork of user2/repoA and user2/repoA is not a fork of a repository from user4") + }) + t.Run("User4ForksUser2Repository", doAPIForkRepository(user4Ctx, user2.Name)) user4branchName := "user4branch" t.Run("CreateUser4RepositoryBranch", newBranchAndFile(user4Ctx, user4, user4branchName, "user4branchfilename.txt")) @@ -199,5 +231,41 @@ func testAPICompareCommits(t *testing.T, objectFormat git.ObjectFormat) { assert.Len(t, apiResp.Files, 1) }) } + + t.Run("ForkUserDoesNotExist", func(t *testing.T) { + notUser := "notauser" + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/master...%s:branchname", user2.Name, user2repo, notUser). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, fmt.Sprintf("the owner %s does not exist", notUser)) + }) + + t.Run("HeadHasTooManyColon", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/master...one:two:many", user2.Name, user2repo). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, fmt.Sprintf("must contain zero or one colon (:) but contains 2")) + }) + + for _, testCase := range []struct { + what string + baseHead string + }{ + { + what: "base", + baseHead: "notexists...master", + }, + { + what: "head", + baseHead: "master...notexists", + }, + } { + t.Run("BaseHeadNotExists "+testCase.what, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s", user2.Name, user2repo, testCase.baseHead). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, fmt.Sprintf("could not find 'notexists' to be a commit, branch or tag in the %s", testCase.what)) + }) + } }) } From 66e0988a43bb81031182a83891c2d3efa4c4b2f3 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sat, 28 Jun 2025 20:27:41 +0200 Subject: [PATCH 13/30] feat: make API pull and compare endpoint references to head more robust It is best to prefix the reference to resolve any ambiguity once it has been determined that it is a branch or a tag. It was done in forgejo/forgejo#5991 for the base reference but not for the head reference. Refs forgejo/forgejo#5991 --- routers/api/v1/repo/pull.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 20f718d6c2..e7ff533d6a 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1202,6 +1202,11 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } headBranchRef := headBranch + if headIsBranch { + headBranchRef = git.BranchPrefix + headBranch + } else if headIsTag { + headBranchRef = git.TagPrefix + headBranch + } compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranchRef, headBranchRef, false, false) if err != nil { From b5e608f3e2d42ef3991e7def7e499cf4a1f8b566 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 29 Jun 2025 00:44:18 +0200 Subject: [PATCH 14/30] feat: bump the minimum required Git version from 2.0.0 to 2.34.1 (#8328) - Resolves forgejo/discussions#324 - Remove all checks of `CheckGitVersionAtLeast` that checked for a version below 2.34.1 - The version was chosen because Debian stable supports 2.39.5 and Ubuntu 22.04 LTS supports 2.34.1 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8328 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Reviewed-by: Earl Warren Co-authored-by: Gusted Co-committed-by: Gusted --- .forgejo/workflows/testing-integration.yml | 19 +++++-- cmd/hook.go | 44 ++++++--------- cmd/serv.go | 10 ++-- modules/git/blame.go | 2 +- modules/git/commit.go | 6 +-- modules/git/git.go | 63 +++++++--------------- modules/git/git_test.go | 12 ----- modules/git/pipeline/revlist.go | 20 ------- modules/git/remote.go | 9 +--- modules/git/repo_commit.go | 34 ++---------- modules/git/repo_commitgraph.go | 6 +-- modules/lfs/pointer_scanner.go | 11 +--- routers/private/hook_post_receive.go | 2 +- routers/private/hook_pre_receive.go | 2 +- routers/private/hook_proc_receive.go | 5 -- routers/private/serv.go | 3 +- routers/web/misc/misc.go | 5 -- routers/web/repo/githttp.go | 4 +- services/gitdiff/gitdiff.go | 2 +- services/pull/temp_repo.go | 6 +-- services/repository/files/patch.go | 6 +-- tests/integration/git_test.go | 5 -- tests/integration/repo_signed_tag_test.go | 4 -- tests/integration/signing_git_test.go | 4 -- 24 files changed, 72 insertions(+), 212 deletions(-) diff --git a/.forgejo/workflows/testing-integration.yml b/.forgejo/workflows/testing-integration.yml index 9e5cfb92ed..630de50435 100644 --- a/.forgejo/workflows/testing-integration.yml +++ b/.forgejo/workflows/testing-integration.yml @@ -33,11 +33,20 @@ jobs: steps: - uses: https://data.forgejo.org/actions/checkout@v4 - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye + - name: install git 2.34.1 + run: | + export DEBIAN_FRONTEND=noninteractive + + apt-get update -qq + apt-get -q install -y -qq curl ca-certificates + + curl -sS -o git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb + curl -sS -o git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb + curl -sS -o git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb + + apt-get -q install -y -qq ./git-man.deb + apt-get -q install -y -qq ./git.deb + apt-get -q install -y -qq ./git-lfs.deb - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-backend test-check' diff --git a/cmd/hook.go b/cmd/hook.go index 909cdfdf84..7378dc21ad 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -231,8 +231,6 @@ Forgejo or set your environment appropriately.`, "") } } - supportProcReceive := git.CheckGitVersionAtLeast("2.29") == nil - for scanner.Scan() { // TODO: support news feeds for wiki if isWiki { @@ -250,31 +248,25 @@ Forgejo or set your environment appropriately.`, "") total++ lastline++ - // If the ref is a branch or tag, check if it's protected - // if supportProcReceive all ref should be checked because - // permission check was delayed - if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() { - oldCommitIDs[count] = oldCommitID - newCommitIDs[count] = newCommitID - refFullNames[count] = refFullName - count++ - fmt.Fprint(out, "*") + // All references should be checked because permission check was delayed. + oldCommitIDs[count] = oldCommitID + newCommitIDs[count] = newCommitID + refFullNames[count] = refFullName + count++ + fmt.Fprint(out, "*") - if count >= hookBatchSize { - fmt.Fprintf(out, " Checking %d references\n", count) + if count >= hookBatchSize { + fmt.Fprintf(out, " Checking %d references\n", count) - hookOptions.OldCommitIDs = oldCommitIDs - hookOptions.NewCommitIDs = newCommitIDs - hookOptions.RefFullNames = refFullNames - extra := private.HookPreReceive(ctx, username, reponame, hookOptions) - if extra.HasError() { - return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) - } - count = 0 - lastline = 0 + hookOptions.OldCommitIDs = oldCommitIDs + hookOptions.NewCommitIDs = newCommitIDs + hookOptions.RefFullNames = refFullNames + extra := private.HookPreReceive(ctx, username, reponame, hookOptions) + if extra.HasError() { + return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) } - } else { - fmt.Fprint(out, ".") + count = 0 + lastline = 0 } if lastline >= hookBatchSize { fmt.Fprint(out, "\n") @@ -513,10 +505,6 @@ Forgejo or set your environment appropriately.`, "") return nil } - if git.CheckGitVersionAtLeast("2.29") != nil { - return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.") - } - reader := bufio.NewReader(os.Stdin) repoUser := os.Getenv(repo_module.EnvRepoUsername) repoName := os.Getenv(repo_module.EnvRepoName) diff --git a/cmd/serv.go b/cmd/serv.go index 1fac2d13f5..b0571a276c 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -193,12 +193,10 @@ func runServ(ctx context.Context, c *cli.Command) error { } if len(words) < 2 { - if git.CheckGitVersionAtLeast("2.29") == nil { - // for AGit Flow - if cmd == "ssh_info" { - fmt.Print(`{"type":"agit","version":1}`) - return nil - } + // for AGit Flow + if cmd == "ssh_info" { + fmt.Print(`{"type":"agit","version":1}`) + return nil } return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) } diff --git a/modules/git/blame.go b/modules/git/blame.go index 4ff347e31b..868edab2b8 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -132,7 +132,7 @@ func (r *BlameReader) Close() error { // CreateBlameReader creates reader for given repository, commit and file func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { var ignoreRevsFile *string - if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { + if !bypassBlameIgnore { ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) } diff --git a/modules/git/commit.go b/modules/git/commit.go index 96831e3ae4..1228b4523b 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -412,11 +412,7 @@ func (c *Commit) GetSubModule(entryname string) (string, error) { // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') func (c *Commit) GetBranchName() (string, error) { - cmd := NewCommand(c.repo.Ctx, "name-rev") - if CheckGitVersionAtLeast("2.13.0") == nil { - cmd.AddArguments("--exclude", "refs/tags/*") - } - cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) + cmd := NewCommand(c.repo.Ctx, "name-rev", "--exclude", "refs/tags/*", "--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) data, _, err := cmd.RunStdString(&RunOpts{Dir: c.repo.Path}) if err != nil { // handle special case where git can not describe commit diff --git a/modules/git/git.go b/modules/git/git.go index 1dfd0b5134..851b090b53 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -23,7 +23,7 @@ import ( ) // RequiredVersion is the minimum Git version required -const RequiredVersion = "2.0.0" +const RequiredVersion = "2.34.1" var ( // GitExecutable is the command name of git @@ -33,7 +33,6 @@ var ( // DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx DefaultContext context.Context - SupportProcReceive bool // >= 2.29 SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’ InvertedGitFlushEnv bool // 2.43.1 SupportCheckAttrOnBare bool // >= 2.40 @@ -113,7 +112,7 @@ func VersionInfo() string { format := "%s" args := []any{GitVersion.Original()} // Since git wire protocol has been released from git v2.18 - if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { + if setting.Git.EnableAutoGitWireProtocol { format += ", Wire Protocol %s Enabled" args = append(args, "Version 2") // for focus color } @@ -172,16 +171,13 @@ func InitFull(ctx context.Context) (err error) { _ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) } - // Since git wire protocol has been released from git v2.18 - if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { + if setting.Git.EnableAutoGitWireProtocol { globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") } // Explicitly disable credential helper, otherwise Git credentials might leak - if CheckGitVersionAtLeast("2.9") == nil { - globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") - } - SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil + globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") + SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil SupportCheckAttrOnBare = CheckGitVersionAtLeast("2.40") == nil if SupportHashSha256 { @@ -195,9 +191,6 @@ func InitFull(ctx context.Context) (err error) { SupportGrepMaxCount = CheckGitVersionAtLeast("2.38") == nil if setting.LFS.StartServer { - if CheckGitVersionAtLeast("2.1.2") != nil { - return errors.New("LFS server support requires Git >= 2.1.2") - } globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") } @@ -234,38 +227,28 @@ func syncGitConfig() (err error) { } } - // Set git some configurations - these must be set to these values for gitea to work correctly + // Set git some configurations - these must be set to these values for forgejo to work correctly if err := configSet("core.quotePath", "false"); err != nil { return err } - if CheckGitVersionAtLeast("2.10") == nil { - if err := configSet("receive.advertisePushOptions", "true"); err != nil { - return err - } + if err := configSet("receive.advertisePushOptions", "true"); err != nil { + return err } - if CheckGitVersionAtLeast("2.18") == nil { - if err := configSet("core.commitGraph", "true"); err != nil { - return err - } - if err := configSet("gc.writeCommitGraph", "true"); err != nil { - return err - } - if err := configSet("fetch.writeCommitGraph", "true"); err != nil { - return err - } + if err := configSet("core.commitGraph", "true"); err != nil { + return err + } + if err := configSet("gc.writeCommitGraph", "true"); err != nil { + return err + } + if err := configSet("fetch.writeCommitGraph", "true"); err != nil { + return err } - if SupportProcReceive { - // set support for AGit flow - if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } - } else { - if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } + // set support for AGit flow + if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { + return err } // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user @@ -284,11 +267,6 @@ func syncGitConfig() (err error) { switch setting.Repository.Signing.Format { case "ssh": - // First do a git version check. - if CheckGitVersionAtLeast("2.34.0") != nil { - return errors.New("ssh signing requires Git >= 2.34.0") - } - // Get the ssh-keygen binary that Git will use. // This can be overridden in app.ini in [git.config] section, so we must // query this information. @@ -325,8 +303,7 @@ func syncGitConfig() (err error) { } } - // By default partial clones are disabled, enable them from git v2.22 - if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { + if !setting.Git.DisablePartialClone { if err = configSet("uploadpack.allowfilter", "true"); err != nil { return err } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 01200dba68..38d4db169c 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -14,7 +14,6 @@ import ( "forgejo.org/modules/test" "forgejo.org/modules/util" - "github.com/hashicorp/go-version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -105,10 +104,6 @@ func TestSyncConfigGPGFormat(t *testing.T) { }) t.Run("SSH format", func(t *testing.T) { - if CheckGitVersionAtLeast("2.34.0") != nil { - t.SkipNow() - } - r, err := os.OpenRoot(t.TempDir()) require.NoError(t, err) f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700) @@ -121,13 +116,6 @@ func TestSyncConfigGPGFormat(t *testing.T) { assert.True(t, gitConfigContains("[gpg]")) assert.True(t, gitConfigContains("format = ssh")) - t.Run("Old version", func(t *testing.T) { - oldVersion, err := version.NewVersion("2.33.0") - require.NoError(t, err) - defer test.MockVariableValue(&GitVersion, oldVersion)() - require.ErrorContains(t, syncGitConfig(), "ssh signing requires Git >= 2.34.0") - }) - t.Run("No ssh-keygen binary", func(t *testing.T) { require.NoError(t, r.Remove("ssh-keygen")) require.ErrorContains(t, syncGitConfig(), "git signing requires a ssh-keygen binary") diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go index f39b7113bb..1ee8921854 100644 --- a/modules/git/pipeline/revlist.go +++ b/modules/git/pipeline/revlist.go @@ -16,26 +16,6 @@ import ( "forgejo.org/modules/log" ) -// RevListAllObjects runs rev-list --objects --all and writes to a pipewriter -func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, basePath string, errChan chan<- error) { - defer wg.Done() - defer revListWriter.Close() - - stderr := new(bytes.Buffer) - var errbuf strings.Builder - cmd := git.NewCommand(ctx, "rev-list", "--objects", "--all") - if err := cmd.Run(&git.RunOpts{ - Dir: basePath, - Stdout: revListWriter, - Stderr: stderr, - }); err != nil { - log.Error("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String()) - err = fmt.Errorf("git rev-list --objects --all [%s]: %w - %s", basePath, err, errbuf.String()) - _ = revListWriter.CloseWithError(err) - errChan <- err - } -} - // RevListObjects run rev-list --objects from headSHA to baseSHA func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath, headSHA, baseSHA string, errChan chan<- error) { defer wg.Done() diff --git a/modules/git/remote.go b/modules/git/remote.go index fb66d76ff0..83a02fe2be 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -12,14 +12,7 @@ import ( // GetRemoteAddress returns remote url of git repository in the repoPath with special remote name func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) { - var cmd *Command - if CheckGitVersionAtLeast("2.7") == nil { - cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName) - } else { - cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url") - } - - result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + result, _, err := NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName).RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return "", err } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 4c8516f828..41ca0e39b1 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -443,42 +443,18 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit, } func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { - if CheckGitVersionAtLeast("2.7.0") == nil { - command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix) + command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix) - if limit != -1 { - command = command.AddOptionFormat("--count=%d", limit) - } - - stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path}) - if err != nil { - return nil, err - } - - branches := strings.Fields(stdout) - return branches, nil + if limit != -1 { + command = command.AddOptionFormat("--count=%d", limit) } - stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } - refs := strings.Split(stdout, "\n") - - var max int - if len(refs) > limit { - max = limit - } else { - max = len(refs) - 1 - } - - branches := make([]string, max) - for i, ref := range refs[:max] { - parts := strings.Fields(ref) - - branches[i] = parts[len(parts)-1] - } + branches := strings.Fields(stdout) return branches, nil } diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go index 492438be37..c3647bd894 100644 --- a/modules/git/repo_commitgraph.go +++ b/modules/git/repo_commitgraph.go @@ -11,10 +11,8 @@ import ( // WriteCommitGraph write commit graph to speed up repo access // this requires git v2.18 to be installed func WriteCommitGraph(ctx context.Context, repoPath string) error { - if CheckGitVersionAtLeast("2.18") == nil { - if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { - return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) - } + if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { + return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) } return nil } diff --git a/modules/lfs/pointer_scanner.go b/modules/lfs/pointer_scanner.go index 632ecd19ae..80da8e5222 100644 --- a/modules/lfs/pointer_scanner.go +++ b/modules/lfs/pointer_scanner.go @@ -39,16 +39,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg) // 1. Run batch-check on all objects in the repository - if git.CheckGitVersionAtLeast("2.6.0") != nil { - revListReader, revListWriter := io.Pipe() - shasToCheckReader, shasToCheckWriter := io.Pipe() - wg.Add(2) - go pipeline.CatFileBatchCheck(ctx, shasToCheckReader, catFileCheckWriter, &wg, basePath) - go pipeline.BlobsFromRevListObjects(revListReader, shasToCheckWriter, &wg) - go pipeline.RevListAllObjects(ctx, revListWriter, &wg, basePath, errChan) - } else { - go pipeline.CatFileBatchCheckAllObjects(ctx, catFileCheckWriter, &wg, basePath, errChan) - } + go pipeline.CatFileBatchCheckAllObjects(ctx, catFileCheckWriter, &wg, basePath, errChan) wg.Wait() close(pointerChan) diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index c7748b01c8..a856a7a00a 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -205,7 +205,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { // post update for agit pull request // FIXME: use pr.Flow to test whether it's an Agit PR or a GH PR - if git.SupportProcReceive && refFullName.IsPull() { + if refFullName.IsPull() { if repo == nil { repo = loadRepository(ctx, ownerName, repoName) if ctx.Written() { diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 4c0e9a8551..45992e8522 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -205,7 +205,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName) case refFullName.IsTag(): preReceiveTag(ourCtx, oldCommitID, newCommitID, refFullName) - case git.SupportProcReceive && refFullName.IsFor(): + case refFullName.IsFor(): preReceiveFor(ourCtx, oldCommitID, newCommitID, refFullName) default: if ourCtx.isOverQuota { diff --git a/routers/private/hook_proc_receive.go b/routers/private/hook_proc_receive.go index cd45794261..9f6e23f158 100644 --- a/routers/private/hook_proc_receive.go +++ b/routers/private/hook_proc_receive.go @@ -7,7 +7,6 @@ import ( "net/http" repo_model "forgejo.org/models/repo" - "forgejo.org/modules/git" "forgejo.org/modules/log" "forgejo.org/modules/private" "forgejo.org/modules/web" @@ -18,10 +17,6 @@ import ( // HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present func HookProcReceive(ctx *gitea_context.PrivateContext) { opts := web.GetForm(ctx).(*private.HookOptions) - if !git.SupportProcReceive { - ctx.Status(http.StatusNotFound) - return - } results, err := agit.ProcReceive(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, opts) if err != nil { diff --git a/routers/private/serv.go b/routers/private/serv.go index 4c5b7bbccb..a4029e354c 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -14,7 +14,6 @@ import ( repo_model "forgejo.org/models/repo" "forgejo.org/models/unit" user_model "forgejo.org/models/user" - "forgejo.org/modules/git" "forgejo.org/modules/log" "forgejo.org/modules/private" "forgejo.org/modules/setting" @@ -303,7 +302,7 @@ func ServCommand(ctx *context.PrivateContext) { // the permission check to read. The pre-receive hook will do another // permission check which ensure for non AGit flow references the write // permission is checked. - if git.SupportProcReceive && unitType == unit.TypeCode && ctx.FormString("verb") == "git-receive-pack" { + if unitType == unit.TypeCode && ctx.FormString("verb") == "git-receive-pack" { mode = perm.AccessModeRead } diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go index 87b5247599..22fdccf79f 100644 --- a/routers/web/misc/misc.go +++ b/routers/web/misc/misc.go @@ -7,7 +7,6 @@ import ( "net/http" "path" - "forgejo.org/modules/git" "forgejo.org/modules/httpcache" "forgejo.org/modules/log" "forgejo.org/modules/setting" @@ -15,10 +14,6 @@ import ( ) func SSHInfo(rw http.ResponseWriter, req *http.Request) { - if !git.SupportProcReceive { - rw.WriteHeader(http.StatusNotFound) - return - } rw.Header().Set("content-type", "text/json;charset=UTF-8") _, err := rw.Write([]byte(`{"type":"agit","version":1}`)) if err != nil { diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 650b1d88f4..42302d0e02 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -183,9 +183,7 @@ func httpBase(ctx *context.Context) *serviceHandler { if repoExist { // Because of special ref "refs/for" .. , need delay write permission check - if git.SupportProcReceive { - accessMode = perm.AccessModeRead - } + accessMode = perm.AccessModeRead if ctx.Data["IsActionsToken"] == true { taskID := ctx.Data["ActionsTaskID"].(int64) diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 6835dfbf36..7033264f18 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1157,7 +1157,7 @@ func GetDiffSimple(ctx context.Context, gitRepo *git.Repository, opts *DiffOptio // so if we are using at least this version of git we don't have to tell ParsePatch to do // the skipping for us parsePatchSkipToFile := opts.SkipTo - if opts.SkipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil { + if opts.SkipTo != "" { cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo) parsePatchSkipToFile = "" } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 1805ffc527..76ae0df018 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -103,11 +103,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) remoteRepoName := "head_repo" baseBranch := "base" - fetchArgs := git.TrustedCmdArgs{"--no-tags"} - if git.CheckGitVersionAtLeast("2.25.0") == nil { - // Writing the commit graph can be slow and is not needed here - fetchArgs = append(fetchArgs, "--no-write-commit-graph") - } + fetchArgs := git.TrustedCmdArgs{"--no-tags", "--no-write-commit-graph"} // addCacheRepo adds git alternatives for the cacheRepoPath in the repoPath addCacheRepo := func(repoPath, cacheRepoPath string) error { diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 5b1dd65b5a..18b5226c02 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -147,11 +147,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user stdout := &strings.Builder{} stderr := &strings.Builder{} - cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") - if git.CheckGitVersionAtLeast("2.32") == nil { - cmdApply.AddArguments("-3") - } - + cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary", "-3") if err := cmdApply.Run(&git.RunOpts{ Dir: t.basePath, Stdout: stdout, diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index 9a66781024..26cddf7288 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -771,11 +771,6 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string return func(t *testing.T) { defer tests.PrintCurrentTest(t)() - // skip this test if git version is low - if git.CheckGitVersionAtLeast("2.29") != nil { - return - } - gitRepo, err := git.OpenRepository(git.DefaultContext, dstPath) require.NoError(t, err) diff --git a/tests/integration/repo_signed_tag_test.go b/tests/integration/repo_signed_tag_test.go index 686690bd19..16d8841304 100644 --- a/tests/integration/repo_signed_tag_test.go +++ b/tests/integration/repo_signed_tag_test.go @@ -25,10 +25,6 @@ import ( ) func TestRepoSSHSignedTags(t *testing.T) { - if git.CheckGitVersionAtLeast("2.34") != nil { - t.Skip("Skipping, does not support SSH signing") - return - } defer tests.PrepareTestEnv(t)() // Preparations diff --git a/tests/integration/signing_git_test.go b/tests/integration/signing_git_test.go index e4c0d6049b..8b6b30ecab 100644 --- a/tests/integration/signing_git_test.go +++ b/tests/integration/signing_git_test.go @@ -42,10 +42,6 @@ func TestInstanceSigning(t *testing.T) { defer test.MockProtect(&setting.Repository.Signing.CRUDActions)() t.Run("SSH", func(t *testing.T) { - if git.CheckGitVersionAtLeast("2.34") != nil { - t.Skip("Skipping, does not support git SSH signing") - return - } defer tests.PrintCurrentTest(t)() pubKeyContent, err := os.ReadFile("tests/integration/ssh-signing-key.pub") From 14309837d41851ab0a33be3e6ebb928e0028cd95 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 02:52:27 +0200 Subject: [PATCH 15/30] Update module github.com/niklasfasching/go-org to v1.9.0 (forgejo) (#8335) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8335 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 510ec9c3ae..33ad5dbc67 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( github.com/minio/minio-go/v7 v7.0.94 github.com/msteinert/pam/v2 v2.1.0 github.com/nektos/act v0.2.52 - github.com/niklasfasching/go-org v1.8.0 + github.com/niklasfasching/go-org v1.9.0 github.com/olivere/elastic/v7 v7.0.32 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 diff --git a/go.sum b/go.sum index 53558fddd7..45497a1b49 100644 --- a/go.sum +++ b/go.sum @@ -426,8 +426,8 @@ github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidG github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc= 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/niklasfasching/go-org v1.8.0 h1:WyGLaajLLp8JbQzkmapZ1y0MOzKuKV47HkZRloi+HGY= -github.com/niklasfasching/go-org v1.8.0/go.mod h1:e2A9zJs7cdONrEGs3gvxCcaAEpwwPNPG7csDpXckMNg= +github.com/niklasfasching/go-org v1.9.0 h1:4/Sr68Qx06hjC9MVDB/4etGP67JionLHGscLMOClpnk= +github.com/niklasfasching/go-org v1.9.0/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= From b6c6981c300b632c8cad1d77bae2184e68aa138a Mon Sep 17 00:00:00 2001 From: Mathieu Fenniak Date: Sun, 29 Jun 2025 05:54:07 +0200 Subject: [PATCH 16/30] feat(ui): add repository description to og:image:alt (#8325) Followup to https://codeberg.org/forgejo/forgejo/pulls/6053 Adds the repository description to the "alt" tag of the OpenGraph summary card, improving accessibility when these images are displayed. Fixes #8192. Other summary cards, for issues and releases, are not modified as they already contain the issue title or release title, which seems reasonable. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8325 Reviewed-by: Gusted Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Mathieu Fenniak Co-committed-by: Mathieu Fenniak --- options/locale_next/locale_en-US.json | 1 + services/context/repo.go | 6 +++++- tests/integration/opengraph_test.go | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index b1c98e4551..a551db87dc 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -102,5 +102,6 @@ "admin.dashboard.cleanup_offline_runners": "Cleanup offline runners", "settings.visibility.description": "Profile visibility affects others' ability to access your non-private repositories. Learn more", "avatar.constraints_hint": "Custom avatar may not exceed %[1]s in size or be larger than %[2]dx%[3]d pixels", + "og.repo.summary_card.alt_description": "Summary card of repository %[1]s, described as: %[2]s", "meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it." } diff --git a/services/context/repo.go b/services/context/repo.go index cce3a5fa70..c8876d7166 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -644,7 +644,11 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["OpenGraphImageURL"] = repo.SummaryCardURL() ctx.Data["OpenGraphImageWidth"] = cardWidth ctx.Data["OpenGraphImageHeight"] = cardHeight - ctx.Data["OpenGraphImageAltText"] = ctx.Tr("repo.summary_card_alt", repo.FullName()) + if util.IsEmptyString(repo.Description) { + ctx.Data["OpenGraphImageAltText"] = ctx.Tr("repo.summary_card_alt", repo.FullName()) + } else { + ctx.Data["OpenGraphImageAltText"] = ctx.Tr("og.repo.summary_card.alt_description", repo.FullName(), repo.Description) + } if repo.IsFork { RetrieveBaseRepo(ctx, repo) diff --git a/tests/integration/opengraph_test.go b/tests/integration/opengraph_test.go index 56fbedd351..aa6d8daf5c 100644 --- a/tests/integration/opengraph_test.go +++ b/tests/integration/opengraph_test.go @@ -98,7 +98,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:url": setting.AppURL + "/user27/repo49/src/branch/master/test/test.txt", "og:type": "object", "og:image": setting.AppURL + "user27/repo49/-/summary-card", - "og:image:alt": "Summary card of repository user27/repo49", + "og:image:alt": "Summary card of repository user27/repo49, described as: A wonderful repository with more than just a README.md", "og:image:width": "1200", "og:image:height": "600", "og:site_name": siteName, @@ -141,7 +141,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:description": "A wonderful repository with more than just a README.md", "og:type": "object", "og:image": setting.AppURL + "user27/repo49/-/summary-card", - "og:image:alt": "Summary card of repository user27/repo49", + "og:image:alt": "Summary card of repository user27/repo49, described as: A wonderful repository with more than just a README.md", "og:image:width": "1200", "og:image:height": "600", "og:site_name": siteName, From 84ed8aa740057ce9d399c9a79c36f50c33484cbe Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 29 Jun 2025 08:06:38 +0200 Subject: [PATCH 17/30] chore: use standard library function (#8334) - As mentioned in the comment, use the standard library function now its available. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8334 Reviewed-by: Earl Warren Co-authored-by: Gusted Co-committed-by: Gusted --- modules/git/repo_attribute_test.go | 32 +----------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index c69382e245..3d2c845fa0 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -5,7 +5,6 @@ package git import ( "context" - "fmt" "io" "io/fs" "os" @@ -197,7 +196,7 @@ func TestGitAttributeCheckerError(t *testing.T) { path := t.TempDir() // we can't use unittest.CopyDir because of an import cycle (git.Init in unittest) - require.NoError(t, CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo")))) + require.NoError(t, os.CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo")))) gitRepo, err := openRepositoryWithDefaultContext(path) require.NoError(t, err) @@ -324,32 +323,3 @@ func TestGitAttributeCheckerError(t *testing.T) { require.ErrorIs(t, err, fs.ErrClosed) }) } - -// CopyFS is adapted from https://github.com/golang/go/issues/62484 -// which should be available with go1.23 -func CopyFS(dir string, fsys fs.FS) error { - return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, _ error) error { - targ := filepath.Join(dir, filepath.FromSlash(path)) - if d.IsDir() { - return os.MkdirAll(targ, 0o777) - } - r, err := fsys.Open(path) - if err != nil { - return err - } - defer r.Close() - info, err := r.Stat() - if err != nil { - return err - } - w, err := os.OpenFile(targ, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o666|info.Mode()&0o777) - if err != nil { - return err - } - if _, err := io.Copy(w, r); err != nil { - w.Close() - return fmt.Errorf("copying %s: %v", path, err) - } - return w.Close() - }) -} From 33217a36332f9af270aea0366707083a63f6a525 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 09:37:43 +0200 Subject: [PATCH 18/30] Update dependency @stylistic/stylelint-plugin to v3.1.3 (forgejo) (#8336) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 604ff38c18..8ddc99ff5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin": "4.4.1", - "@stylistic/stylelint-plugin": "3.1.2", + "@stylistic/stylelint-plugin": "3.1.3", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", @@ -3089,9 +3089,9 @@ } }, "node_modules/@stylistic/stylelint-plugin": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.2.tgz", - "integrity": "sha512-tylFJGMQo62alGazK74MNxFjMagYOHmBZiePZFOJK2n13JZta0uVkB3Bh5qodUmOLtRH+uxH297EibK14UKm8g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.3.tgz", + "integrity": "sha512-85fsmzgsIVmyG3/GFrjuYj6Cz8rAM7IZiPiXCMiSMfoDOC1lOrzrXPDk24WqviAghnPqGpx8b0caK2PuewWGFg==", "dev": true, "license": "MIT", "dependencies": { @@ -3099,10 +3099,10 @@ "@csstools/css-tokenizer": "^3.0.1", "@csstools/media-query-list-parser": "^3.0.1", "is-plain-object": "^5.0.0", + "postcss": "^8.4.41", "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0", - "style-search": "^0.1.0", - "stylelint": "^16.8.2" + "style-search": "^0.1.0" }, "engines": { "node": "^18.12 || >=20.9" diff --git a/package.json b/package.json index 3d71e94cd3..90023d762e 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin": "4.4.1", - "@stylistic/stylelint-plugin": "3.1.2", + "@stylistic/stylelint-plugin": "3.1.3", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", From ad1adabcbb72e81e513e4a9232a74fe901e78aa8 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 09:37:44 +0200 Subject: [PATCH 19/30] Update linters (forgejo) (#8338) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 414 ++++++++++++++++++++++------------------------ package.json | 12 +- 2 files changed, 207 insertions(+), 219 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ddc99ff5d..85d4401889 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,15 +71,15 @@ "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", - "eslint": "9.28.0", - "eslint-import-resolver-typescript": "4.4.3", + "eslint": "9.30.0", + "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-array-func": "5.0.2", - "eslint-plugin-import-x": "4.15.1", + "eslint-plugin-import-x": "4.16.1", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.7.2", "eslint-plugin-playwright": "2.2.0", "eslint-plugin-regexp": "2.9.0", - "eslint-plugin-sonarjs": "3.0.2", + "eslint-plugin-sonarjs": "3.0.4", "eslint-plugin-toml": "0.12.0", "eslint-plugin-unicorn": "59.0.1", "eslint-plugin-vitest-globals": "1.5.0", @@ -92,13 +92,13 @@ "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", "sharp": "0.34.2", - "stylelint": "16.20.0", + "stylelint": "16.21.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "typescript": "5.8.3", - "typescript-eslint": "8.34.0", + "typescript-eslint": "8.35.0", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, @@ -1021,9 +1021,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1049,9 +1049,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1146,9 +1146,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", + "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", "dev": true, "license": "MIT", "engines": { @@ -3561,17 +3561,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", - "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", + "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/type-utils": "8.34.0", - "@typescript-eslint/utils": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/type-utils": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3585,7 +3585,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.34.0", + "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3601,16 +3601,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", - "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", + "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3626,14 +3626,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", - "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.0", - "@typescript-eslint/types": "^8.34.0", + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3648,14 +3648,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", - "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0" + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3666,9 +3666,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", - "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", "dev": true, "license": "MIT", "engines": { @@ -3683,14 +3683,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", - "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", + "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/utils": "8.35.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3707,9 +3707,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", - "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", "dev": true, "license": "MIT", "engines": { @@ -3721,16 +3721,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", - "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.0", - "@typescript-eslint/tsconfig-utils": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3783,16 +3783,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0" + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3807,14 +3807,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", - "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.35.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3825,9 +3825,9 @@ } }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.1.tgz", - "integrity": "sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", + "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", "cpu": [ "arm" ], @@ -3839,9 +3839,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.1.tgz", - "integrity": "sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", + "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", "cpu": [ "arm64" ], @@ -3853,9 +3853,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.1.tgz", - "integrity": "sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", "cpu": [ "arm64" ], @@ -3867,9 +3867,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.1.tgz", - "integrity": "sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", + "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", "cpu": [ "x64" ], @@ -3881,9 +3881,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.1.tgz", - "integrity": "sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", + "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", "cpu": [ "x64" ], @@ -3895,9 +3895,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.1.tgz", - "integrity": "sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", "cpu": [ "arm" ], @@ -3909,9 +3909,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.1.tgz", - "integrity": "sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", + "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", "cpu": [ "arm" ], @@ -3923,9 +3923,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.1.tgz", - "integrity": "sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", "cpu": [ "arm64" ], @@ -3937,9 +3937,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.1.tgz", - "integrity": "sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", "cpu": [ "arm64" ], @@ -3951,9 +3951,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.1.tgz", - "integrity": "sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", + "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", "cpu": [ "ppc64" ], @@ -3965,9 +3965,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.1.tgz", - "integrity": "sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", + "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", "cpu": [ "riscv64" ], @@ -3979,9 +3979,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.1.tgz", - "integrity": "sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", + "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", "cpu": [ "riscv64" ], @@ -3993,9 +3993,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.1.tgz", - "integrity": "sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", + "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", "cpu": [ "s390x" ], @@ -4007,9 +4007,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.1.tgz", - "integrity": "sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", "cpu": [ "x64" ], @@ -4021,9 +4021,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.1.tgz", - "integrity": "sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", "cpu": [ "x64" ], @@ -4035,9 +4035,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.1.tgz", - "integrity": "sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", + "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", "cpu": [ "wasm32" ], @@ -4052,9 +4052,9 @@ } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.1.tgz", - "integrity": "sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", "cpu": [ "arm64" ], @@ -4066,9 +4066,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.1.tgz", - "integrity": "sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", "cpu": [ "ia32" ], @@ -4080,9 +4080,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.1.tgz", - "integrity": "sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", "cpu": [ "x64" ], @@ -7262,19 +7262,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", + "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.30.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -7286,9 +7286,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -7339,14 +7339,14 @@ } }, "node_modules/eslint-import-context": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz", - "integrity": "sha512-bq+F7nyc65sKpZGT09dY0S0QrOnQtuDVIfyTGQ8uuvtMIF7oHp6CEP3mouN0rrnYF3Jqo6Ke0BfU/5wASZue1w==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", "dev": true, "license": "MIT", "dependencies": { "get-tsconfig": "^4.10.1", - "stable-hash-x": "^0.1.1" + "stable-hash-x": "^0.2.0" }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" @@ -7364,9 +7364,9 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.3.tgz", - "integrity": "sha512-elVDn1eWKFrWlzxlWl9xMt8LltjKl161Ix50JFC50tHXI5/TRP32SNEqlJ/bo/HV+g7Rou/tlPQU2AcRtIhrOg==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", "dev": true, "license": "ISC", "dependencies": { @@ -7374,7 +7374,7 @@ "eslint-import-context": "^0.1.8", "get-tsconfig": "^4.10.1", "is-bun-module": "^2.0.0", - "stable-hash-x": "^0.1.1", + "stable-hash-x": "^0.2.0", "tinyglobby": "^0.2.14", "unrs-resolver": "^1.7.11" }, @@ -7412,21 +7412,21 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.15.1.tgz", - "integrity": "sha512-JfVpNg1qMkPD66iaSgmMoSYeUCGS8UFSm3GwHV0IbuV3Knar/SyK5qqCct9+AxoMIzaM+KSO7KK5pOeOkC/3GQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", + "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", - "eslint-import-context": "^0.1.7", + "eslint-import-context": "^0.1.9", "is-glob": "^4.0.3", "minimatch": "^9.0.3 || ^10.0.1", "semver": "^7.7.2", - "stable-hash-x": "^0.1.1", - "unrs-resolver": "^1.7.10" + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7535,9 +7535,9 @@ } }, "node_modules/eslint-plugin-sonarjs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.2.tgz", - "integrity": "sha512-LxjbfwI7ypENeTmGyKmDyNux3COSkMi7H/6Cal5StSLQ6edf0naP45SZR43OclaNR7WfhVTZdhOn63q3/Y6puQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.4.tgz", + "integrity": "sha512-ftQcP811kRJNXapqpQXHErEoVOdTPfYPPYd7n3AExIPwv4qWKKHf4slFvXmodiOnfgy1Tl3waPZZLD7lcvJOtw==", "dev": true, "license": "LGPL-3.0-only", "dependencies": { @@ -7546,10 +7546,11 @@ "bytes": "3.1.2", "functional-red-black-tree": "1.0.1", "jsx-ast-utils": "3.3.5", + "lodash.merge": "4.6.2", "minimatch": "9.0.5", "scslre": "0.3.0", - "semver": "7.7.1", - "typescript": "^5" + "semver": "7.7.2", + "typescript": ">=5" }, "peerDependencies": { "eslint": "^8.0.0 || ^9.0.0" @@ -7588,19 +7589,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-sonarjs/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-toml": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/eslint-plugin-toml/-/eslint-plugin-toml-0.12.0.tgz", @@ -9993,9 +9981,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz", - "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", "dev": true, "license": "MIT" }, @@ -13590,9 +13578,9 @@ } }, "node_modules/stable-hash-x": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.1.1.tgz", - "integrity": "sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", "dev": true, "license": "MIT", "engines": { @@ -13801,9 +13789,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.20.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.20.0.tgz", - "integrity": "sha512-B5Myu9WRxrgKuLs3YyUXLP2H0mrbejwNxPmyADlACWwFsrL8Bmor/nTSh4OMae5sHjOz6gkSeccQH34gM4/nAw==", + "version": "16.21.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.21.0.tgz", + "integrity": "sha512-ki3PpJGG7xhm3WtINoWGnlvqAmbqSexoRMbEMJzlwewSIOqPRKPlq452c22xAdEJISVi80r+I7KL9GPUiwFgbg==", "dev": true, "funding": [ { @@ -13817,9 +13805,9 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3", "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", @@ -13830,21 +13818,21 @@ "debug": "^4.4.1", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^10.1.0", + "file-entry-cache": "^10.1.1", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^7.0.4", + "ignore": "^7.0.5", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.36.0", + "known-css-properties": "^0.37.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.5.3", + "postcss": "^8.5.5", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.0", @@ -14872,15 +14860,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", - "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz", + "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.34.0", - "@typescript-eslint/parser": "8.34.0", - "@typescript-eslint/utils": "8.34.0" + "@typescript-eslint/eslint-plugin": "8.35.0", + "@typescript-eslint/parser": "8.35.0", + "@typescript-eslint/utils": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -14955,38 +14943,38 @@ } }, "node_modules/unrs-resolver": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.1.tgz", - "integrity": "sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", + "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.2.2" + "napi-postinstall": "^0.2.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.1", - "@unrs/resolver-binding-android-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-x64": "1.9.1", - "@unrs/resolver-binding-freebsd-x64": "1.9.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-musl": "1.9.1", - "@unrs/resolver-binding-wasm32-wasi": "1.9.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.1" + "@unrs/resolver-binding-android-arm-eabi": "1.9.2", + "@unrs/resolver-binding-android-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-x64": "1.9.2", + "@unrs/resolver-binding-freebsd-x64": "1.9.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-musl": "1.9.2", + "@unrs/resolver-binding-wasm32-wasi": "1.9.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.2" } }, "node_modules/update-browserslist-db": { diff --git a/package.json b/package.json index 90023d762e..c9afea9a99 100644 --- a/package.json +++ b/package.json @@ -70,15 +70,15 @@ "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", - "eslint": "9.28.0", - "eslint-import-resolver-typescript": "4.4.3", + "eslint": "9.30.0", + "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-array-func": "5.0.2", - "eslint-plugin-import-x": "4.15.1", + "eslint-plugin-import-x": "4.16.1", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.7.2", "eslint-plugin-playwright": "2.2.0", "eslint-plugin-regexp": "2.9.0", - "eslint-plugin-sonarjs": "3.0.2", + "eslint-plugin-sonarjs": "3.0.4", "eslint-plugin-toml": "0.12.0", "eslint-plugin-unicorn": "59.0.1", "eslint-plugin-vitest-globals": "1.5.0", @@ -91,13 +91,13 @@ "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", "sharp": "0.34.2", - "stylelint": "16.20.0", + "stylelint": "16.21.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "typescript": "5.8.3", - "typescript-eslint": "8.34.0", + "typescript-eslint": "8.35.0", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, From 7a881e2f2648b724a905a0f61e694055ec667e24 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 10:59:35 +0200 Subject: [PATCH 20/30] Update dependency happy-dom to v18.0.1 (forgejo) (#8337) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85d4401889..076882bb07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,7 +87,7 @@ "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "3.0.1", "globals": "16.1.0", - "happy-dom": "18.0.0", + "happy-dom": "18.0.1", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", @@ -8551,9 +8551,9 @@ } }, "node_modules/happy-dom": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.0.tgz", - "integrity": "sha512-o3p2Axi1EdIfMaOUulDzO/5yXzLLV0g/54eLPVrkt3u20r3yOuOenHpyp2clAJ0eHMc+HyE139ulQxl+8pEJIw==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz", + "integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c9afea9a99..c88721a647 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "3.0.1", "globals": "16.1.0", - "happy-dom": "18.0.0", + "happy-dom": "18.0.1", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", From 3feceb10c79974e74bc078a7292c836dbb883c57 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 11:40:47 +0200 Subject: [PATCH 21/30] Update dependency @stylistic/eslint-plugin to v5 (forgejo) (#8340) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 076882bb07..63bb2d4cf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", - "@stylistic/eslint-plugin": "4.4.1", + "@stylistic/eslint-plugin": "5.0.0", "@stylistic/stylelint-plugin": "3.1.3", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", @@ -3056,15 +3056,16 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", - "integrity": "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.0.0.tgz", + "integrity": "sha512-nVV2FSzeTJ3oFKw+3t9gQYQcrgbopgCASSY27QOtkhEGgSfdQQjDmzZd41NeT1myQ8Wc6l+pZllST9qIu4NKzg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.32.1", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/types": "^8.34.1", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.2" }, diff --git a/package.json b/package.json index c88721a647..258a041451 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", - "@stylistic/eslint-plugin": "4.4.1", + "@stylistic/eslint-plugin": "5.0.0", "@stylistic/stylelint-plugin": "3.1.3", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", From 216074122163ce5161be78a1ad747c0c47f357c0 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 11:50:41 +0200 Subject: [PATCH 22/30] Update dependency @vitejs/plugin-vue to v6 (forgejo) (#8341) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 22 ++++++++++++++++------ package.json | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63bb2d4cf6..b8d258e28f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin": "5.0.0", "@stylistic/stylelint-plugin": "3.1.3", - "@vitejs/plugin-vue": "5.2.4", + "@vitejs/plugin-vue": "6.0.0", "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", @@ -2221,6 +2221,13 @@ "object-assign": "^4.1.1" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/plugin-commonjs": { "version": "22.0.2", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz", @@ -4095,16 +4102,19 @@ ] }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", - "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.0.tgz", + "integrity": "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ==", "dev": true, "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.19" + }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, diff --git a/package.json b/package.json index 258a041451..2bcf441081 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin": "5.0.0", "@stylistic/stylelint-plugin": "3.1.3", - "@vitejs/plugin-vue": "5.2.4", + "@vitejs/plugin-vue": "6.0.0", "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", From 920f6d24d28d2c044eb3ed10e6a281a4523b9fbb Mon Sep 17 00:00:00 2001 From: floss4good Date: Sun, 29 Jun 2025 12:08:03 +0200 Subject: [PATCH 23/30] fix: load OldMilestone based on OldMilestoneID, not MilestoneID (#8330) Fixes #8329 ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. ### Documentation - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8330 Reviewed-by: Robert Wolff Co-authored-by: floss4good Co-committed-by: floss4good --- models/fixtures/comment.yml | 38 ++++++++++++++++++++++++- models/issues/comment_list.go | 16 +++++------ tests/integration/issue_comment_test.go | 26 ++++++++++++++++- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/models/fixtures/comment.yml b/models/fixtures/comment.yml index 34407d6f81..6908d85dda 100644 --- a/models/fixtures/comment.yml +++ b/models/fixtures/comment.yml @@ -186,10 +186,46 @@ type: 8 # milestone poster_id: 1 issue_id: 1 # in repo_id 1 - milestone_id: 10 # not exsting milestone + milestone_id: 10 # not existing milestone old_milestone_id: 0 created_unix: 946685080 +- + id: 2004 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 1 + old_milestone_id: 10 # not existing (ghost) milestone + created_unix: 946685085 + +- + id: 2005 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 10 # not existing (ghost) milestone + old_milestone_id: 1 + created_unix: 946685090 + +- + id: 2006 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 11 # not existing (ghost) milestone + old_milestone_id: 10 # not existing (ghost) milestone + created_unix: 946685095 + +- + id: 2007 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 0 + old_milestone_id: 11 # not existing (ghost) milestone + created_unix: 946685100 + - id: 2010 type: 30 # project diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 7285e347b4..9b502d1c91 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -101,7 +101,7 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { return nil } - milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) + milestones := make(map[int64]*Milestone, len(milestoneIDs)) left := len(milestoneIDs) for left > 0 { limit := db.DefaultMaxInSize @@ -110,7 +110,7 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { } err := db.GetEngine(ctx). In("id", milestoneIDs[:limit]). - Find(&milestoneMaps) + Find(&milestones) if err != nil { return err } @@ -118,8 +118,8 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { milestoneIDs = milestoneIDs[limit:] } - for _, issue := range comments { - issue.Milestone = milestoneMaps[issue.MilestoneID] + for _, comment := range comments { + comment.Milestone = milestones[comment.MilestoneID] } return nil } @@ -140,7 +140,7 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { return nil } - milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) + milestones := make(map[int64]*Milestone, len(milestoneIDs)) left := len(milestoneIDs) for left > 0 { limit := db.DefaultMaxInSize @@ -149,7 +149,7 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { } err := db.GetEngine(ctx). In("id", milestoneIDs[:limit]). - Find(&milestoneMaps) + Find(&milestones) if err != nil { return err } @@ -157,8 +157,8 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { milestoneIDs = milestoneIDs[limit:] } - for _, issue := range comments { - issue.OldMilestone = milestoneMaps[issue.MilestoneID] + for _, comment := range comments { + comment.OldMilestone = milestones[comment.OldMilestoneID] } return nil } diff --git a/tests/integration/issue_comment_test.go b/tests/integration/issue_comment_test.go index f77bfaa9bd..0c53c3028b 100644 --- a/tests/integration/issue_comment_test.go +++ b/tests/integration/issue_comment_test.go @@ -102,11 +102,35 @@ func TestIssueCommentChangeMilestone(t *testing.T) { []string{"user1 removed this from the milestone2 milestone"}, []string{"/user1", "/user2/repo1/milestone/2"}) - // Deleted milestone + // Added milestone that in the meantime was deleted testIssueCommentChangeEvent(t, htmlDoc, "2003", "octicon-milestone", "User One", "/user1", []string{"user1 added this to the (deleted) milestone"}, []string{"/user1"}) + + // Modified milestone - from a meantime deleted one to a valid one + testIssueCommentChangeEvent(t, htmlDoc, "2004", + "octicon-milestone", "User One", "/user1", + []string{"user1 modified the milestone from (deleted) to milestone1"}, + []string{"/user1", "/user2/repo1/milestone/1"}) + + // Modified milestone - from a valid one to a meantime deleted one + testIssueCommentChangeEvent(t, htmlDoc, "2005", + "octicon-milestone", "User One", "/user1", + []string{"user1 modified the milestone from milestone1 to (deleted)"}, + []string{"/user1", "/user2/repo1/milestone/1"}) + + // Modified milestone - from a meantime deleted one to a meantime deleted one + testIssueCommentChangeEvent(t, htmlDoc, "2006", + "octicon-milestone", "User One", "/user1", + []string{"user1 modified the milestone from (deleted) to (deleted)"}, + []string{"/user1"}) + + // Removed milestone that in the meantime was deleted + testIssueCommentChangeEvent(t, htmlDoc, "2007", + "octicon-milestone", "User One", "/user1", + []string{"user1 removed this from the (deleted) milestone"}, + []string{"/user1"}) } func TestIssueCommentChangeProject(t *testing.T) { From 447c5789bdaf092dca6af7282bb20931999ff456 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sun, 29 Jun 2025 13:04:28 +0200 Subject: [PATCH 24/30] fix(ci): add install-minimum-git-version helper for workflows (#8345) https://codeberg.org/forgejo-integration/forgejo/actions/runs/10592#jobstep-3-14 failed with > E: Packages were downgraded and -y was used without --allow-downgrades. Running the tests is done following the instructions in the workflow: ``` # - uncomment [on].pull_request # - swap 'forgejo-integration' and 'forgejo-coding' # - open a pull request at https://codeberg.org/forgejo/forgejo and fix things # - swap 'forgejo-integration' and 'forgejo-coding' # - comment [on].pull_request ``` The result of the test is available at https://codeberg.org/forgejo/forgejo/actions/runs/85408/jobs/0 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8345 Reviewed-by: Gusted Co-authored-by: Earl Warren Co-committed-by: Earl Warren --- .../install-minimum-git-version/action.yaml | 22 ++++++++++++++++++ .forgejo/workflows/testing-integration.yml | 23 ++++--------------- 2 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 .forgejo/workflows-composite/install-minimum-git-version/action.yaml diff --git a/.forgejo/workflows-composite/install-minimum-git-version/action.yaml b/.forgejo/workflows-composite/install-minimum-git-version/action.yaml new file mode 100644 index 0000000000..d4e6e3f2a7 --- /dev/null +++ b/.forgejo/workflows-composite/install-minimum-git-version/action.yaml @@ -0,0 +1,22 @@ +# +# Install the minimal version of Git supported by Forgejo +# +runs: + using: "composite" + steps: + - name: install git and git-lfs + run: | + set -x + + export DEBIAN_FRONTEND=noninteractive + + apt-get update -qq + apt-get -q install -y -qq curl ca-certificates + + curl -sS -o /tmp/git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb + curl -sS -o /tmp/git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb + curl -sS -o /tmp/git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb + + apt-get -q install --allow-downgrades -y -qq /tmp/git-man.deb + apt-get -q install --allow-downgrades -y -qq /tmp/git.deb + apt-get -q install --allow-downgrades -y -qq /tmp/git-lfs.deb diff --git a/.forgejo/workflows/testing-integration.yml b/.forgejo/workflows/testing-integration.yml index 630de50435..102a2d9774 100644 --- a/.forgejo/workflows/testing-integration.yml +++ b/.forgejo/workflows/testing-integration.yml @@ -33,20 +33,8 @@ jobs: steps: - uses: https://data.forgejo.org/actions/checkout@v4 - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.34.1 - run: | - export DEBIAN_FRONTEND=noninteractive - - apt-get update -qq - apt-get -q install -y -qq curl ca-certificates - - curl -sS -o git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb - curl -sS -o git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb - curl -sS -o git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb - - apt-get -q install -y -qq ./git-man.deb - apt-get -q install -y -qq ./git.deb - apt-get -q install -y -qq ./git-lfs.deb + - name: install git 2.34.1 and git-lfs 3.0.2 + uses: ./.forgejo/workflows-composite/install-minimum-git-version - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-backend test-check' @@ -64,11 +52,8 @@ jobs: steps: - uses: https://data.forgejo.org/actions/checkout@v4 - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye + - name: install git 2.34.1 and git-lfs 3.0.2 + uses: ./.forgejo/workflows-composite/install-minimum-git-version - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-sqlite-migration test-sqlite' From 878ce241a40503bd716a9f711ac49fd14695da8a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 13:52:24 +0200 Subject: [PATCH 25/30] Update dependency htmx.org to v2 (forgejo) (#8342) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8342 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 8 ++++---- package.json | 2 +- web_src/js/htmx.js | 2 +- webpack.config.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8d258e28f..15b3243b33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "esbuild-loader": "4.3.0", "escape-goat": "4.0.0", "fast-glob": "3.3.3", - "htmx.org": "1.9.12", + "htmx.org": "2.0.6", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.22", @@ -8745,9 +8745,9 @@ } }, "node_modules/htmx.org": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz", - "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.6.tgz", + "integrity": "sha512-7ythjYneGSk3yCHgtCnQeaoF+D+o7U2LF37WU3O0JYv3gTZSicdEFiI/Ai/NJyC5ZpYJWMpUb11OC5Lr6AfAqA==", "license": "0BSD" }, "node_modules/iconv-lite": { diff --git a/package.json b/package.json index 2bcf441081..ff4dd825f4 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "esbuild-loader": "4.3.0", "escape-goat": "4.0.0", "fast-glob": "3.3.3", - "htmx.org": "1.9.12", + "htmx.org": "2.0.6", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.22", diff --git a/web_src/js/htmx.js b/web_src/js/htmx.js index 5ca3018308..c4893f7c1b 100644 --- a/web_src/js/htmx.js +++ b/web_src/js/htmx.js @@ -1,4 +1,4 @@ -import * as htmx from 'htmx.org'; +import htmx from 'htmx.org'; import {showErrorToast} from './modules/toast.js'; // https://github.com/bigskysoftware/idiomorph#htmx diff --git a/webpack.config.js b/webpack.config.js index 7729035972..8f9949d7b1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -238,7 +238,7 @@ export default { activeModules: true, }), new webpack.ProvidePlugin({ // for htmx extensions - htmx: 'htmx.org', + htmx: ['htmx.org', 'default'], }), new DefinePlugin({ __VUE_OPTIONS_API__: true, // at the moment, many Vue components still use the Vue Options API From 31fc02332a6ad7b2dea19ff70344253d2ebc1a3a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Jun 2025 13:57:15 +0200 Subject: [PATCH 26/30] Update dependency svgo to v4 (forgejo) (#8343) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8343 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 62 ++++++------------- package.json | 2 +- public/assets/img/svg/gitea-alt.svg | 2 +- public/assets/img/svg/gitea-chef.svg | 2 +- public/assets/img/svg/gitea-debian.svg | 2 +- public/assets/img/svg/gitea-gitbucket.svg | 2 +- public/assets/img/svg/gitea-gitlab.svg | 2 +- public/assets/img/svg/gitea-google.svg | 2 +- public/assets/img/svg/gitea-maven.svg | 2 +- .../assets/img/svg/gitea-microsoftonline.svg | 2 +- public/assets/img/svg/gitea-npm.svg | 2 +- public/assets/img/svg/gitea-onedev.svg | 2 +- public/assets/img/svg/gitea-openid.svg | 2 +- public/assets/img/svg/gitea-rubygems.svg | 2 +- public/assets/img/svg/gitea-swift.svg | 2 +- public/assets/img/svg/gitea-vagrant.svg | 2 +- 16 files changed, 34 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15b3243b33..3a87a54f97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,7 +96,7 @@ "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", - "svgo": "3.2.0", + "svgo": "4.0.0", "typescript": "5.8.3", "typescript-eslint": "8.35.0", "vite-string-plugin": "1.3.4", @@ -3125,16 +3125,6 @@ "integrity": "sha512-wpCQMhf5p5GhNg2MmGKXzUNwxe7zRiCsmqYsamez2beP7mKPCSiu+BjZcdN95yYSzO857kr0VfQewmGpS77nqA==", "license": "MIT" }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -14201,25 +14191,25 @@ "dev": true }, "node_modules/svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", "dev": true, "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1", + "sax": "^1.4.1" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -14227,35 +14217,21 @@ } }, "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=16" } }, - "node_modules/svgo/node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "node_modules/svgo/node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/svgo/node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "license": "CC0-1.0" + "license": "ISC" }, "node_modules/swagger-ui-dist": { "version": "5.17.14", diff --git a/package.json b/package.json index ff4dd825f4..88f904c44b 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", - "svgo": "3.2.0", + "svgo": "4.0.0", "typescript": "5.8.3", "typescript-eslint": "8.35.0", "vite-string-plugin": "1.3.4", diff --git a/public/assets/img/svg/gitea-alt.svg b/public/assets/img/svg/gitea-alt.svg index 53e3f17c13..efe4830a0b 100644 --- a/public/assets/img/svg/gitea-alt.svg +++ b/public/assets/img/svg/gitea-alt.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-chef.svg b/public/assets/img/svg/gitea-chef.svg index c5e8a721cc..8fd8ed325d 100644 --- a/public/assets/img/svg/gitea-chef.svg +++ b/public/assets/img/svg/gitea-chef.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-debian.svg b/public/assets/img/svg/gitea-debian.svg index fa2f2f49dc..e92d0b6937 100644 --- a/public/assets/img/svg/gitea-debian.svg +++ b/public/assets/img/svg/gitea-debian.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-gitbucket.svg b/public/assets/img/svg/gitea-gitbucket.svg index 62f603484e..b9e99724b2 100644 --- a/public/assets/img/svg/gitea-gitbucket.svg +++ b/public/assets/img/svg/gitea-gitbucket.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-gitlab.svg b/public/assets/img/svg/gitea-gitlab.svg index 03fcb0b87e..e2d708e7be 100644 --- a/public/assets/img/svg/gitea-gitlab.svg +++ b/public/assets/img/svg/gitea-gitlab.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-google.svg b/public/assets/img/svg/gitea-google.svg index 7dd2622df6..26ee04cb64 100644 --- a/public/assets/img/svg/gitea-google.svg +++ b/public/assets/img/svg/gitea-google.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-maven.svg b/public/assets/img/svg/gitea-maven.svg index 320d01a234..f6ece7dc28 100644 --- a/public/assets/img/svg/gitea-maven.svg +++ b/public/assets/img/svg/gitea-maven.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-microsoftonline.svg b/public/assets/img/svg/gitea-microsoftonline.svg index f2ce13ac22..c143eccbb6 100644 --- a/public/assets/img/svg/gitea-microsoftonline.svg +++ b/public/assets/img/svg/gitea-microsoftonline.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-npm.svg b/public/assets/img/svg/gitea-npm.svg index 7ef74e72bd..2b05c79353 100644 --- a/public/assets/img/svg/gitea-npm.svg +++ b/public/assets/img/svg/gitea-npm.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-onedev.svg b/public/assets/img/svg/gitea-onedev.svg index 94ad1bab31..7ecd18895d 100644 --- a/public/assets/img/svg/gitea-onedev.svg +++ b/public/assets/img/svg/gitea-onedev.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-openid.svg b/public/assets/img/svg/gitea-openid.svg index f4702d2cdf..10c37145a3 100644 --- a/public/assets/img/svg/gitea-openid.svg +++ b/public/assets/img/svg/gitea-openid.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-rubygems.svg b/public/assets/img/svg/gitea-rubygems.svg index 4e43bdf2f4..7cd9d34e6a 100644 --- a/public/assets/img/svg/gitea-rubygems.svg +++ b/public/assets/img/svg/gitea-rubygems.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-swift.svg b/public/assets/img/svg/gitea-swift.svg index 4182100185..891ac12b56 100644 --- a/public/assets/img/svg/gitea-swift.svg +++ b/public/assets/img/svg/gitea-swift.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-vagrant.svg b/public/assets/img/svg/gitea-vagrant.svg index ba50101d52..18b05e900d 100644 --- a/public/assets/img/svg/gitea-vagrant.svg +++ b/public/assets/img/svg/gitea-vagrant.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From c57dea336c2ad3dcff7dc4b791af69b195abe01a Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Sun, 29 Jun 2025 16:22:07 +0200 Subject: [PATCH 27/30] fix(ui): small org dashboard ui cleanup (#8327) Small UI cleanups in this area with no visual changes: https://codeberg.org/attachments/4282f225-63e0-41b7-9edb-8b64877092b2 * remove classes `ui`, `top`, `attached`: following https://codeberg.org/forgejo/forgejo/pulls/2593, it is no longer a fomantic ui segment for those classes to be relevant to it * use gap in flexbox as it is a cleaner way than setting margins Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8327 Reviewed-by: Gusted Reviewed-by: Robert Wolff Reviewed-by: Beowulf --- web_src/js/components/DashboardRepoList.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 58c5461baa..35f1082a93 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -340,10 +340,10 @@ export default sfc; // activate the IDE's Vue plugin {{ textMyOrgs }} {{ organizationsTotalCount }}
-

-
+

+
{{ textMyRepos }} - {{ reposTotalCount }} + {{ reposTotalCount }}