From daf2a4c047c88083d8820bdee9074357d5c5d7b7 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Wed, 22 May 2024 02:00:35 +0900 Subject: [PATCH] Fix wrong display of recently pushed notification (#25812) There's a bug in #25715: If user pushed a commit into another repo with same branch name, the no-related repo will display the recently pushed notification incorrectly. It is simple to fix this, we should match the repo id in the sql query. ![image](https://github.com/go-gitea/gitea/assets/18380374/9411a926-16f1-419e-a1b5-e953af38bab1) The latest commit is 2 weeks ago. ![image](https://github.com/go-gitea/gitea/assets/18380374/52f9ab22-4999-43ac-a86f-6d36fb1e0411) The notification comes from another repo with same branch name: ![image](https://github.com/go-gitea/gitea/assets/18380374/a26bc335-8e5b-4b9c-a965-c3dc3fa6f252) After: In forked repo: ![image](https://github.com/go-gitea/gitea/assets/18380374/ce6ffc35-deb7-4be7-8b09-184207392f32) New PR Link will redirect to the original repo: ![image](https://github.com/go-gitea/gitea/assets/18380374/7b98e76f-0c75-494c-9462-80cf9f98e786) In the original repo: ![image](https://github.com/go-gitea/gitea/assets/18380374/5f6a821b-e51a-4bbd-9980-d9eb94a3c847) New PR Link: ![image](https://github.com/go-gitea/gitea/assets/18380374/1ce8c879-9f11-4312-8c32-695d7d9af0df) In the same repo: ![image](https://github.com/go-gitea/gitea/assets/18380374/64b56073-4d0e-40c4-b8a0-80be7a775f69) New PR Link: ![image](https://github.com/go-gitea/gitea/assets/18380374/96e1b6a3-fb98-40ee-b2ee-648039fb0dcf) 08/15 Update: Follow #26257, added permission check and logic fix mentioned in https://github.com/go-gitea/gitea/pull/26257#discussion_r1294085203 2024/04/25 Update: Fix #30611 --------- Co-authored-by: silverwind Co-authored-by: Lunny Xiao Co-authored-by: wxiaoguang --- models/fixtures/branch.yml | 36 +++++ models/fixtures/issue_index.yml | 8 + models/fixtures/org_user.yml | 12 ++ models/fixtures/repository.yml | 2 +- models/fixtures/team.yml | 22 +++ models/fixtures/team_unit.yml | 18 +++ models/fixtures/team_user.yml | 12 ++ models/fixtures/user.yml | 8 +- models/git/branch.go | 142 ++++++++++++++--- models/git/branch_list.go | 19 +++ models/organization/org_user_test.go | 6 +- models/repo/repo_list.go | 6 + routers/web/repo/view.go | 26 ++- .../code/recently_pushed_new_branches.tmpl | 4 +- tests/integration/api_user_orgs_test.go | 26 +++ tests/integration/compare_test.go | 2 +- tests/integration/empty_repo_test.go | 13 ++ tests/integration/integration_test.go | 9 ++ tests/integration/pull_compare_test.go | 2 +- tests/integration/pull_create_test.go | 6 +- tests/integration/pull_merge_test.go | 30 ++-- tests/integration/pull_review_test.go | 2 +- tests/integration/pull_status_test.go | 6 +- tests/integration/repo_activity_test.go | 2 +- tests/integration/repo_branch_test.go | 148 +++++++++++++++++- tests/integration/repo_fork_test.go | 13 +- 26 files changed, 508 insertions(+), 72 deletions(-) diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml index 93003049c..c7bdff773 100644 --- a/models/fixtures/branch.yml +++ b/models/fixtures/branch.yml @@ -45,3 +45,39 @@ is_deleted: false deleted_by_id: 0 deleted_unix: 0 + +- + id: 5 + repo_id: 10 + name: 'master' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: 'Initial commit' + commit_time: 1489927679 + pusher_id: 12 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 6 + repo_id: 10 + name: 'outdated-new-branch' + commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d' + commit_message: 'add' + commit_time: 1489927679 + pusher_id: 12 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 14 + repo_id: 11 + name: 'master' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: 'Initial commit' + commit_time: 1489927679 + pusher_id: 13 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 diff --git a/models/fixtures/issue_index.yml b/models/fixtures/issue_index.yml index de6e95580..5aabc08e3 100644 --- a/models/fixtures/issue_index.yml +++ b/models/fixtures/issue_index.yml @@ -1,27 +1,35 @@ - group_id: 1 max_index: 5 + - group_id: 2 max_index: 2 + - group_id: 3 max_index: 2 + - group_id: 10 max_index: 1 + - group_id: 32 max_index: 2 + - group_id: 48 max_index: 1 + - group_id: 42 max_index: 1 + - group_id: 50 max_index: 1 + - group_id: 51 max_index: 1 diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml index a7fbcb2c5..cf21b84aa 100644 --- a/models/fixtures/org_user.yml +++ b/models/fixtures/org_user.yml @@ -117,3 +117,15 @@ uid: 40 org_id: 41 is_public: true + +- + id: 21 + uid: 12 + org_id: 25 + is_public: true + +- + id: 22 + uid: 2 + org_id: 35 + is_public: true diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index e5c6224c9..e1f1dd736 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -327,7 +327,7 @@ is_archived: false is_mirror: false status: 0 - is_fork: false + is_fork: true fork_id: 10 is_template: false template_id: 0 diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index 149fe9088..b549d0589 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -239,3 +239,25 @@ num_members: 2 includes_all_repositories: false can_create_org_repo: false + +- + id: 23 + org_id: 25 + lower_name: owners + name: Owners + authorize: 4 # owner + num_repos: 0 + num_members: 1 + includes_all_repositories: false + can_create_org_repo: true + +- + id: 24 + org_id: 35 + lower_name: team24 + name: team24 + authorize: 2 # write + num_repos: 0 + num_members: 1 + includes_all_repositories: true + can_create_org_repo: false diff --git a/models/fixtures/team_unit.yml b/models/fixtures/team_unit.yml index de0e8d738..110019eee 100644 --- a/models/fixtures/team_unit.yml +++ b/models/fixtures/team_unit.yml @@ -322,3 +322,21 @@ team_id: 22 type: 3 access_mode: 1 + +- + id: 55 + team_id: 18 + type: 1 # code + access_mode: 4 + +- + id: 56 + team_id: 23 + type: 1 # code + access_mode: 4 + +- + id: 57 + team_id: 24 + type: 1 # code + access_mode: 2 diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml index 02d57ae64..6b2d15327 100644 --- a/models/fixtures/team_user.yml +++ b/models/fixtures/team_user.yml @@ -147,3 +147,15 @@ org_id: 41 team_id: 22 uid: 39 + +- + id: 26 + org_id: 25 + team_id: 23 + uid: 12 + +- + id: 27 + org_id: 35 + team_id: 24 + uid: 2 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index a3de53550..8504d88ce 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -918,8 +918,8 @@ num_following: 0 num_stars: 0 num_repos: 0 - num_teams: 1 - num_members: 1 + num_teams: 2 + num_members: 2 visibility: 0 repo_admin_change_team_access: false theme: "" @@ -1289,8 +1289,8 @@ num_following: 0 num_stars: 0 num_repos: 0 - num_teams: 1 - num_members: 1 + num_teams: 2 + num_members: 2 visibility: 2 repo_admin_change_team_access: false theme: "" diff --git a/models/git/branch.go b/models/git/branch.go index 2979dff3d..c315d921f 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -10,9 +10,11 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -102,8 +104,9 @@ func (err ErrBranchesEqual) Unwrap() error { // for pagination, keyword search and filtering type Branch struct { ID int64 - RepoID int64 `xorm:"UNIQUE(s)"` - Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment + RepoID int64 `xorm:"UNIQUE(s)"` + Repo *repo_model.Repository `xorm:"-"` + Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment CommitID string CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line) PusherID int64 @@ -139,6 +142,14 @@ func (b *Branch) LoadPusher(ctx context.Context) (err error) { return err } +func (b *Branch) LoadRepo(ctx context.Context) (err error) { + if b.Repo != nil || b.RepoID == 0 { + return nil + } + b.Repo, err = repo_model.GetRepositoryByID(ctx, b.RepoID) + return err +} + func init() { db.RegisterModel(new(Branch)) db.RegisterModel(new(RenamedBranch)) @@ -400,24 +411,111 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str return committer.Commit() } -// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created -// except the indicate branch -func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) { - branches := make(BranchList, 0, 2) - subQuery := builder.Select("head_branch").From("pull_request"). - InnerJoin("issue", "issue.id = pull_request.issue_id"). - Where(builder.Eq{ - "pull_request.head_repo_id": repoID, - "issue.is_closed": false, - }) - err := db.GetEngine(ctx). - Where("pusher_id=? AND is_deleted=?", userID, false). - And("name <> ?", excludeBranchName). - And("repo_id = ?", repoID). - And("commit_time >= ?", time.Now().Add(-time.Hour*6).Unix()). - NotIn("name", subQuery). - OrderBy("branch.commit_time DESC"). - Limit(2). - Find(&branches) - return branches, err +type FindRecentlyPushedNewBranchesOptions struct { + Repo *repo_model.Repository + BaseRepo *repo_model.Repository + CommitAfterUnix int64 + MaxCount int +} + +type RecentlyPushedNewBranch struct { + BranchDisplayName string + BranchLink string + BranchCompareURL string + CommitTime timeutil.TimeStamp +} + +// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 2 hours which has no opened PRs created +// if opts.CommitAfterUnix is 0, we will find the branches that were committed to in the last 2 hours +// if opts.ListOptions is not set, we will only display top 2 latest branch +func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, opts *FindRecentlyPushedNewBranchesOptions) ([]*RecentlyPushedNewBranch, error) { + if doer == nil { + return []*RecentlyPushedNewBranch{}, nil + } + + // find all related repo ids + repoOpts := repo_model.SearchRepoOptions{ + Actor: doer, + Private: true, + AllPublic: false, // Include also all public repositories of users and public organisations + AllLimited: false, // Include also all public repositories of limited organisations + Fork: optional.Some(true), + ForkFrom: opts.BaseRepo.ID, + Archived: optional.Some(false), + } + repoCond := repo_model.SearchRepositoryCondition(&repoOpts).And(repo_model.AccessibleRepositoryCondition(doer, unit.TypeCode)) + if opts.Repo.ID == opts.BaseRepo.ID { + // should also include the base repo's branches + repoCond = repoCond.Or(builder.Eq{"id": opts.BaseRepo.ID}) + } else { + // in fork repo, we only detect the fork repo's branch + repoCond = repoCond.And(builder.Eq{"id": opts.Repo.ID}) + } + repoIDs := builder.Select("id").From("repository").Where(repoCond) + + if opts.CommitAfterUnix == 0 { + opts.CommitAfterUnix = time.Now().Add(-time.Hour * 2).Unix() + } + + baseBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch) + if err != nil { + return nil, err + } + + // find all related branches, these branches may already created PRs, we will check later + var branches []*Branch + if err := db.GetEngine(ctx). + Where(builder.And( + builder.Eq{ + "pusher_id": doer.ID, + "is_deleted": false, + }, + builder.Gte{"commit_time": opts.CommitAfterUnix}, + builder.In("repo_id", repoIDs), + // newly created branch have no changes, so skip them + builder.Neq{"commit_id": baseBranch.CommitID}, + )). + OrderBy(db.SearchOrderByRecentUpdated.String()). + Find(&branches); err != nil { + return nil, err + } + + newBranches := make([]*RecentlyPushedNewBranch, 0, len(branches)) + if opts.MaxCount == 0 { + // by default we display 2 recently pushed new branch + opts.MaxCount = 2 + } + for _, branch := range branches { + // whether branch have already created PR + count, err := db.GetEngine(ctx).Table("pull_request"). + // we should not only use branch name here, because if there are branches with same name in other repos, + // we can not detect them correctly + Where(builder.Eq{"head_repo_id": branch.RepoID, "head_branch": branch.Name}).Count() + if err != nil { + return nil, err + } + + // if no PR, we add to the result + if count == 0 { + if err := branch.LoadRepo(ctx); err != nil { + return nil, err + } + + branchDisplayName := branch.Name + if branch.Repo.ID != opts.BaseRepo.ID && branch.Repo.ID != opts.Repo.ID { + branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName) + } + newBranches = append(newBranches, &RecentlyPushedNewBranch{ + BranchDisplayName: branchDisplayName, + BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)), + BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name), + CommitTime: branch.CommitTime, + }) + } + if len(newBranches) == opts.MaxCount { + break + } + } + + return newBranches, nil } diff --git a/models/git/branch_list.go b/models/git/branch_list.go index 980bd7b4c..5c887461d 100644 --- a/models/git/branch_list.go +++ b/models/git/branch_list.go @@ -7,6 +7,7 @@ import ( "context" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/optional" @@ -59,6 +60,24 @@ func (branches BranchList) LoadPusher(ctx context.Context) error { return nil } +func (branches BranchList) LoadRepo(ctx context.Context) error { + ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) { + return branch.RepoID, branch.RepoID > 0 && branch.Repo == nil + }) + + reposMap := make(map[int64]*repo_model.Repository, len(ids)) + if err := db.GetEngine(ctx).In("id", ids).Find(&reposMap); err != nil { + return err + } + for _, branch := range branches { + if branch.RepoID <= 0 || branch.Repo != nil { + continue + } + branch.Repo = reposMap[branch.RepoID] + } + return nil +} + type FindBranchOptions struct { db.ListOptions RepoID int64 diff --git a/models/organization/org_user_test.go b/models/organization/org_user_test.go index 7924517f3..cf7acdf83 100644 --- a/models/organization/org_user_test.go +++ b/models/organization/org_user_test.go @@ -81,7 +81,7 @@ func TestUserListIsPublicMember(t *testing.T) { {3, map[int64]bool{2: true, 4: false, 28: true}}, {6, map[int64]bool{5: true, 28: true}}, {7, map[int64]bool{5: false}}, - {25, map[int64]bool{24: true}}, + {25, map[int64]bool{12: true, 24: true}}, {22, map[int64]bool{}}, } for _, v := range tt { @@ -108,8 +108,8 @@ func TestUserListIsUserOrgOwner(t *testing.T) { {3, map[int64]bool{2: true, 4: false, 28: false}}, {6, map[int64]bool{5: true, 28: false}}, {7, map[int64]bool{5: true}}, - {25, map[int64]bool{24: false}}, // ErrTeamNotExist - {22, map[int64]bool{}}, // No member + {25, map[int64]bool{12: true, 24: false}}, // ErrTeamNotExist + {22, map[int64]bool{}}, // No member } for _, v := range tt { t.Run(fmt.Sprintf("IsUserOrgOwnerOfOrgId%d", v.orgid), func(t *testing.T) { diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 987c7df9b..eacc98e22 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -175,6 +175,8 @@ type SearchRepoOptions struct { // True -> include just forks // False -> include just non-forks Fork optional.Option[bool] + // If Fork option is True, you can use this option to limit the forks of a special repo by repo id. + ForkFrom int64 // None -> include templates AND non-templates // True -> include just templates // False -> include just non-templates @@ -514,6 +516,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { cond = cond.And(builder.Eq{"is_fork": false}) } else { cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()}) + + if opts.ForkFrom > 0 && opts.Fork.Value() { + cond = cond.And(builder.Eq{"fork_id": opts.ForkFrom}) + } } } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index e4e6201c2..e1498c0d5 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -29,6 +29,7 @@ import ( "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issue_model "code.gitea.io/gitea/models/issues" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -1027,15 +1028,26 @@ func renderHomeCode(ctx *context.Context) { return } - showRecentlyPushedNewBranches := true - if ctx.Repo.Repository.IsMirror || - !ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypePullRequests) { - showRecentlyPushedNewBranches = false + opts := &git_model.FindRecentlyPushedNewBranchesOptions{ + Repo: ctx.Repo.Repository, + BaseRepo: ctx.Repo.Repository, } - if showRecentlyPushedNewBranches { - ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID, ctx.Repo.Repository.DefaultBranch) + if ctx.Repo.Repository.IsFork { + opts.BaseRepo = ctx.Repo.Repository.BaseRepo + } + + baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } + + if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror && + opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) && + baseRepoPerm.CanRead(unit_model.TypePullRequests) { + ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts) if err != nil { - ctx.ServerError("GetRecentlyPushedBranches", err) + ctx.ServerError("FindRecentlyPushedNewBranches", err) return } } diff --git a/templates/repo/code/recently_pushed_new_branches.tmpl b/templates/repo/code/recently_pushed_new_branches.tmpl index b808f413d..7f613fcba 100644 --- a/templates/repo/code/recently_pushed_new_branches.tmpl +++ b/templates/repo/code/recently_pushed_new_branches.tmpl @@ -2,10 +2,10 @@
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}} - {{$branchLink := HTMLFormat `%s` $.RepoLink (PathEscapeSegments .Name) .Name}} + {{$branchLink := HTMLFormat `%s` .BranchLink .BranchDisplayName}} {{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $branchLink $timeSince}}
- + {{ctx.Locale.Tr "repo.pulls.compare_changes"}}
diff --git a/tests/integration/api_user_orgs_test.go b/tests/integration/api_user_orgs_test.go index b6b4b6f2b..c656ded5a 100644 --- a/tests/integration/api_user_orgs_test.go +++ b/tests/integration/api_user_orgs_test.go @@ -29,6 +29,7 @@ func TestUserOrgs(t *testing.T) { org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) assert.Equal(t, []*api.Organization{ { @@ -55,6 +56,18 @@ func TestUserOrgs(t *testing.T) { Location: "", Visibility: "public", }, + { + ID: 35, + Name: org35.Name, + UserName: org35.Name, + FullName: org35.FullName, + Email: org35.Email, + AvatarURL: org35.AvatarLink(db.DefaultContext), + Description: "", + Website: "", + Location: "", + Visibility: "private", + }, }, orgs) // user itself should get it's org's he is a member of @@ -102,6 +115,7 @@ func TestMyOrgs(t *testing.T) { DecodeJSON(t, resp, &orgs) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) assert.Equal(t, []*api.Organization{ { @@ -128,5 +142,17 @@ func TestMyOrgs(t *testing.T) { Location: "", Visibility: "public", }, + { + ID: 35, + Name: org35.Name, + UserName: org35.Name, + FullName: org35.FullName, + Email: org35.Email, + AvatarURL: org35.AvatarLink(db.DefaultContext), + Description: "", + Website: "", + Location: "", + Visibility: "private", + }, }, orgs) } diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go index 7fb8dbc33..9f73ac80e 100644 --- a/tests/integration/compare_test.go +++ b/tests/integration/compare_test.go @@ -140,7 +140,7 @@ func TestCompareCodeExpand(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session = loginUser(t, user2.Name) - testRepoFork(t, session, user1.Name, repo.Name, user2.Name, "test_blob_excerpt-fork") + testRepoFork(t, session, user1.Name, repo.Name, user2.Name, "test_blob_excerpt-fork", "") testCreateBranch(t, session, user2.Name, "test_blob_excerpt-fork", "branch/main", "forked-branch", http.StatusSeeOther) testEditFile(t, session, user2.Name, "test_blob_excerpt-fork", "forked-branch", "README.md", strings.Repeat("a\n", 15)+"CHANGED\n"+strings.Repeat("a\n", 15)) diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index ea393a606..002aa5600 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -6,9 +6,11 @@ package integration import ( "bytes" "encoding/base64" + "fmt" "io" "mime/multipart" "net/http" + "net/http/httptest" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -24,6 +26,17 @@ import ( "github.com/stretchr/testify/assert" ) +func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) *httptest.ResponseRecorder { + url := fmt.Sprintf("/%s/%s/_new/%s", user, repo, branch) + req := NewRequestWithValues(t, "POST", url, map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "commit_choice": "direct", + "tree_path": treePath, + "content": content, + }) + return session.MakeRequest(t, req, http.StatusSeeOther) +} + func TestEmptyRepo(t *testing.T) { defer tests.PrepareTestEnv(t)() subPaths := []string{ diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index f9bd352b6..18f415083 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -485,6 +485,7 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile assert.True(t, result.Valid()) } +// GetCSRF returns CSRF token from body func GetCSRF(t testing.TB, session *TestSession, urlStr string) string { t.Helper() req := NewRequest(t, "GET", urlStr) @@ -492,3 +493,11 @@ func GetCSRF(t testing.TB, session *TestSession, urlStr string) string { doc := NewHTMLParser(t, resp.Body) return doc.GetCSRF() } + +// GetCSRFFrom returns CSRF token from body +func GetCSRFFromCookie(t testing.TB, session *TestSession, urlStr string) string { + t.Helper() + req := NewRequest(t, "GET", urlStr) + session.MakeRequest(t, req, http.StatusOK) + return session.GetCookie("_csrf").Value +} diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go index 39d9103df..aed699fd2 100644 --- a/tests/integration/pull_compare_test.go +++ b/tests/integration/pull_compare_test.go @@ -45,7 +45,7 @@ func TestPullCompare(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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") testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title") diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 7add8e1db..5a06a7817 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -85,7 +85,7 @@ func testPullCreateDirectly(t *testing.T, session *TestSession, baseRepoOwner, b func TestPullCreate(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master", "This is a pull title") @@ -113,7 +113,7 @@ func TestPullCreate(t *testing.T) { func TestPullCreate_TitleEscape(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master", "XSS PR") @@ -177,7 +177,7 @@ func TestPullBranchDelete(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master1", "This is a pull title") diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 979c40838..3e7054c7e 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -95,7 +95,7 @@ func TestPullMerge(t *testing.T) { hookTasksLenBefore := len(hookTasks) session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master", "This is a pull title") @@ -117,7 +117,7 @@ func TestPullRebase(t *testing.T) { hookTasksLenBefore := len(hookTasks) session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master", "This is a pull title") @@ -139,7 +139,7 @@ func TestPullRebaseMerge(t *testing.T) { hookTasksLenBefore := len(hookTasks) session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master", "This is a pull title") @@ -161,7 +161,7 @@ func TestPullSquash(t *testing.T) { hookTasksLenBefore := len(hookTasks) session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n") @@ -180,7 +180,7 @@ func TestPullSquash(t *testing.T) { func TestPullCleanUpAfterMerge(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "feature/test", "This is a pull title") @@ -215,7 +215,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { func TestCantMergeWorkInProgress(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master", "[wip] This is a pull title") @@ -234,7 +234,7 @@ func TestCantMergeWorkInProgress(t *testing.T) { func TestCantMergeConflict(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") @@ -280,7 +280,7 @@ func TestCantMergeConflict(t *testing.T) { func TestCantMergeUnrelated(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") // Now we want to create a commit on a branch that is totally unrelated to our current head @@ -375,7 +375,7 @@ func TestCantMergeUnrelated(t *testing.T) { func TestFastForwardOnlyMerge(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "update", "README.md", "Hello, World 2\n") // Use API to create a pr from update to master @@ -416,7 +416,7 @@ func TestFastForwardOnlyMerge(t *testing.T) { func TestCantFastForwardOnlyMergeDiverging(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "diverging", "README.md", "Hello, World diverged\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World 2\n") @@ -539,7 +539,7 @@ 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") + 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") @@ -568,7 +568,7 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) { 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") + 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)") @@ -599,7 +599,7 @@ func TestPullMergeIndexerNotifier(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { // create a pull request session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") createPullResp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "Indexer notifier test pull") @@ -676,7 +676,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { session := loginUser(t, "user1") user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) forkedName := "repo1-1" - testRepoFork(t, session, "user2", "repo1", "user1", forkedName) + testRepoFork(t, session, "user2", "repo1", "user1", forkedName, "") defer func() { testDeleteRepository(t, session, "user1", forkedName) }() @@ -759,7 +759,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) { session := loginUser(t, "user1") user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) forkedName := "repo1-2" - testRepoFork(t, session, "user2", "repo1", "user1", forkedName) + testRepoFork(t, session, "user2", "repo1", "user1", forkedName, "") defer func() { testDeleteRepository(t, session, "user1", forkedName) }() diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index df5d7b38e..5ecf3ef46 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -186,7 +186,7 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { user2Session := loginUser(t, "user2") // Have user1 create a fork of repo1. - testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1", "") t.Run("Submit approve/reject review on merged PR", func(t *testing.T) { // Create a merged PR (made by user1) in the upstream repo1. diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index 80eea3451..26e1baeb1 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -23,7 +23,7 @@ import ( func TestPullCreate_CommitStatus(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") url := path.Join("user1", "repo1", "compare", "master...status1") @@ -122,7 +122,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) { // so we need to have this meta commit also in develop branch. onGiteaRun(t, func(t *testing.T, u *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") testEditFileToNewBranch(t, session, "user1", "repo1", "status1", "status1", "README.md", "# repo1\n\nDescription for repo1") @@ -147,7 +147,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) { func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther) url := path.Join("user1", "repo1", "compare", "master...status1") req := NewRequestWithValues(t, "POST", url, diff --git a/tests/integration/repo_activity_test.go b/tests/integration/repo_activity_test.go index 792554db4..b04560379 100644 --- a/tests/integration/repo_activity_test.go +++ b/tests/integration/repo_activity_test.go @@ -20,7 +20,7 @@ func TestRepoActivity(t *testing.T) { session := loginUser(t, "user1") // Create PRs (1 merged & 2 proposed) - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + 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", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index baa8da4b7..d1bc9198c 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -4,26 +4,37 @@ package integration import ( + "fmt" "net/http" "net/url" "path" "strings" "testing" + auth_model "code.gitea.io/gitea/models/auth" + org_model "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/tests" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" ) func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { var csrf string if expectedStatus == http.StatusNotFound { - csrf = GetCSRF(t, session, path.Join(user, repo, "src/branch/master")) + // src/branch/branch_name may not container "_csrf" input, + // so we need to get it from cookies not from body + csrf = GetCSRFFromCookie(t, session, path.Join(user, repo, "src/branch/master")) } else { - csrf = GetCSRF(t, session, path.Join(user, repo, "src", oldRefSubURL)) + csrf = GetCSRFFromCookie(t, session, path.Join(user, repo, "src", oldRefSubURL)) } req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefSubURL), map[string]string{ "_csrf": csrf, @@ -145,3 +156,136 @@ func TestCreateBranchInvalidCSRF(t *testing.T) { strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), ) } + +func prepareBranch(t *testing.T, session *TestSession, repo *repo_model.Repository) { + baseRefSubURL := fmt.Sprintf("branch/%s", repo.DefaultBranch) + + // create branch with no new commit + testCreateBranch(t, session, repo.OwnerName, repo.Name, baseRefSubURL, "no-commit", http.StatusSeeOther) + + // create branch with commit + testCreateBranch(t, session, repo.OwnerName, repo.Name, baseRefSubURL, "new-commit", http.StatusSeeOther) + testAPINewFile(t, session, repo.OwnerName, repo.Name, "new-commit", "new-commit.txt", "new-commit") + + // create deleted branch + testCreateBranch(t, session, repo.OwnerName, repo.Name, "branch/new-commit", "deleted-branch", http.StatusSeeOther) + testUIDeleteBranch(t, session, repo.OwnerName, repo.Name, "deleted-branch") +} + +func testCreatePullToDefaultBranch(t *testing.T, session *TestSession, baseRepo, headRepo *repo_model.Repository, headBranch, title string) string { + srcRef := headBranch + if baseRepo.ID != headRepo.ID { + srcRef = fmt.Sprintf("%s/%s:%s", headRepo.OwnerName, headRepo.Name, headBranch) + } + resp := testPullCreate(t, session, baseRepo.OwnerName, baseRepo.Name, false, baseRepo.DefaultBranch, srcRef, title) + elem := strings.Split(test.RedirectURL(resp), "/") + // return pull request ID + return elem[4] +} + +func prepareRepoPR(t *testing.T, baseSession, headSession *TestSession, baseRepo, headRepo *repo_model.Repository) { + // create opening PR + testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "opening-pr", http.StatusSeeOther) + testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "opening-pr", "opening pr") + + // create closed PR + testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "closed-pr", http.StatusSeeOther) + prID := testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "closed-pr", "closed pr") + testIssueClose(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID) + + // create closed PR with deleted branch + testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "closed-pr-deleted", http.StatusSeeOther) + prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "closed-pr-deleted", "closed pr with deleted branch") + testIssueClose(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID) + testUIDeleteBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "closed-pr-deleted") + + // create merged PR + testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "merged-pr", http.StatusSeeOther) + prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "merged-pr", "merged pr") + testAPINewFile(t, headSession, headRepo.OwnerName, headRepo.Name, "merged-pr", fmt.Sprintf("new-commit-%s.txt", headRepo.Name), "new-commit") + testPullMerge(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID, repo_model.MergeStyleRebaseMerge, false) + + // create merged PR with deleted branch + testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "merged-pr-deleted", http.StatusSeeOther) + prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "merged-pr-deleted", "merged pr with deleted branch") + testAPINewFile(t, headSession, headRepo.OwnerName, headRepo.Name, "merged-pr-deleted", fmt.Sprintf("new-commit-%s-2.txt", headRepo.Name), "new-commit") + testPullMerge(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID, repo_model.MergeStyleRebaseMerge, true) +} + +func checkRecentlyPushedNewBranches(t *testing.T, session *TestSession, repoPath string, expected []string) { + branches := make([]string, 0, 2) + req := NewRequest(t, "GET", repoPath) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + doc.doc.Find(".ui.positive.message div a").Each(func(index int, branch *goquery.Selection) { + branches = append(branches, branch.Text()) + }) + assert.Equal(t, expected, branches) +} + +func TestRecentlyPushedNewBranches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user1Session := loginUser(t, "user1") + user2Session := loginUser(t, "user2") + user12Session := loginUser(t, "user12") + user13Session := loginUser(t, "user13") + + // prepare branch and PRs in original repo + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + prepareBranch(t, user12Session, repo10) + prepareRepoPR(t, user12Session, user12Session, repo10, repo10) + + // outdated new branch should not be displayed + checkRecentlyPushedNewBranches(t, user12Session, "user12/repo10", []string{"new-commit"}) + + // create a fork repo in public org + testRepoFork(t, user12Session, repo10.OwnerName, repo10.Name, "org25", "org25_fork_repo10", "new-commit") + orgPublicForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 25, Name: "org25_fork_repo10"}) + prepareRepoPR(t, user12Session, user12Session, repo10, orgPublicForkRepo) + + // user12 is the owner of the repo10 and the organization org25 + // in repo10, user12 has opening/closed/merged pr and closed/merged pr with deleted branch + checkRecentlyPushedNewBranches(t, user12Session, "user12/repo10", []string{"org25/org25_fork_repo10:new-commit", "new-commit"}) + + userForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) + testCtx := NewAPITestContext(t, repo10.OwnerName, repo10.Name, auth_model.AccessTokenScopeWriteRepository) + t.Run("AddUser13AsCollaborator", doAPIAddCollaborator(testCtx, "user13", perm.AccessModeWrite)) + prepareBranch(t, user13Session, userForkRepo) + prepareRepoPR(t, user13Session, user13Session, repo10, userForkRepo) + + // create branch with same name in different repo by user13 + testCreateBranch(t, user13Session, repo10.OwnerName, repo10.Name, "branch/new-commit", "same-name-branch", http.StatusSeeOther) + testCreateBranch(t, user13Session, userForkRepo.OwnerName, userForkRepo.Name, "branch/new-commit", "same-name-branch", http.StatusSeeOther) + testCreatePullToDefaultBranch(t, user13Session, repo10, userForkRepo, "same-name-branch", "same name branch pr") + + // user13 pushed 2 branches with the same name in repo10 and repo11 + // and repo11's branch has a pr, but repo10's branch doesn't + // in this case, we should get repo10's branch but not repo11's branch + checkRecentlyPushedNewBranches(t, user13Session, "user12/repo10", []string{"same-name-branch", "user13/repo11:new-commit"}) + + // create a fork repo in private org + testRepoFork(t, user1Session, repo10.OwnerName, repo10.Name, "private_org35", "org35_fork_repo10", "new-commit") + orgPrivateForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 35, Name: "org35_fork_repo10"}) + prepareRepoPR(t, user1Session, user1Session, repo10, orgPrivateForkRepo) + + // user1 is the owner of private_org35 and no write permission to repo10 + // so user1 can only see the branch in org35_fork_repo10 + checkRecentlyPushedNewBranches(t, user1Session, "user12/repo10", []string{"private_org35/org35_fork_repo10:new-commit"}) + + // user2 push a branch in private_org35 + testCreateBranch(t, user2Session, orgPrivateForkRepo.OwnerName, orgPrivateForkRepo.Name, "branch/new-commit", "user-read-permission", http.StatusSeeOther) + // convert write permission to read permission for code unit + token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", 24), &api.EditTeamOption{ + Name: "team24", + UnitsMap: map[string]string{"repo.code": "read"}, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + teamUnit := unittest.AssertExistsAndLoadBean(t, &org_model.TeamUnit{TeamID: 24, Type: unit.TypeCode}) + assert.Equal(t, perm.AccessModeRead, teamUnit.AccessMode) + // user2 can see the branch as it is created by user2 + checkRecentlyPushedNewBranches(t, user2Session, "user12/repo10", []string{"private_org35/org35_fork_repo10:user-read-permission"}) + }) +} diff --git a/tests/integration/repo_fork_test.go b/tests/integration/repo_fork_test.go index ca5d61ecc..feebebf06 100644 --- a/tests/integration/repo_fork_test.go +++ b/tests/integration/repo_fork_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/assert" ) -func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName string) *httptest.ResponseRecorder { +func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName, forkBranch string) *httptest.ResponseRecorder { forkOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: forkOwnerName}) // Step0: check the existence of the to-fork repo @@ -41,9 +41,10 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO _, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", forkOwner.ID)).Attr("data-value") assert.True(t, exists, fmt.Sprintf("Fork owner '%s' is not present in select box", forkOwnerName)) req = NewRequestWithValues(t, "POST", link, map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "uid": fmt.Sprintf("%d", forkOwner.ID), - "repo_name": forkRepoName, + "_csrf": htmlDoc.GetCSRF(), + "uid": fmt.Sprintf("%d", forkOwner.ID), + "repo_name": forkRepoName, + "fork_single_branch": forkBranch, }) session.MakeRequest(t, req, http.StatusSeeOther) @@ -57,13 +58,13 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO func TestRepoFork(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") } func TestRepoForkToOrg(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user2") - testRepoFork(t, session, "user2", "repo1", "org3", "repo1") + testRepoFork(t, session, "user2", "repo1", "org3", "repo1", "") // Check that no more forking is allowed as user2 owns repository // and org3 organization that owner user2 is also now has forked this repository