mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-23 06:45:21 +02:00
before: gitrepo vs git packages after: git package fully handle all git operations by the way, use `WithRepo(repo)` instead of `WithDir(repo.Path)` to hide path details. benefits: 1. remove all unnecessary wrappers, developers no need to struggle with "which package should be used" 2. simplify code, RepositoryFacade can (will) be used everywhere, all "path" details are (will be) hidden
320 lines
12 KiB
Go
320 lines
12 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
repo_model "gitea.dev/models/repo"
|
|
"gitea.dev/models/unittest"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/git"
|
|
"gitea.dev/modules/git/gitcmd"
|
|
"gitea.dev/modules/test"
|
|
"gitea.dev/modules/util"
|
|
"gitea.dev/routers/common"
|
|
repo_service "gitea.dev/services/repository"
|
|
"gitea.dev/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCompareTag(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1...master")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
selection := htmlDoc.doc.Find(".ui.dropdown.select-branch")
|
|
// A dropdown for both base and head.
|
|
assert.Lenf(t, selection.Nodes, 2, "The template has changed")
|
|
|
|
req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...HEAD")
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
|
|
|
req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...NotExisting").SetHeader("Accept", "text/html")
|
|
resp = session.MakeRequest(t, req, http.StatusNotFound)
|
|
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
|
|
|
req = NewRequest(t, "GET", "/user2/repo1/compare/invalid").SetHeader("Accept", "text/html")
|
|
resp = session.MakeRequest(t, req, http.StatusNotFound)
|
|
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
|
}
|
|
|
|
// Compare with inferred default branch (master)
|
|
func TestCompareDefault(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
selection := htmlDoc.doc.Find(".ui.dropdown.select-branch")
|
|
assert.Lenf(t, selection.Nodes, 2, "The template has changed")
|
|
}
|
|
|
|
// Ensure the comparison matches what we expect
|
|
func inspectCompare(t *testing.T, htmlDoc *HTMLDoc, diffCount int, diffChanges []string) {
|
|
selection := htmlDoc.doc.Find("#diff-file-boxes").Children()
|
|
|
|
assert.Lenf(t, selection.Nodes, diffCount, "Expected %v diffed files, found: %v", diffCount, len(selection.Nodes))
|
|
|
|
for _, diffChange := range diffChanges {
|
|
selection = htmlDoc.doc.Find(fmt.Sprintf("[data-new-filename=\"%s\"]", diffChange))
|
|
assert.Lenf(t, selection.Nodes, 1, "Expected 1 match for [data-new-filename=\"%s\"], found: %v", diffChange, len(selection.Nodes))
|
|
}
|
|
}
|
|
|
|
// Git commit graph for repo20
|
|
// * 8babce9 (origin/remove-files-b) Add a dummy file
|
|
// * b67e43a Delete test.csv and link_hi
|
|
// | * cfe3b3c (origin/remove-files-a) Delete test.csv and link_hi
|
|
// |/
|
|
// * c8e31bc (origin/add-csv) Add test csv file
|
|
// * 808038d (HEAD -> master, origin/master, origin/HEAD) Added test links
|
|
|
|
func TestCompareBranches(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
// Indirect compare remove-files-b (head) with add-csv (base) branch
|
|
//
|
|
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
|
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
|
|
diffCount := 3
|
|
diffChanges := []string{"link_hi", "test.csv", "test.txt"}
|
|
|
|
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
|
|
|
// Indirect compare remove-files-b (head) with remove-files-a (base) branch
|
|
//
|
|
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
|
|
|
|
req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-a...remove-files-b")
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
|
|
diffCount = 3
|
|
diffChanges = []string{"link_hi", "test.csv", "test.txt"}
|
|
|
|
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
|
|
|
// Indirect compare remove-files-a (head) with remove-files-b (base) branch
|
|
//
|
|
// 'link_hi' and 'test.csv' are deleted
|
|
|
|
req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b...remove-files-a")
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
|
|
diffCount = 2
|
|
diffChanges = []string{"link_hi", "test.csv"}
|
|
|
|
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
|
|
|
// Direct compare remove-files-b (head) with remove-files-a (base) branch
|
|
//
|
|
// 'test.txt' is deleted
|
|
|
|
req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b..remove-files-a")
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
|
|
diffCount = 1
|
|
diffChanges = []string{"test.txt"}
|
|
|
|
inspectCompare(t, htmlDoc, diffCount, diffChanges)
|
|
}
|
|
|
|
func TestCompareWithRefSuffix(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
// remove-files-b^ resolves to the tip's parent, so the test.txt added by the tip is excluded
|
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b^")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"})
|
|
|
|
// a suffix resolves to a commit rather than a branch, so the page offers no pull request to create
|
|
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
|
|
|
// the same suffix on the direct ".." comparison resolves to the same commit
|
|
req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv..remove-files-b^")
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"})
|
|
|
|
// a ~N suffix on the base side resolves and renders the compare page, but also
|
|
// resolves to a commit rather than a branch, so no pull request form is offered
|
|
req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv~1...remove-files-b")
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
|
|
|
// the web handler folds an unsupported (^{...}) and an unresolvable (~50) suffix alike into 404
|
|
for _, basehead := range []string{
|
|
"add-csv...remove-files-b~50",
|
|
"add-csv...remove-files-b^{/Add}",
|
|
"add-csv^{/Add}...remove-files-b",
|
|
} {
|
|
req = NewRequest(t, "GET", "/user2/repo20/compare/"+basehead).SetHeader("Accept", "text/html")
|
|
resp = session.MakeRequest(t, req, http.StatusNotFound)
|
|
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
|
|
}
|
|
}
|
|
|
|
func TestResolveRefWithSuffixContract(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31})
|
|
gitRepo, err := git.OpenRepository(repo)
|
|
require.NoError(t, err)
|
|
defer gitRepo.Close()
|
|
|
|
// a nil error guarantees a usable RefName
|
|
ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, "add-csv", "^")
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, ref)
|
|
// a ref resolved with a suffix must be a commit SHA, not a branch ref
|
|
// (branch refs would break "New Pull Request" logic)
|
|
assert.False(t, ref.IsBranch(), "ref with suffix must not resolve to a branch")
|
|
|
|
// a missing ref and an unresolvable suffix both report not-found instead of an empty RefName
|
|
for _, tc := range []struct{ oriRef, suffix string }{
|
|
{"does-not-exist", ""},
|
|
{"add-csv", "~50"},
|
|
} {
|
|
ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, tc.oriRef, tc.suffix)
|
|
assert.ErrorIs(t, err, util.ErrNotExist, "ref %q suffix %q", tc.oriRef, tc.suffix)
|
|
assert.Empty(t, ref, "ref %q suffix %q", tc.oriRef, tc.suffix)
|
|
}
|
|
}
|
|
|
|
func TestCompareBranchesNoCommonMergeBase(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"})
|
|
|
|
repoPath := repo_model.RepoPath(user2.Name, repo1.Name)
|
|
_, _, runErr := gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(strings.TrimSpace(`
|
|
commit refs/heads/unrelated-history
|
|
committer User <user@example.com> 1714310400 +0000
|
|
data 13
|
|
Second commit
|
|
M 100644 inline file2.txt
|
|
data 12
|
|
Hello from 2
|
|
`))).RunStdString(t.Context())
|
|
require.NoError(t, runErr)
|
|
|
|
session := loginUser(t, "user2")
|
|
req := NewRequest(t, "GET", "/user2/repo1/compare/master...unrelated-history")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
body := resp.Body.String()
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
|
|
selection := htmlDoc.doc.Find(".ui.dropdown.select-branch")
|
|
assert.Lenf(t, selection.Nodes, 2, "The template has changed")
|
|
assert.Contains(t, body, "These branches do not share a common merge base")
|
|
assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...unrelated-history"]`).Length())
|
|
assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...master"]`).Length())
|
|
assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length())
|
|
}
|
|
|
|
func TestCompareDownloadDiffOrPatch(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
t.Run("BranchToBranchDiff", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.diff")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
|
})
|
|
|
|
t.Run("BranchToBranchPatch", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.patch")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
|
assert.True(t, strings.HasPrefix(resp.Body.String(), "From "), "patch output should start with a format-patch header")
|
|
})
|
|
|
|
t.Run("SingleRefImplicitBase", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv.diff")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Contains(t, resp.Body.String(), "diff --git ")
|
|
})
|
|
|
|
t.Run("InvalidBaseRef", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
req := NewRequest(t, "GET", "/user2/repo20/compare/does-not-exist...remove-files-b.diff")
|
|
session.MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
|
|
t.Run("PrivateRepoAnonymous", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
// repo16 is private; an unauthenticated request must not leak its existence.
|
|
req := NewRequest(t, "GET", "/user2/repo16/compare/master...good-sign.diff")
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
}
|
|
|
|
func TestCompareCodeExpand(t *testing.T) {
|
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
repo, err := repo_service.CreateRepositoryDirectly(t.Context(), user1, user1, repo_service.CreateRepoOptions{
|
|
Name: "test_blob_excerpt",
|
|
Readme: "Default",
|
|
AutoInit: true,
|
|
DefaultBranch: "main",
|
|
}, true)
|
|
assert.NoError(t, err)
|
|
|
|
session := loginUser(t, user1.Name)
|
|
testEditFile(t, session, user1.Name, repo.Name, "main", "README.md", strings.Repeat("a\n", 30))
|
|
|
|
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", "")
|
|
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))
|
|
|
|
req := NewRequest(t, "GET", "/user1/test_blob_excerpt/compare/main...user2/test_blob_excerpt-fork:forked-branch")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
els := htmlDoc.Find(`button.code-expander-button[data-fetch-url]`)
|
|
|
|
// all the links in the comparison should be to the forked repo&branch
|
|
assert.NotZero(t, els.Length())
|
|
for i := 0; i < els.Length(); i++ {
|
|
link := els.Eq(i).AttrOr("data-fetch-url", "")
|
|
assert.True(t, strings.HasPrefix(link, "/user2/test_blob_excerpt-fork/blob_excerpt/"))
|
|
}
|
|
})
|
|
}
|