diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 301e88b14e645..b0875123b7b8b 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1067,6 +1067,9 @@ LEVEL = Info ;; ;; In addition to testing patches using the three-way merge method, re-test conflicting patches with git apply ;TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY = false +;; +;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo. +;RETARGET_CHILDREN_ON_MERGE = true ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index c9e99ea54fcef..eb9b8d1ae9a43 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -135,6 +135,7 @@ In addition, there is _`StaticRootPath`_ which can be set as a built-in at build - `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`: **false**: In default squash-merge messages include the commit message of all commits comprising the pull request. - `ADD_CO_COMMITTER_TRAILERS`: **true**: Add co-authored-by and co-committed-by trailers to merge commit messages if committer does not match author. - `TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY`: **false**: PR patches are tested using a three-way merge method to discover if there are conflicts. If this setting is set to **true**, conflicting patches will be retested using `git apply` - This was the previous behaviour in 1.18 (and earlier) but is somewhat inefficient. Please report if you find that this setting is required. +- `RETARGET_CHILDREN_ON_MERGE`: **true**: Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo. ### Repository - Issue (`repository.issue`) diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index 0104419550572..e6c59f527a4df 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -669,3 +669,10 @@ type: 10 config: "{}" created_unix: 946684810 + +- + id: 101 + repo_id: 59 + type: 1 + config: "{}" + created_unix: 946684810 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 373c1caa6257a..f4e837673541d 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -1693,3 +1693,16 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + +- + id: 59 + owner_id: 2 + owner_name: user2 + lower_name: test_commit_revert + name: test_commit_revert + default_branch: main + is_empty: false + is_archived: false + is_private: true + status: 0 + num_issues: 0 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index fd513798167d4..79fbb981f647a 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -66,7 +66,7 @@ num_followers: 2 num_following: 1 num_stars: 2 - num_repos: 14 + num_repos: 15 num_teams: 0 num_members: 0 visibility: 0 diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 9697a851d3ba9..a6f0ed8833589 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -85,6 +85,7 @@ var ( PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool TestConflictingPatchesWithGitApply bool + RetargetChildrenOnMerge bool } `ini:"repository.pull-request"` // Issue Setting @@ -209,6 +210,7 @@ var ( PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool TestConflictingPatchesWithGitApply bool + RetargetChildrenOnMerge bool }{ WorkInProgressPrefixes: []string{"WIP:", "[WIP]"}, // Same as GitHub. See @@ -223,6 +225,7 @@ var ( DefaultMergeMessageOfficialApproversOnly: true, PopulateSquashCommentWithCommitMessages: false, AddCoCommitterTrailers: true, + RetargetChildrenOnMerge: true, }, // Issue settings diff --git a/package-lock.json b/package-lock.json index 3d27df398f1a1..4b8cb6896f24c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@citation-js/plugin-software-formats": "0.6.1", "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.1", - "@github/relative-time-element": "4.3.0", + "@github/relative-time-element": "4.3.1", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.8.0", @@ -1006,9 +1006,9 @@ "integrity": "sha512-ap+ulyqzG3aVqwKsKjbDdYwM75TQXZpPtmIuPwm+54OTgcC96267oX3cEqd1wSqGsH7O5PonZ//fE9jH7Q4JkA==" }, "node_modules/@github/relative-time-element": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.3.0.tgz", - "integrity": "sha512-+tFjX9//HRS1HnBa5cNgfEtE52arwiutYg1TOF+Trk40SPxst9Q8Rtc3BKD6aKsvfbtub68vfhipgchGjj9o7g==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.3.1.tgz", + "integrity": "sha512-zL79nlhZVCg7x2Pf/HT5MB0mowmErE71VXpF10/3Wy8dQwkninNO1M9aOizh2wKC5LkSpDXqNYjDZwbH0/bcSg==" }, "node_modules/@github/text-expander-element": { "version": "2.6.1", diff --git a/package.json b/package.json index dab7765091882..801e85db83a7e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@citation-js/plugin-software-formats": "0.6.1", "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.1", - "@github/relative-time-element": "4.3.0", + "@github/relative-time-element": "4.3.1", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.8.0", diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 34129ad5958c8..b1cb7011f1d4b 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -913,6 +913,10 @@ func MergePullRequest(ctx *context.APIContext) { } defer headRepo.Close() } + if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil { + ctx.Error(http.StatusInternalServerError, "RetargetChildrenOnMerge", err) + return + } if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil { switch { case git.IsErrBranchNotExist(err): diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 32d82b2a643cb..e36d7092af286 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1587,6 +1587,12 @@ func CleanUpPullRequest(ctx *context.Context) { func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) { fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch + + if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil { + ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) + return + } + if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil { switch { case git.IsErrBranchNotExist(err): diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index cd5eac057e040..b5c550ae45849 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -288,7 +288,7 @@ func CreatePost(ctx *context.Context) { DefaultBranch: form.DefaultBranch, AutoInit: form.AutoInit, IsTemplate: form.Template, - TrustModel: repo_model.ToTrustModel(form.TrustModel), + TrustModel: repo_model.DefaultTrustModel, ObjectFormatName: form.ObjectFormatName, }) if err == nil { diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 780fc8800098a..845eccf817d3a 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -21,13 +21,6 @@ import ( "gitea.com/go-chi/binding" ) -// _______________________________________ _________.______________________ _______________.___. -// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | | -// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | | -// | | \| \ | | / | \/ \| | | | / | \ | \\____ | -// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______| -// \/ \/ \/ \/ \/ \/ \/ - // CreateRepoForm form for creating repository type CreateRepoForm struct { UID int64 `binding:"Required"` @@ -50,7 +43,6 @@ type CreateRepoForm struct { Avatar bool Labels bool ProtectedBranch bool - TrustModel string ForkSingleBranch string ObjectFormatName string diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index 9514e35bedd9a..e8a8313625dcb 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -267,7 +267,7 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re defer t.Close() var lastCommitID string - if err := t.Clone(repo.DefaultBranch); err != nil { + if err := t.Clone(repo.DefaultBranch, true); err != nil { if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { return err } diff --git a/services/pull/pull.go b/services/pull/pull.go index d1630f3792c07..930954bdfdfa1 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -546,6 +546,43 @@ func (errs errlist) Error() string { return "" } +// RetargetChildrenOnMerge retarget children pull requests on merge if possible +func RetargetChildrenOnMerge(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) error { + if setting.Repository.PullRequest.RetargetChildrenOnMerge && pr.BaseRepoID == pr.HeadRepoID { + return RetargetBranchPulls(ctx, doer, pr.HeadRepoID, pr.HeadBranch, pr.BaseBranch) + } + return nil +} + +// RetargetBranchPulls change target branch for all pull requests whose base branch is the branch +// Both branch and targetBranch must be in the same repo (for security reasons) +func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch, targetBranch string) error { + prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch) + if err != nil { + return err + } + + if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + return err + } + + var errs errlist + for _, pr := range prs { + if err = pr.Issue.LoadRepo(ctx); err != nil { + errs = append(errs, err) + } else if err = ChangeTargetBranch(ctx, pr, doer, targetBranch); err != nil && + !issues_model.IsErrIssueIsClosed(err) && !models.IsErrPullRequestHasMerged(err) && + !issues_model.IsErrPullRequestAlreadyExists(err) { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return errs + } + return nil +} + // CloseBranchPulls close all the pull requests who's head branch is the branch func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch string) error { prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch) diff --git a/services/repository/files/cherry_pick.go b/services/repository/files/cherry_pick.go index e88ea16119799..613b46d8f6629 100644 --- a/services/repository/files/cherry_pick.go +++ b/services/repository/files/cherry_pick.go @@ -31,12 +31,15 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod log.Error("%v", err) } defer t.Close() - if err := t.Clone(opts.OldBranch); err != nil { + if err := t.Clone(opts.OldBranch, false); err != nil { return nil, err } if err := t.SetDefaultIndex(); err != nil { return nil, err } + if err := t.RefreshIndex(); err != nil { + return nil, err + } // Get the commit of the original branch commit, err := t.GetBranchCommit(opts.OldBranch) diff --git a/services/repository/files/diff.go b/services/repository/files/diff.go index 373249b114541..bf8b938e2178f 100644 --- a/services/repository/files/diff.go +++ b/services/repository/files/diff.go @@ -21,7 +21,7 @@ func GetDiffPreview(ctx context.Context, repo *repo_model.Repository, branch, tr return nil, err } defer t.Close() - if err := t.Clone(branch); err != nil { + if err := t.Clone(branch, true); err != nil { return nil, err } if err := t.SetDefaultIndex(); err != nil { diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 96899e6e0e9f1..14f8caaa8cf73 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -113,7 +113,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user log.Error("%v", err) } defer t.Close() - if err := t.Clone(opts.OldBranch); err != nil { + if err := t.Clone(opts.OldBranch, true); err != nil { return nil, err } if err := t.SetDefaultIndex(); err != nil { diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 6a0b7b675c81e..9fcd335c55a6a 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -51,8 +51,13 @@ func (t *TemporaryUploadRepository) Close() { } // Clone the base repository to our path and set branch as the HEAD -func (t *TemporaryUploadRepository) Clone(branch string) error { - if _, _, err := git.NewCommand(t.ctx, "clone", "-s", "--bare", "-b").AddDynamicArguments(branch, t.repo.RepoPath(), t.basePath).RunStdString(nil); err != nil { +func (t *TemporaryUploadRepository) Clone(branch string, bare bool) error { + cmd := git.NewCommand(t.ctx, "clone", "-s", "-b").AddDynamicArguments(branch, t.repo.RepoPath(), t.basePath) + if bare { + cmd.AddArguments("--bare") + } + + if _, _, err := cmd.RunStdString(nil); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched { return git.ErrBranchNotExist{ @@ -97,6 +102,14 @@ func (t *TemporaryUploadRepository) SetDefaultIndex() error { return nil } +// RefreshIndex looks at the current index and checks to see if merges or updates are needed by checking stat() information. +func (t *TemporaryUploadRepository) RefreshIndex() error { + if _, _, err := git.NewCommand(t.ctx, "update-index", "--refresh").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { + return fmt.Errorf("RefreshIndex: %w", err) + } + return nil +} + // LsFiles checks if the given filename arguments are in the index func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, error) { stdOut := new(bytes.Buffer) diff --git a/services/repository/files/update.go b/services/repository/files/update.go index dd8d9ee42563d..1892043304e29 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -146,7 +146,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } defer t.Close() hasOldBranch := true - if err := t.Clone(opts.OldBranch); err != nil { + if err := t.Clone(opts.OldBranch, true); err != nil { for _, file := range opts.Files { if file.Operation == "delete" { return nil, err diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index 61e38b55a3705..cbfaf49d13176 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -87,7 +87,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use defer t.Close() hasOldBranch := true - if err = t.Clone(opts.OldBranch); err != nil { + if err = t.Clone(opts.OldBranch, true); err != nil { if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { return err } diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl index fe592c00005d6..3149f20670e42 100644 --- a/templates/repo/activity.tmpl +++ b/templates/repo/activity.tmpl @@ -126,7 +126,7 @@ {{ctx.Locale.Tr "repo.activity.published_release_label"}} {{.TagName}} {{if not .IsTag}} - {{.Title | RenderEmoji $.Context}} + {{.Title | RenderEmoji $.Context | RenderCodeBlock}} {{end}} {{TimeSinceUnix .CreatedUnix ctx.Locale}}

