From c08d263a1900aa5ee92f56af8ad1c7a2697d02e1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 16 Jan 2024 20:54:48 +0800 Subject: [PATCH 1/5] Remove trust model selection from repository creation on web page because it can be changed in settings later (#28814) As more and more options can be set for creating the repository, I don't think we should put all of them into the creation web page which will make things look complicated and confusing. And I think we need some rules about how to decide which should/should not be put in creating a repository page. One rule I can imagine is if this option can be changed later and it's not a MUST on the creation, then it can be removed on the page. So I found trust model is the first one. This PR removed the trust model selections on creating a repository web page and kept others as before. This is also a preparation for #23894 which will add a choice about SHA1 or SHA256 that cannot be changed once the repository created. --- routers/web/repo/repo.go | 2 +- services/forms/repo_form.go | 8 -------- templates/repo/create.tmpl | 26 +------------------------- 3 files changed, 2 insertions(+), 34 deletions(-) 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/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"}} -
    -
  • {{ctx.Locale.Tr "repo.trust_model_helper_collaborator"}}
  • -
  • {{ctx.Locale.Tr "repo.trust_model_helper_committer"}}
  • -
  • {{ctx.Locale.Tr "repo.trust_model_helper_collaborator_committer"}}
  • -
  • {{ctx.Locale.Tr "repo.trust_model_helper_default"}}
  • -
-
-
@@ -216,7 +193,6 @@
-
From edfb57e6996367ec2c682fd7fdbdb405728c001f Mon Sep 17 00:00:00 2001 From: JakobDev Date: Tue, 16 Jan 2024 15:38:09 +0100 Subject: [PATCH 2/5] Render code block in activity tab (#28816) This is a little bugfix. Inline code is usually rendered in issue titles, but it is missing in the activity tab. Before: ![Screenshot 2024-01-16 at 14-20-51 Test](https://github.com/go-gitea/gitea/assets/15185051/383370f3-0fb2-49de-81cc-014e5cf86727) After: ![grafik](https://github.com/go-gitea/gitea/assets/15185051/83eaf973-ce9a-44ce-beea-2db49fc8bd73) --- templates/repo/activity.tmpl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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}}

From b8270240bf53cdf4ed3267a395347272cf48b176 Mon Sep 17 00:00:00 2001 From: Mihir Joshi Date: Tue, 16 Jan 2024 20:36:51 +0530 Subject: [PATCH 3/5] Fix reverting a merge commit failing (#28794) Fixes #22236 --- Error occurring currently while trying to revert commit using read-tree -m approach: > 2022/12/26 16:04:43 ...rvices/pull/patch.go:240:AttemptThreeWayMerge() [E] [63a9c61a] Unable to run read-tree -m! Error: exit status 128 - fatal: this operation must be run in a work tree > - fatal: this operation must be run in a work tree We need to clone a non-bare repository for `git read-tree -m` to work. https://github.com/go-gitea/gitea/commit/bb371aee6ecf5e570cdf7b5f7f0d6f47a607a325 adds support to create a non-bare cloned temporary upload repository. After cloning a non-bare temporary upload repository, we [set default index](https://github.com/go-gitea/gitea/blob/main/services/repository/files/cherry_pick.go#L37) (`git read-tree HEAD`). This operation ends up resetting the git index file (see investigation details below), due to which, we need to call `git update-index --refresh` afterward. Here's the diff of the index file before and after we execute SetDefaultIndex: https://www.diffchecker.com/hyOP3eJy/ Notice the **ctime**, **mtime** are set to 0 after SetDefaultIndex. You can reproduce the same behavior using these steps: ```bash $ git clone https://try.gitea.io/me-heer/test.git -s -b main $ cd test $ git read-tree HEAD $ git read-tree -m 1f085d7ed8 1f085d7ed8 9933caed00 error: Entry '1' not uptodate. Cannot merge. ``` After which, we can fix like this: ``` $ git update-index --refresh $ git read-tree -m 1f085d7ed8 1f085d7ed8 9933caed00 ``` --- models/fixtures/repo_unit.yml | 7 ++++ models/fixtures/repository.yml | 13 +++++++ models/fixtures/user.yml | 2 +- services/packages/cargo/index.go | 2 +- services/repository/files/cherry_pick.go | 5 ++- services/repository/files/diff.go | 2 +- services/repository/files/patch.go | 2 +- services/repository/files/temp_repo.go | 17 +++++++-- services/repository/files/update.go | 2 +- services/repository/files/upload.go | 2 +- .../user2/test_commit_revert.git/HEAD | 1 + .../user2/test_commit_revert.git/config | 8 +++++ .../user2/test_commit_revert.git/description | 1 + .../user2/test_commit_revert.git/info/exclude | 6 ++++ ...200c8e6707636a6cc3e0d8101fba08b19dcb91.idx | Bin 0 -> 1268 bytes ...00c8e6707636a6cc3e0d8101fba08b19dcb91.pack | Bin 0 -> 609 bytes .../user2/test_commit_revert.git/packed-refs | 3 ++ .../repo_mergecommit_revert_test.go | 34 ++++++++++++++++++ 18 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/HEAD create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/config create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/description create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/info/exclude create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.idx create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/objects/pack/pack-91200c8e6707636a6cc3e0d8101fba08b19dcb91.pack create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/packed-refs create mode 100644 tests/integration/repo_mergecommit_revert_test.go 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/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/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/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 0000000000000000000000000000000000000000..77bcbe7fb4caeaf783d375068e6ffad1a61ed6f9 GIT binary patch literal 1268 zcmexg;-AdGz`z8=v<8eo3&_PF_YD+g2HH(a%mOrj6c0=oumZz=pfMZJ4dh~WpkAtA ziT!>0sdIKX`Q+r7_O6Ur@4djIQr(9k=xzwdk9FE7To^R}r(d6a^sw8itW75x*G+gB zJLmjK_lrp}ojOnB{w=l+mP?F$x@Cip-r*TtF8d5W?KM9hE4Ed{`{q?8<@3(p#qS*Z z6?X6S>El699d>OwXZKwA@}K@YrklRMmHNzh>-92&^UoPf=J!P_8Ex$p5RAF4$7r)< z|M#umx9-w6^fNoBCjH=l=F9XikL7_y$T?t9-~gm0f!H62{{h2*2Ph^mQGur~ojo}# m=kS9Y0`j{!HqJdgaY~07L(uVgcZ<)l8XQVHe#P^wn?3-RA9Cve literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7271cdaeb87741f0ed005bdae83367b7e3dd76ff GIT binary patch literal 609 zcmWG=boORoU|<4b_8F2DbLRH>`W-e9Y58t>baI49%d8{yD`ORpu-;JE=(f!`Pg%9F zAf9`#t*c0&blku9iJsbyWO}4;yFP z%7iHn>OIYzR$HvRT%NA^8pWv~9j*;B@8BN$p*G1 z_wCH-hqq<^duMh%m^c1IzWwt1r&vRF{EV|RS@eskb64Y;*G!GP6?0}E{4=r8>CO>5 zV>RZ(oyTRC@bZXm;caN>*50C^x=|%mQP)`gYQXv>i(0rD^doqK=5Ygq@QfqhAq5fF ziJvnG&>DY92Q5*&f8<>p`?>?xR*O$FPdH&E=56J^q+*+ic!g3mS89{@ zv53icL`*t=JX*?svmmBHQQ!W~yxN55@}tS&{iixseOrBH-khy&zZLku_@3Fj^;FM) z{zvmR>y);J{Ym@CsKRK~v6QJ|jfw5tr$LTYkrdPCAY9!vsuCoctS>5sd>}s!5 z&*$#%zHj+Wn}^}W1io*UAmv784~*?~gJPg+!^9h?kOkiYS{K0T~q5@A}I(u?f&fy0)1mt&dY@B;~A^`875 Date: Tue, 16 Jan 2024 23:52:19 +0200 Subject: [PATCH 4/5] Bump `@github/relative-time-element` to 4.3.1 (#28819) - Fixes https://github.com/go-gitea/gitea/issues/28747 # Before ![image](https://github.com/go-gitea/gitea/assets/20454870/65d8dc84-680f-4c16-9aa1-b5986102e4e7) # After ![image](https://github.com/go-gitea/gitea/assets/20454870/7cb288e7-ebde-4e94-a10a-cac28d6bdcfd) Signed-off-by: Yarden Shoham --- 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 52f8c3f1a4e9a..5f4dc9bc2ba46 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", From 49eb16867728913d1eb2ced96e0b0b0a358f6ebe Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Wed, 17 Jan 2024 03:44:56 +0300 Subject: [PATCH 5/5] Retarget depending pulls when the parent branch is deleted (#28686) Sometimes you need to work on a feature which depends on another (unmerged) feature. In this case, you may create a PR based on that feature instead of the main branch. Currently, such PRs will be closed without the possibility to reopen in case the parent feature is merged and its branch is deleted. Automatic target branch change make life a lot easier in such cases. Github and Bitbucket behave in such way. Example: $PR_1$: main <- feature1 $PR_2$: feature1 <- feature2 Currently, merging $PR_1$ and deleting its branch leads to $PR_2$ being closed without the possibility to reopen. This is both annoying and loses the review history when you open a new PR. With this change, $PR_2$ will change its target branch to main ($PR_2$: main <- feature2) after $PR_1$ has been merged and its branch has been deleted. This behavior is enabled by default but can be disabled. For security reasons, this target branch change will not be executed when merging PRs targeting another repo. Fixes #27062 Fixes #18408 --------- Co-authored-by: Denys Konovalov Co-authored-by: delvh --- custom/conf/app.example.ini | 3 + .../config-cheat-sheet.en-us.md | 1 + modules/setting/repository.go | 3 + routers/api/v1/repo/pull.go | 4 + routers/web/repo/pull.go | 6 ++ services/pull/pull.go | 37 ++++++++ tests/integration/pull_create_test.go | 25 +++-- tests/integration/pull_merge_test.go | 95 ++++++++++++++++--- tests/integration/repo_activity_test.go | 8 +- 9 files changed, 158 insertions(+), 24 deletions(-) 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/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/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/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/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")