From 426bb491c0c2a92de8e25bfd5d1d3bc4b3e5922f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 4 Jan 2026 08:45:36 -0800 Subject: [PATCH 1/3] Move assign project when creating pull request to the same database transaction (#36244) --- routers/web/repo/pull.go | 10 +--------- services/forms/repo_form.go | 7 ------- services/pull/pull.go | 10 ++++++++++ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index ad4d3da294..7c67d614f6 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1390,6 +1390,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { AssigneeIDs: assigneeIDs, Reviewers: validateRet.Reviewers, TeamReviewers: validateRet.TeamReviewers, + ProjectID: projectID, } if err := pull_service.NewPullRequest(ctx, prOpts); err != nil { switch { @@ -1441,15 +1442,6 @@ func CompareAndPullRequestPost(ctx *context.Context) { return } - if projectID > 0 && ctx.Repo.CanWrite(unit.TypeProjects) { - if err := issues_model.IssueAssignOrRemoveProject(ctx, pullIssue, ctx.Doer, projectID, 0); err != nil { - if !errors.Is(err, util.ErrPermissionDenied) { - ctx.ServerError("IssueAssignOrRemoveProject", err) - return - } - } - } - log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID) ctx.JSONRedirect(pullIssue.Link()) } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 2d33d2b42b..e4545570c8 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -405,13 +405,6 @@ func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// .___ -// | | ______ ________ __ ____ -// | |/ ___// ___/ | \_/ __ \ -// | |\___ \ \___ \| | /\ ___/ -// |___/____ >____ >____/ \___ > -// \/ \/ \/ - // CreateIssueForm form for creating issue type CreateIssueForm struct { Title string `binding:"Required;MaxSize(255)"` diff --git a/services/pull/pull.go b/services/pull/pull.go index f8f64dd650..7038285bd1 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -52,6 +52,7 @@ type NewPullRequestOptions struct { AssigneeIDs []int64 Reviewers []*user_model.User TeamReviewers []*organization.Team + ProjectID int64 } // NewPullRequest creates new pull request with labels for repository. @@ -67,11 +68,13 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error { // user should be a collaborator or a member of the organization for base repo canCreate := issue.Poster.IsAdmin || pr.Flow == issues_model.PullRequestFlowAGit + canAssignProject := canCreate if !canCreate { canCreate, err := repo_model.IsOwnerMemberCollaborator(ctx, repo, issue.Poster.ID) if err != nil { return err } + canAssignProject = canCreate if !canCreate { // or user should have write permission in the head repo @@ -85,6 +88,7 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error { if !perm.CanWrite(unit.TypeCode) { return issues_model.ErrMustCollaborator } + canAssignProject = perm.CanWrite(unit.TypeProjects) } } @@ -117,6 +121,12 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error { assigneeCommentMap[assigneeID] = comment } + if opts.ProjectID > 0 && canAssignProject { + if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, issue.Poster, opts.ProjectID, 0); err != nil { + return err + } + } + pr.Issue = issue issue.PullRequest = pr From 1ee7f8e96635ea1768b1a8309918e2463fb607e6 Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Mon, 5 Jan 2026 14:48:12 +0100 Subject: [PATCH 2/3] fix: prevent panic when GitLab release has more links than sources (#36295) The code incorrectly assumed rel.Assets.Links and rel.Assets.Sources arrays have equal length. This causes index out of bounds panic when migrating GitLab releases with more links than sources, which is common with GoReleaser-generated releases. Fixes #36292 --------- Co-authored-by: wxiaoguang --- modules/migration/release.go | 9 ++++++--- services/migrations/github.go | 1 - services/migrations/gitlab.go | 3 +-- services/migrations/main_test.go | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/migration/release.go b/modules/migration/release.go index f92cf25e7b..e25e7e4428 100644 --- a/modules/migration/release.go +++ b/modules/migration/release.go @@ -10,9 +10,12 @@ import ( // ReleaseAsset represents a release asset type ReleaseAsset struct { - ID int64 - Name string - ContentType *string `yaml:"content_type"` + ID int64 + Name string + + // There was a field "ContentType (content_type)" because Some forges can provide that for assets, + // but we don't need it when migrating, so the field is omitted here. + Size *int DownloadCount *int `yaml:"download_count"` Created time.Time diff --git a/services/migrations/github.go b/services/migrations/github.go index ae7350c016..ce631dcd42 100644 --- a/services/migrations/github.go +++ b/services/migrations/github.go @@ -329,7 +329,6 @@ func (g *GithubDownloaderV3) convertGithubRelease(ctx context.Context, rel *gith r.Assets = append(r.Assets, &base.ReleaseAsset{ ID: asset.GetID(), Name: asset.GetName(), - ContentType: asset.ContentType, Size: asset.Size, DownloadCount: asset.DownloadCount, Created: asset.CreatedAt.Time, diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go index 260fa9cd5d..cbf974af2c 100644 --- a/services/migrations/gitlab.go +++ b/services/migrations/gitlab.go @@ -316,12 +316,11 @@ func (g *GitlabDownloader) convertGitlabRelease(ctx context.Context, rel *gitlab httpClient := NewMigrationHTTPClient() - for k, asset := range rel.Assets.Links { + for _, asset := range rel.Assets.Links { assetID := asset.ID // Don't optimize this, for closure we need a local variable r.Assets = append(r.Assets, &base.ReleaseAsset{ ID: int64(asset.ID), Name: asset.Name, - ContentType: &rel.Assets.Sources[k].Format, Size: &zero, DownloadCount: &zero, DownloadFunc: func() (io.ReadCloser, error) { diff --git a/services/migrations/main_test.go b/services/migrations/main_test.go index d0ec6a3f8d..581af614f9 100644 --- a/services/migrations/main_test.go +++ b/services/migrations/main_test.go @@ -171,7 +171,6 @@ func assertReactionsEqual(t *testing.T, expected, actual []*base.Reaction) { func assertReleaseAssetEqual(t *testing.T, expected, actual *base.ReleaseAsset) { assert.Equal(t, expected.ID, actual.ID) assert.Equal(t, expected.Name, actual.Name) - assert.Equal(t, expected.ContentType, actual.ContentType) assert.Equal(t, expected.Size, actual.Size) assert.Equal(t, expected.DownloadCount, actual.DownloadCount) assertTimeEqual(t, expected.Created, actual.Created) From 1d01286f4c4503ab629fad8c5f78dd245c3245c1 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Mon, 5 Jan 2026 18:00:26 +0100 Subject: [PATCH 3/3] Add 'allow_maintainer_edit' API option for creating a pull request (#36283) WebUI has a checkbox for enabling maintainer edits you can check right away when creating a new pull request. Also, it is possible to set `allow_maintainer_edit` in an existing pull request via API. This change enables the option while creating a new pull request via API. --------- Co-authored-by: wxiaoguang --- modules/structs/pull.go | 2 ++ routers/api/v1/repo/pull.go | 8 ++++++++ templates/swagger/v1_json.tmpl | 5 +++++ tests/integration/api_pull_test.go | 19 +++++++++++++++++-- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/structs/pull.go b/modules/structs/pull.go index 7cc58217a0..3ad2f78bd3 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -140,6 +140,8 @@ type CreatePullRequestOption struct { Reviewers []string `json:"reviewers"` // The list of team reviewer names TeamReviewers []string `json:"team_reviewers"` + // Whether maintainers can edit the pull request + AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` } // EditPullRequestOption options when modify pull request diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 9b1eb3fc85..09eeefd2c7 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -496,6 +497,11 @@ func CreatePullRequest(ctx *context.APIContext) { deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix()) } + unitPullRequest, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypePullRequests) + if err != nil { + ctx.APIErrorInternal(err) + } + prIssue := &issues_model.Issue{ RepoID: repo.ID, Title: form.Title, @@ -517,6 +523,8 @@ func CreatePullRequest(ctx *context.APIContext) { Type: issues_model.PullRequestGitea, } + pr.AllowMaintainerEdit = optional.FromPtr(form.AllowMaintainerEdit).ValueOrDefault(unitPullRequest.PullRequestsConfig().DefaultAllowMaintainerEdit) + // Get all assignee IDs assigneeIDs, err := issues_model.MakeIDsFromAPIAssigneesToAdd(ctx, form.Assignee, form.Assignees) if err != nil { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index be6c4bdfd3..0c33227364 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -23420,6 +23420,11 @@ "description": "CreatePullRequestOption options when creating a pull request", "type": "object", "properties": { + "allow_maintainer_edit": { + "description": "Whether maintainers can edit the pull request", + "type": "boolean", + "x-go-name": "AllowMaintainerEdit" + }, "assignee": { "description": "The primary assignee username", "type": "string", diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index 433dce3d5e..0e1a88dcee 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -270,13 +270,20 @@ func TestAPICreatePullSuccess(t *testing.T) { owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) session := loginUser(t, owner11.Name) + prTitle := "test pull request title " + time.Now().String() token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ Head: owner11.Name + ":master", Base: "master", - Title: "create a failure pr", + Title: prTitle, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) + + // Also test that AllowMaintainerEdit is false by default + prIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: prTitle}) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: prIssue.ID}) + assert.False(t, pr.AllowMaintainerEdit) + MakeRequest(t, req, http.StatusUnprocessableEntity) // second request should fail } @@ -290,11 +297,14 @@ func TestAPICreatePullBasePermission(t *testing.T) { user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) session := loginUser(t, user4.Name) + prTitle := "test pull request title " + time.Now().String() token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ Head: repo11.OwnerName + ":master", Base: "master", - Title: "create a failure pr", + Title: prTitle, + + AllowMaintainerEdit: util.ToPointer(true), } req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -306,6 +316,11 @@ func TestAPICreatePullBasePermission(t *testing.T) { // create again req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) + + // Also test that AllowMaintainerEdit is set to true + prIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: prTitle}) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: prIssue.ID}) + assert.True(t, pr.AllowMaintainerEdit) } func TestAPICreatePullHeadPermission(t *testing.T) {