@@ -146,7 +146,7 @@ {{range .Activity.MergedPRs}}

{{ctx.Locale.Tr "repo.activity.merged_prs_label"}} - #{{.Index}} {{.Issue.Title | RenderEmoji $.Context}} + #{{.Index}} {{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}} {{TimeSinceUnix .MergedUnix ctx.Locale}}

{{end}} @@ -165,7 +165,7 @@ {{range .Activity.OpenedPRs}}

{{ctx.Locale.Tr "repo.activity.opened_prs_label"}} - #{{.Index}} {{.Issue.Title | RenderEmoji $.Context}} + #{{.Index}} {{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}} {{TimeSinceUnix .Issue.CreatedUnix ctx.Locale}}

{{end}} @@ -184,7 +184,7 @@ {{range .Activity.ClosedIssues}}

{{ctx.Locale.Tr "repo.activity.closed_issue_label"}} - #{{.Index}} {{.Title | RenderEmoji $.Context}} + #{{.Index}} {{.Title | RenderEmoji $.Context | RenderCodeBlock}} {{TimeSinceUnix .ClosedUnix ctx.Locale}}

{{end}} @@ -203,7 +203,7 @@ {{range .Activity.OpenedIssues}}

{{ctx.Locale.Tr "repo.activity.new_issue_label"}} - #{{.Index}} {{.Title | RenderEmoji $.Context}} + #{{.Index}} {{.Title | RenderEmoji $.Context | RenderCodeBlock}} {{TimeSinceUnix .CreatedUnix ctx.Locale}}

