]> source.dussan.org Git - gitea.git/commitdiff
Fix wrong display of recently pushed notification (#25812) (#31043)
authoryp05327 <576951401@qq.com>
Thu, 23 May 2024 04:14:26 +0000 (13:14 +0900)
committerGitHub <noreply@github.com>
Thu, 23 May 2024 04:14:26 +0000 (04:14 +0000)
Backport #25812

~~ps: removed some new codes in `tests/integration/pull_merge_test.go`~~

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
26 files changed:
models/fixtures/branch.yml
models/fixtures/issue_index.yml
models/fixtures/org_user.yml
models/fixtures/repository.yml
models/fixtures/team.yml
models/fixtures/team_unit.yml
models/fixtures/team_user.yml
models/fixtures/user.yml
models/git/branch.go
models/git/branch_list.go
models/organization/org_user_test.go
models/repo/repo_list.go
routers/web/repo/view.go
templates/repo/code/recently_pushed_new_branches.tmpl
tests/integration/api_user_orgs_test.go
tests/integration/compare_test.go
tests/integration/empty_repo_test.go
tests/integration/integration_test.go
tests/integration/pull_compare_test.go
tests/integration/pull_create_test.go
tests/integration/pull_merge_test.go
tests/integration/pull_review_test.go
tests/integration/pull_status_test.go
tests/integration/repo_activity_test.go
tests/integration/repo_branch_test.go
tests/integration/repo_fork_test.go

index 93003049c67fbfbe9f4c9d8cf7a28214181ea7e1..c7bdff773399537c5377a17d78b2c59b3d7a3340 100644 (file)
   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
index de6e955804ab6fd94f51f9928ecc91ff85f66515..5aabc08e388c5a76b59dc36b25eb41a4e656e937 100644 (file)
@@ -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
index a7fbcb2c5a3173b2ccdefb851772dded68337964..cf21b84aa9fc57033517863f7f7edeb09ea95e88 100644 (file)
   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
index e5c6224c96ff31c57eee33819e5ebd2b82470103..e1f1dd73679b5b9a016e68e844221305a1e0e432 100644 (file)
   is_archived: false
   is_mirror: false
   status: 0
-  is_fork: false
+  is_fork: true
   fork_id: 10
   is_template: false
   template_id: 0
index 149fe90888b29c4c3f84fcaffc45fa3bd927252f..b549d0589bc9e7d0a3882b7cfc5e2a42e9d0c7b8 100644 (file)
   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
index de0e8d738bbf825cf4491f805c6bbc9836c75fda..110019eee30cfb8c7f30ce0bcc384938a2f367cc 100644 (file)
   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
index 02d57ae644fd03551275a120e5337f18dff5457e..6b2d153278ac51903ced3d91c6d6bcb944f1458c 100644 (file)
   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
index a3de535508b39120e34ba2a67d3f4ee75c4f54b2..8504d88ce59951c3d834a1d5cb8199be7668ab25 100644 (file)
   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: ""
   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: ""
index 2979dff3d211e91300a1b0d9b6aedf4e6dfc516c..c315d921ffb8859404cb49702b88155238ae35c6 100644 (file)
@@ -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
 }
index 980bd7b4c9df859a350d2c5efb97e59be096fba7..5c887461d5a0708573b036c8a8b81cb394182139 100644 (file)
@@ -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
index 7924517f312095c3482be005b296ed340f19ad8d..cf7acdf83ba799aecaa7f9512ba2a060fb5ae059 100644 (file)
@@ -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) {
index 987c7df9b0eb0fb956b1973ac05069b646eb518d..eacc98e2225d19b93e4e16d4415cc6eb2bbdba1e 100644 (file)
@@ -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})
+                       }
                }
        }
 
index e4e6201c24abd4b0e72e486b3b415d307bd26161..e1498c0d581e04686ce800840eeac30bc6ae2d70 100644 (file)
@@ -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
                        }
                }
index b808f413d352fc596cc3b02ed15856f60fdada71..7f613fcba7eae9854e0e5cb96a98c78c94c2772e 100644 (file)
@@ -2,10 +2,10 @@
        <div class="ui positive message tw-flex tw-items-center">
                <div class="tw-flex-1">
                        {{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}}
-                       {{$branchLink := HTMLFormat `<a href="%s/src/branch/%s">%s</a>` $.RepoLink (PathEscapeSegments .Name) .Name}}
+                       {{$branchLink := HTMLFormat `<a href="%s">%s</a>` .BranchLink .BranchDisplayName}}
                        {{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $branchLink $timeSince}}
                </div>
-               <a role="button" class="ui compact green button tw-m-0" href="{{$.Repository.ComposeBranchCompareURL $.Repository.BaseRepo .Name}}">
+               <a role="button" class="ui compact green button tw-m-0" href="{{.BranchCompareURL}}">
                        {{ctx.Locale.Tr "repo.pulls.compare_changes"}}
                </a>
        </div>
index b6b4b6f2b2d2ed0ca63fba23aa6febc42c515c4f..c656ded5ae9638222760ad89ec29122344dea081 100644 (file)
@@ -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)
 }
index 7fb8dbc3327ab69550612d32781afa9d4765f402..9f73ac80e2f9a553906bd021778bd413eaba26d6 100644 (file)
@@ -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))
 
index ea393a606167abc2a0a44d19cff6867e8ace5f6a..002aa5600e08b97a001ed15566eaa7ed485368b0 100644 (file)
@@ -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{
index f9bd352b6221c803445279632c4be5e4aad320cf..18f415083c9e625c590e41e27246bbe0b2034727 100644 (file)
@@ -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
+}
index 39d9103dfd9f7aa5bb9c4fe7e06eccafbf5f71eb..aed699fd20018fd27f516bb5b42ede4cbabec1f2 100644 (file)
@@ -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")
index 7add8e1db65164ad6be8b55e95537c408f2d41d1..5a06a7817f661916c6c2a2c6941cc5e8565acf0b 100644 (file)
@@ -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", "<i>XSS PR</i>")
 
@@ -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")
index 979c408388760e0132bda207a0d00f96fb35d508..3e7054c7e83d2f3f1ae45917fa0f4841f43cd6ce 100644 (file)
@@ -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)
                }()
index df5d7b38ea49aaf69f7ec4fe320a021769bd185f..5ecf3ef46918b2b354db62e60f86327057221dcb 100644 (file)
@@ -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.
index 80eea34513f32c064554a56c5650dbd384e243fd..26e1baeb1130516c87f491ee93a9591ebfc19081 100644 (file)
@@ -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,
index 792554db4bc93fae81e0f153b637610197c5f055..b04560379d2cdab6a85b33cd079aee7acb20b052 100644 (file)
@@ -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), "/")
index baa8da4b7595624ab37c112a83fddab4acf46b60..d1bc9198c32f49064aa412317603404e9548bcc2 100644 (file)
@@ -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"})
+       })
+}
index ca5d61ecc2a90a3098ab716537101bdabafc39dd..feebebf062081cc060e62e7390016d76a8fe7f73 100644 (file)
@@ -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