code.gitea.io/gitea@v1.22.3/tests/integration/repo_branch_test.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"net/url"
    10  	"path"
    11  	"strings"
    12  	"testing"
    13  
    14  	auth_model "code.gitea.io/gitea/models/auth"
    15  	org_model "code.gitea.io/gitea/models/organization"
    16  	"code.gitea.io/gitea/models/perm"
    17  	repo_model "code.gitea.io/gitea/models/repo"
    18  	"code.gitea.io/gitea/models/unit"
    19  	"code.gitea.io/gitea/models/unittest"
    20  	api "code.gitea.io/gitea/modules/structs"
    21  	"code.gitea.io/gitea/modules/test"
    22  	"code.gitea.io/gitea/modules/translation"
    23  	"code.gitea.io/gitea/tests"
    24  
    25  	"github.com/PuerkitoBio/goquery"
    26  	"github.com/stretchr/testify/assert"
    27  )
    28  
    29  func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string {
    30  	var csrf string
    31  	if expectedStatus == http.StatusNotFound {
    32  		// src/branch/branch_name may not container "_csrf" input,
    33  		// so we need to get it from cookies not from body
    34  		csrf = GetCSRFFromCookie(t, session, path.Join(user, repo, "src/branch/master"))
    35  	} else {
    36  		csrf = GetCSRFFromCookie(t, session, path.Join(user, repo, "src", oldRefSubURL))
    37  	}
    38  	req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefSubURL), map[string]string{
    39  		"_csrf":           csrf,
    40  		"new_branch_name": newBranchName,
    41  	})
    42  	resp := session.MakeRequest(t, req, expectedStatus)
    43  	if expectedStatus != http.StatusSeeOther {
    44  		return ""
    45  	}
    46  	return test.RedirectURL(resp)
    47  }
    48  
    49  func TestCreateBranch(t *testing.T) {
    50  	onGiteaRun(t, testCreateBranches)
    51  }
    52  
    53  func testCreateBranches(t *testing.T, giteaURL *url.URL) {
    54  	tests := []struct {
    55  		OldRefSubURL   string
    56  		NewBranch      string
    57  		CreateRelease  string
    58  		FlashMessage   string
    59  		ExpectedStatus int
    60  	}{
    61  		{
    62  			OldRefSubURL:   "branch/master",
    63  			NewBranch:      "feature/test1",
    64  			ExpectedStatus: http.StatusSeeOther,
    65  			FlashMessage:   translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature/test1"),
    66  		},
    67  		{
    68  			OldRefSubURL:   "branch/master",
    69  			NewBranch:      "",
    70  			ExpectedStatus: http.StatusSeeOther,
    71  			FlashMessage:   translation.NewLocale("en-US").TrString("form.NewBranchName") + translation.NewLocale("en-US").TrString("form.require_error"),
    72  		},
    73  		{
    74  			OldRefSubURL:   "branch/master",
    75  			NewBranch:      "feature=test1",
    76  			ExpectedStatus: http.StatusSeeOther,
    77  			FlashMessage:   translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature=test1"),
    78  		},
    79  		{
    80  			OldRefSubURL:   "branch/master",
    81  			NewBranch:      strings.Repeat("b", 101),
    82  			ExpectedStatus: http.StatusSeeOther,
    83  			FlashMessage:   translation.NewLocale("en-US").TrString("form.NewBranchName") + translation.NewLocale("en-US").TrString("form.max_size_error", "100"),
    84  		},
    85  		{
    86  			OldRefSubURL:   "branch/master",
    87  			NewBranch:      "master",
    88  			ExpectedStatus: http.StatusSeeOther,
    89  			FlashMessage:   translation.NewLocale("en-US").TrString("repo.branch.branch_already_exists", "master"),
    90  		},
    91  		{
    92  			OldRefSubURL:   "branch/master",
    93  			NewBranch:      "master/test",
    94  			ExpectedStatus: http.StatusSeeOther,
    95  			FlashMessage:   translation.NewLocale("en-US").TrString("repo.branch.branch_name_conflict", "master/test", "master"),
    96  		},
    97  		{
    98  			OldRefSubURL:   "commit/acd1d892867872cb47f3993468605b8aa59aa2e0",
    99  			NewBranch:      "feature/test2",
   100  			ExpectedStatus: http.StatusNotFound,
   101  		},
   102  		{
   103  			OldRefSubURL:   "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d",
   104  			NewBranch:      "feature/test3",
   105  			ExpectedStatus: http.StatusSeeOther,
   106  			FlashMessage:   translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature/test3"),
   107  		},
   108  		{
   109  			OldRefSubURL:   "branch/master",
   110  			NewBranch:      "v1.0.0",
   111  			CreateRelease:  "v1.0.0",
   112  			ExpectedStatus: http.StatusSeeOther,
   113  			FlashMessage:   translation.NewLocale("en-US").TrString("repo.branch.tag_collision", "v1.0.0"),
   114  		},
   115  		{
   116  			OldRefSubURL:   "tag/v1.0.0",
   117  			NewBranch:      "feature/test4",
   118  			CreateRelease:  "v1.0.1",
   119  			ExpectedStatus: http.StatusSeeOther,
   120  			FlashMessage:   translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature/test4"),
   121  		},
   122  	}
   123  	for _, test := range tests {
   124  		session := loginUser(t, "user2")
   125  		if test.CreateRelease != "" {
   126  			createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false)
   127  		}
   128  		redirectURL := testCreateBranch(t, session, "user2", "repo1", test.OldRefSubURL, test.NewBranch, test.ExpectedStatus)
   129  		if test.ExpectedStatus == http.StatusSeeOther {
   130  			req := NewRequest(t, "GET", redirectURL)
   131  			resp := session.MakeRequest(t, req, http.StatusOK)
   132  			htmlDoc := NewHTMLParser(t, resp.Body)
   133  			assert.Contains(t,
   134  				strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
   135  				test.FlashMessage,
   136  			)
   137  		}
   138  	}
   139  }
   140  
   141  func TestCreateBranchInvalidCSRF(t *testing.T) {
   142  	defer tests.PrepareTestEnv(t)()
   143  	session := loginUser(t, "user2")
   144  	req := NewRequestWithValues(t, "POST", "user2/repo1/branches/_new/branch/master", map[string]string{
   145  		"_csrf":           "fake_csrf",
   146  		"new_branch_name": "test",
   147  	})
   148  	resp := session.MakeRequest(t, req, http.StatusBadRequest)
   149  	assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
   150  }
   151  
   152  func prepareBranch(t *testing.T, session *TestSession, repo *repo_model.Repository) {
   153  	baseRefSubURL := fmt.Sprintf("branch/%s", repo.DefaultBranch)
   154  
   155  	// create branch with no new commit
   156  	testCreateBranch(t, session, repo.OwnerName, repo.Name, baseRefSubURL, "no-commit", http.StatusSeeOther)
   157  
   158  	// create branch with commit
   159  	testCreateBranch(t, session, repo.OwnerName, repo.Name, baseRefSubURL, "new-commit", http.StatusSeeOther)
   160  	testAPINewFile(t, session, repo.OwnerName, repo.Name, "new-commit", "new-commit.txt", "new-commit")
   161  
   162  	// create deleted branch
   163  	testCreateBranch(t, session, repo.OwnerName, repo.Name, "branch/new-commit", "deleted-branch", http.StatusSeeOther)
   164  	testUIDeleteBranch(t, session, repo.OwnerName, repo.Name, "deleted-branch")
   165  }
   166  
   167  func testCreatePullToDefaultBranch(t *testing.T, session *TestSession, baseRepo, headRepo *repo_model.Repository, headBranch, title string) string {
   168  	srcRef := headBranch
   169  	if baseRepo.ID != headRepo.ID {
   170  		srcRef = fmt.Sprintf("%s/%s:%s", headRepo.OwnerName, headRepo.Name, headBranch)
   171  	}
   172  	resp := testPullCreate(t, session, baseRepo.OwnerName, baseRepo.Name, false, baseRepo.DefaultBranch, srcRef, title)
   173  	elem := strings.Split(test.RedirectURL(resp), "/")
   174  	// return pull request ID
   175  	return elem[4]
   176  }
   177  
   178  func prepareRepoPR(t *testing.T, baseSession, headSession *TestSession, baseRepo, headRepo *repo_model.Repository) {
   179  	// create opening PR
   180  	testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "opening-pr", http.StatusSeeOther)
   181  	testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "opening-pr", "opening pr")
   182  
   183  	// create closed PR
   184  	testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "closed-pr", http.StatusSeeOther)
   185  	prID := testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "closed-pr", "closed pr")
   186  	testIssueClose(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID)
   187  
   188  	// create closed PR with deleted branch
   189  	testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "closed-pr-deleted", http.StatusSeeOther)
   190  	prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "closed-pr-deleted", "closed pr with deleted branch")
   191  	testIssueClose(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID)
   192  	testUIDeleteBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "closed-pr-deleted")
   193  
   194  	// create merged PR
   195  	testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "merged-pr", http.StatusSeeOther)
   196  	prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "merged-pr", "merged pr")
   197  	testAPINewFile(t, headSession, headRepo.OwnerName, headRepo.Name, "merged-pr", fmt.Sprintf("new-commit-%s.txt", headRepo.Name), "new-commit")
   198  	testPullMerge(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID, repo_model.MergeStyleRebaseMerge, false)
   199  
   200  	// create merged PR with deleted branch
   201  	testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "merged-pr-deleted", http.StatusSeeOther)
   202  	prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "merged-pr-deleted", "merged pr with deleted branch")
   203  	testAPINewFile(t, headSession, headRepo.OwnerName, headRepo.Name, "merged-pr-deleted", fmt.Sprintf("new-commit-%s-2.txt", headRepo.Name), "new-commit")
   204  	testPullMerge(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID, repo_model.MergeStyleRebaseMerge, true)
   205  }
   206  
   207  func checkRecentlyPushedNewBranches(t *testing.T, session *TestSession, repoPath string, expected []string) {
   208  	branches := make([]string, 0, 2)
   209  	req := NewRequest(t, "GET", repoPath)
   210  	resp := session.MakeRequest(t, req, http.StatusOK)
   211  	doc := NewHTMLParser(t, resp.Body)
   212  	doc.doc.Find(".ui.positive.message div a").Each(func(index int, branch *goquery.Selection) {
   213  		branches = append(branches, branch.Text())
   214  	})
   215  	assert.Equal(t, expected, branches)
   216  }
   217  
   218  func TestRecentlyPushedNewBranches(t *testing.T) {
   219  	defer tests.PrepareTestEnv(t)()
   220  
   221  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
   222  		user1Session := loginUser(t, "user1")
   223  		user2Session := loginUser(t, "user2")
   224  		user12Session := loginUser(t, "user12")
   225  		user13Session := loginUser(t, "user13")
   226  
   227  		// prepare branch and PRs in original repo
   228  		repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
   229  		prepareBranch(t, user12Session, repo10)
   230  		prepareRepoPR(t, user12Session, user12Session, repo10, repo10)
   231  
   232  		// outdated new branch should not be displayed
   233  		checkRecentlyPushedNewBranches(t, user12Session, "user12/repo10", []string{"new-commit"})
   234  
   235  		// create a fork repo in public org
   236  		testRepoFork(t, user12Session, repo10.OwnerName, repo10.Name, "org25", "org25_fork_repo10", "new-commit")
   237  		orgPublicForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 25, Name: "org25_fork_repo10"})
   238  		prepareRepoPR(t, user12Session, user12Session, repo10, orgPublicForkRepo)
   239  
   240  		// user12 is the owner of the repo10 and the organization org25
   241  		// in repo10, user12 has opening/closed/merged pr and closed/merged pr with deleted branch
   242  		checkRecentlyPushedNewBranches(t, user12Session, "user12/repo10", []string{"org25/org25_fork_repo10:new-commit", "new-commit"})
   243  
   244  		userForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
   245  		testCtx := NewAPITestContext(t, repo10.OwnerName, repo10.Name, auth_model.AccessTokenScopeWriteRepository)
   246  		t.Run("AddUser13AsCollaborator", doAPIAddCollaborator(testCtx, "user13", perm.AccessModeWrite))
   247  		prepareBranch(t, user13Session, userForkRepo)
   248  		prepareRepoPR(t, user13Session, user13Session, repo10, userForkRepo)
   249  
   250  		// create branch with same name in different repo by user13
   251  		testCreateBranch(t, user13Session, repo10.OwnerName, repo10.Name, "branch/new-commit", "same-name-branch", http.StatusSeeOther)
   252  		testCreateBranch(t, user13Session, userForkRepo.OwnerName, userForkRepo.Name, "branch/new-commit", "same-name-branch", http.StatusSeeOther)
   253  		testCreatePullToDefaultBranch(t, user13Session, repo10, userForkRepo, "same-name-branch", "same name branch pr")
   254  
   255  		// user13 pushed 2 branches with the same name in repo10 and repo11
   256  		// and repo11's branch has a pr, but repo10's branch doesn't
   257  		// in this case, we should get repo10's branch but not repo11's branch
   258  		checkRecentlyPushedNewBranches(t, user13Session, "user12/repo10", []string{"same-name-branch", "user13/repo11:new-commit"})
   259  
   260  		// create a fork repo in private org
   261  		testRepoFork(t, user1Session, repo10.OwnerName, repo10.Name, "private_org35", "org35_fork_repo10", "new-commit")
   262  		orgPrivateForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 35, Name: "org35_fork_repo10"})
   263  		prepareRepoPR(t, user1Session, user1Session, repo10, orgPrivateForkRepo)
   264  
   265  		// user1 is the owner of private_org35 and no write permission to repo10
   266  		// so user1 can only see the branch in org35_fork_repo10
   267  		checkRecentlyPushedNewBranches(t, user1Session, "user12/repo10", []string{"private_org35/org35_fork_repo10:new-commit"})
   268  
   269  		// user2 push a branch in private_org35
   270  		testCreateBranch(t, user2Session, orgPrivateForkRepo.OwnerName, orgPrivateForkRepo.Name, "branch/new-commit", "user-read-permission", http.StatusSeeOther)
   271  		// convert write permission to read permission for code unit
   272  		token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteOrganization)
   273  		req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", 24), &api.EditTeamOption{
   274  			Name:     "team24",
   275  			UnitsMap: map[string]string{"repo.code": "read"},
   276  		}).AddTokenAuth(token)
   277  		MakeRequest(t, req, http.StatusOK)
   278  		teamUnit := unittest.AssertExistsAndLoadBean(t, &org_model.TeamUnit{TeamID: 24, Type: unit.TypeCode})
   279  		assert.Equal(t, perm.AccessModeRead, teamUnit.AccessMode)
   280  		// user2 can see the branch as it is created by user2
   281  		checkRecentlyPushedNewBranches(t, user2Session, "user12/repo10", []string{"private_org35/org35_fork_repo10:user-read-permission"})
   282  	})
   283  }