{{end}} @@ -221,9 +221,9 @@ {{ctx.Locale.Tr "repo.activity.unresolved_conv_label"}} #{{.Index}} {{if .IsPull}} - {{.Title | RenderEmoji $.Context}} + {{.Title | RenderEmoji $.Context | RenderCodeBlock}} {{else}} - {{.Title | RenderEmoji $.Context}} + {{.Title | RenderEmoji $.Context | RenderCodeBlock}} {{end}} {{TimeSinceUnix .UpdatedUnix ctx.Locale}}

diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 7b2e51130f6a6..3b4b994be7a48 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -61,7 +61,7 @@
- +
@@ -185,29 +185,6 @@ {{ctx.Locale.Tr "repo.default_branch_helper"}}
-
- - -
- {{ctx.Locale.Tr "repo.trust_model_helper"}} - -
-
@@ -216,7 +193,6 @@
-
diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/HEAD b/tests/gitea-repositories-meta/user2/test_commit_revert.git/HEAD new file mode 100644 index 0000000000000..b870d82622c1a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_commit_revert.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/config b/tests/gitea-repositories-meta/user2/test_commit_revert.git/config new file mode 100644 index 0000000000000..57bbcba5beed0 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_commit_revert.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true +[remote "origin"] + url = https://try.gitea.io/me-heer/test_commit_revert.git diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/description b/tests/gitea-repositories-meta/user2/test_commit_revert.git/description new file mode 100644 index 0000000000000..498b267a8c781 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_commit_revert.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/info/exclude b/tests/gitea-repositories-meta/user2/test_commit_revert.git/info/exclude new file mode 100644 index 0000000000000..a5196d1be8fb5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_commit_revert.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.idx b/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.idx new file mode 100644 index 0000000000000..77bcbe7fb4cae Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.idx differ diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.pack b/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.pack new file mode 100644 index 0000000000000..7271cdaeb8774 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.pack differ diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/packed-refs b/tests/gitea-repositories-meta/user2/test_commit_revert.git/packed-refs new file mode 100644 index 0000000000000..1f546d7fd510e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_commit_revert.git/packed-refs @@ -0,0 +1,3 @@ +# pack-refs with: peeled fully-peeled sorted +46aa6ab2c881ae90e15d9ccfc947d1625c892ce5 refs/heads/develop +deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7 refs/heads/main diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index a6ee0d9dfa1ef..029ea65d7190f 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -17,7 +17,7 @@ import ( "github.com/stretchr/testify/assert" ) -func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, title string) *httptest.ResponseRecorder { +func testPullCreate(t *testing.T, session *TestSession, user, repo string, toSelf bool, targetBranch, sourceBranch, title string) *httptest.ResponseRecorder { req := NewRequest(t, "GET", path.Join(user, repo)) resp := session.MakeRequest(t, req, http.StatusOK) @@ -25,8 +25,21 @@ func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, titl htmlDoc := NewHTMLParser(t, resp.Body) link, exists := htmlDoc.doc.Find("#new-pull-request").Attr("href") assert.True(t, exists, "The template has changed") - if branch != "master" { - link = strings.Replace(link, ":master", ":"+branch, 1) + + targetUser := strings.Split(link, "/")[1] + if toSelf && targetUser != user { + link = strings.Replace(link, targetUser, user, 1) + } + + if targetBranch != "master" { + link = strings.Replace(link, "master...", targetBranch+"...", 1) + } + if sourceBranch != "master" { + if targetUser == user { + link = strings.Replace(link, "...master", "..."+sourceBranch, 1) + } else { + link = strings.Replace(link, ":master", ":"+sourceBranch, 1) + } } req = NewRequest(t, "GET", link) @@ -49,7 +62,7 @@ func TestPullCreate(t *testing.T) { session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") // check the redirected URL url := test.RedirectURL(resp) @@ -77,7 +90,7 @@ func TestPullCreate_TitleEscape(t *testing.T) { session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "XSS PR") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "XSS PR") // check the redirected URL url := test.RedirectURL(resp) @@ -142,7 +155,7 @@ func TestPullBranchDelete(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther) testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master1", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title") // check the redirected URL url := test.RedirectURL(resp) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 35af47f802fd6..2aa6742a56ac0 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -35,16 +35,23 @@ import ( "github.com/stretchr/testify/assert" ) -func testPullMerge(t *testing.T, session *TestSession, user, repo, pullnum string, mergeStyle repo_model.MergeStyle) *httptest.ResponseRecorder { +func testPullMerge(t *testing.T, session *TestSession, user, repo, pullnum string, mergeStyle repo_model.MergeStyle, deleteBranch bool) *httptest.ResponseRecorder { req := NewRequest(t, "GET", path.Join(user, repo, "pulls", pullnum)) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) link := path.Join(user, repo, "pulls", pullnum, "merge") - req = NewRequestWithValues(t, "POST", link, map[string]string{ + + options := map[string]string{ "_csrf": htmlDoc.GetCSRF(), "do": string(mergeStyle), - }) + } + + if deleteBranch { + options["delete_branch_after_merge"] = "on" + } + + req = NewRequestWithValues(t, "POST", link, options) resp = session.MakeRequest(t, req, http.StatusOK) respJSON := struct { @@ -83,11 +90,11 @@ func TestPullMerge(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) assert.NoError(t, err) @@ -105,11 +112,11 @@ func TestPullRebase(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) assert.NoError(t, err) @@ -127,11 +134,11 @@ func TestPullRebaseMerge(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) assert.NoError(t, err) @@ -150,11 +157,11 @@ func TestPullSquash(t *testing.T) { testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleSquash) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleSquash, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) assert.NoError(t, err) @@ -168,11 +175,11 @@ func TestPullCleanUpAfterMerge(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited - TestPullCleanUpAfterMerge)\n") - resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "feature/test", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) // Check PR branch deletion resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) @@ -203,7 +210,7 @@ func TestCantMergeWorkInProgress(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "[wip] This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "[wip] This is a pull title") req := NewRequest(t, "GET", test.RedirectURL(resp)) resp = session.MakeRequest(t, req, http.StatusOK) @@ -435,3 +442,63 @@ func TestConflictChecking(t *testing.T) { assert.False(t, conflictingPR.Mergeable(db.DefaultContext)) }) } + +func TestPullRetargetChildOnBranchDelete(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testEditFileToNewBranch(t, session, "user2", "repo1", "master", "base-pr", "README.md", "Hello, World\n(Edited - TestPullRetargetOnCleanup - base PR)\n") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "base-pr", "child-pr", "README.md", "Hello, World\n(Edited - TestPullRetargetOnCleanup - base PR)\n(Edited - TestPullRetargetOnCleanup - child PR)") + + respBasePR := testPullCreate(t, session, "user2", "repo1", true, "master", "base-pr", "Base Pull Request") + elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") + assert.EqualValues(t, "pulls", elemBasePR[3]) + + respChildPR := testPullCreate(t, session, "user1", "repo1", false, "base-pr", "child-pr", "Child Pull Request") + elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/") + assert.EqualValues(t, "pulls", elemChildPR[3]) + + testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true) + + // Check child PR + req := NewRequest(t, "GET", test.RedirectURL(respChildPR)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + targetBranch := htmlDoc.doc.Find("#branch_target>a").Text() + prStatus := strings.TrimSpace(htmlDoc.doc.Find(".issue-title-meta>.issue-state-label").Text()) + + assert.EqualValues(t, "master", targetBranch) + assert.EqualValues(t, "Open", prStatus) + }) +} + +func TestPullDontRetargetChildOnWrongRepo(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base-pr", "README.md", "Hello, World\n(Edited - TestPullDontRetargetChildOnWrongRepo - base PR)\n") + testEditFileToNewBranch(t, session, "user1", "repo1", "base-pr", "child-pr", "README.md", "Hello, World\n(Edited - TestPullDontRetargetChildOnWrongRepo - base PR)\n(Edited - TestPullDontRetargetChildOnWrongRepo - child PR)") + + respBasePR := testPullCreate(t, session, "user1", "repo1", false, "master", "base-pr", "Base Pull Request") + elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") + assert.EqualValues(t, "pulls", elemBasePR[3]) + + respChildPR := testPullCreate(t, session, "user1", "repo1", true, "base-pr", "child-pr", "Child Pull Request") + elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/") + assert.EqualValues(t, "pulls", elemChildPR[3]) + + testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true) + + // Check child PR + req := NewRequest(t, "GET", test.RedirectURL(respChildPR)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + targetBranch := htmlDoc.doc.Find("#branch_target>a").Text() + prStatus := strings.TrimSpace(htmlDoc.doc.Find(".issue-title-meta>.issue-state-label").Text()) + + assert.EqualValues(t, "base-pr", targetBranch) + assert.EqualValues(t, "Closed", prStatus) + }) +} diff --git a/tests/integration/repo_activity_test.go b/tests/integration/repo_activity_test.go index c8d0c46d6455e..792554db4bc93 100644 --- a/tests/integration/repo_activity_test.go +++ b/tests/integration/repo_activity_test.go @@ -22,16 +22,16 @@ func TestRepoActivity(t *testing.T) { // Create PRs (1 merged & 2 proposed) testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") - testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title") + testPullCreate(t, session, "user1", "repo1", false, "master", "feat/better_readme", "This is a pull title") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n") - testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title") + testPullCreate(t, session, "user1", "repo1", false, "master", "feat/much_better_readme", "This is a pull title") // Create issues (3 new issues) testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1") diff --git a/tests/integration/repo_mergecommit_revert_test.go b/tests/integration/repo_mergecommit_revert_test.go new file mode 100644 index 0000000000000..4d612bdcdb685 --- /dev/null +++ b/tests/integration/repo_mergecommit_revert_test.go @@ -0,0 +1,34 @@ +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestRepoMergeCommitRevert(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main?ref=main&refType=branch&cherry-pick-type=revert") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + req = NewRequestWithValues(t, "POST", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main", map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "last_commit": "deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7", + "page_has_posted": "true", + "revert": "true", + "commit_summary": "reverting test commit", + "commit_message": "test message", + "commit_choice": "direct", + "new_branch_name": "test-revert-branch-1", + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + // A successful revert redirects to the main branch + assert.EqualValues(t, "/user2/test_commit_revert/src/branch/main", resp.Header().Get("Location")) +}