mirror of
https://gitea.com/gitea/gitea-mirror.git
synced 2026-03-20 11:50:27 +00:00
Fix README symlink resolution in subdirectories like .github (#36775)
Fixes #36774. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -86,50 +86,6 @@ func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
|
||||
return c.repo.getCommitByPathWithID(c.ID, relpath)
|
||||
}
|
||||
|
||||
// AddChanges marks local changes to be ready for commit.
|
||||
func AddChanges(ctx context.Context, repoPath string, all bool, files ...string) error {
|
||||
cmd := gitcmd.NewCommand().AddArguments("add")
|
||||
if all {
|
||||
cmd.AddArguments("--all")
|
||||
}
|
||||
cmd.AddDashesAndList(files...)
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// CommitChangesOptions the options when a commit created
|
||||
type CommitChangesOptions struct {
|
||||
Committer *Signature
|
||||
Author *Signature
|
||||
Message string
|
||||
}
|
||||
|
||||
// CommitChanges commits local changes with given committer, author and message.
|
||||
// If author is nil, it will be the same as committer.
|
||||
func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptions) error {
|
||||
cmd := gitcmd.NewCommand()
|
||||
if opts.Committer != nil {
|
||||
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name)
|
||||
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email)
|
||||
}
|
||||
cmd.AddArguments("commit")
|
||||
|
||||
if opts.Author == nil {
|
||||
opts.Author = opts.Committer
|
||||
}
|
||||
if opts.Author != nil {
|
||||
cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email)
|
||||
}
|
||||
cmd.AddOptionFormat("--message=%s", opts.Message)
|
||||
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
// No stderr but exit status 1 means nothing to commit.
|
||||
if gitcmd.IsErrorExitCode(err, 1) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
|
||||
func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
|
||||
return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
|
||||
|
||||
@@ -102,7 +102,7 @@ func findReadmeFileInEntries(ctx *context.Context, parentDir string, entries []*
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, parentDir, childEntries, false)
|
||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, path.Join(parentDir, subTreeEntry.Name()), childEntries, false)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
70
routers/web/repo/view_readme_test.go
Normal file
70
routers/web/repo/view_readme_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFindReadmeFileInEntriesWithSymlinkInSubfolder(t *testing.T) {
|
||||
for _, subdir := range []string{".github", ".gitea", "docs"} {
|
||||
t.Run(subdir, func(t *testing.T) {
|
||||
repoPath := t.TempDir()
|
||||
stdin := fmt.Sprintf(`commit refs/heads/master
|
||||
author Test <test@example.com> 1700000000 +0000
|
||||
committer Test <test@example.com> 1700000000 +0000
|
||||
data <<EOT
|
||||
initial
|
||||
EOT
|
||||
M 100644 inline target.md
|
||||
data <<EOT
|
||||
target-content
|
||||
EOT
|
||||
M 120000 inline %s/README.md
|
||||
data 12
|
||||
../target.md
|
||||
`, subdir)
|
||||
|
||||
var err error
|
||||
err = gitcmd.NewCommand("init", "--bare", ".").WithDir(repoPath).RunWithStderr(t.Context())
|
||||
require.NoError(t, err)
|
||||
err = gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
gitRepo, err := git.OpenRepository(t.Context(), repoPath)
|
||||
require.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
commit, err := gitRepo.GetBranchCommit("master")
|
||||
require.NoError(t, err)
|
||||
|
||||
entries, err := commit.ListEntries()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, _ := contexttest.MockContext(t, "/")
|
||||
ctx.Repo.Commit = commit
|
||||
foundDir, foundReadme, err := findReadmeFileInEntries(ctx, "", entries, true)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, foundReadme)
|
||||
|
||||
assert.Equal(t, subdir, foundDir)
|
||||
assert.Equal(t, "README.md", foundReadme.Name())
|
||||
assert.True(t, foundReadme.IsLink())
|
||||
|
||||
// Verify that it can follow the link
|
||||
res, err := git.EntryFollowLinks(commit, path.Join(foundDir, foundReadme.Name()), foundReadme)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "target.md", res.TargetFullPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -199,10 +199,10 @@ func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFil
|
||||
_, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix + "*").
|
||||
WithDir(dstPath).RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
err = git.AddChanges(t.Context(), dstPath, false, ".gitattributes")
|
||||
err = gitAddChangesDeprecated(t.Context(), dstPath, false, ".gitattributes")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "User Two",
|
||||
@@ -347,11 +347,11 @@ func generateCommitWithNewData(ctx context.Context, size int, repoPath, email, f
|
||||
_ = tmpFile.Close()
|
||||
|
||||
// Commit
|
||||
err = git.AddChanges(ctx, repoPath, false, filepath.Base(tmpFile.Name()))
|
||||
err = gitAddChangesDeprecated(ctx, repoPath, false, filepath.Base(tmpFile.Name()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = git.CommitChanges(ctx, repoPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(ctx, repoPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: email,
|
||||
Name: fullName,
|
||||
@@ -837,10 +837,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = git.AddChanges(t.Context(), dstPath, true)
|
||||
err = gitAddChangesDeprecated(t.Context(), dstPath, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
@@ -909,10 +909,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = git.AddChanges(t.Context(), dstPath, true)
|
||||
err = gitAddChangesDeprecated(t.Context(), dstPath, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
|
||||
@@ -58,6 +58,52 @@ func createSSHUrl(gitPath string, u *url.URL) *url.URL {
|
||||
return &u2
|
||||
}
|
||||
|
||||
// gitAddChangesDeprecated marks local changes to be ready for commit.
|
||||
// Deprecated: use "git fast-import" instead for better performance and more control over the commit creation.
|
||||
func gitAddChangesDeprecated(ctx context.Context, repoPath string, all bool, files ...string) error {
|
||||
cmd := gitcmd.NewCommand().AddArguments("add")
|
||||
if all {
|
||||
cmd.AddArguments("--all")
|
||||
}
|
||||
cmd.AddDashesAndList(files...)
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// CommitChangesOptions the options when a commit created
|
||||
type gitCommitChangesOptions struct {
|
||||
Committer *git.Signature
|
||||
Author *git.Signature
|
||||
Message string
|
||||
}
|
||||
|
||||
// gitCommitChangesDeprecated commits local changes with given committer, author and message.
|
||||
// If author is nil, it will be the same as committer.
|
||||
// Deprecated: use "git fast-import" instead for better performance and more control over the commit creation.
|
||||
func gitCommitChangesDeprecated(ctx context.Context, repoPath string, opts gitCommitChangesOptions) error {
|
||||
cmd := gitcmd.NewCommand()
|
||||
if opts.Committer != nil {
|
||||
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name)
|
||||
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email)
|
||||
}
|
||||
cmd.AddArguments("commit")
|
||||
|
||||
if opts.Author == nil {
|
||||
opts.Author = opts.Committer
|
||||
}
|
||||
if opts.Author != nil {
|
||||
cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email)
|
||||
}
|
||||
cmd.AddOptionFormat("--message=%s", opts.Message)
|
||||
|
||||
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
|
||||
// No stderr but exit status 1 means nothing to commit.
|
||||
if gitcmd.IsErrorExitCode(err, 1) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func onGiteaRun[T testing.TB](t T, callback func(T, *url.URL)) {
|
||||
defer tests.PrepareTestEnv(t, 1)()
|
||||
s := http.Server{
|
||||
@@ -128,13 +174,13 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) {
|
||||
RunStdString(t.Context())
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+dstPath), 0o644))
|
||||
assert.NoError(t, git.AddChanges(t.Context(), dstPath, true))
|
||||
assert.NoError(t, gitAddChangesDeprecated(t.Context(), dstPath, true))
|
||||
signature := git.Signature{
|
||||
Email: "test@example.com",
|
||||
Name: "test",
|
||||
When: time.Now(),
|
||||
}
|
||||
assert.NoError(t, git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
assert.NoError(t, gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "Initial Commit",
|
||||
@@ -181,12 +227,12 @@ func doGitCheckoutWriteFileCommit(opts localGitAddCommitOptions) func(*testing.T
|
||||
doGitCheckoutBranch(opts.LocalRepoPath, opts.CheckoutBranch)(t)
|
||||
localFilePath := filepath.Join(opts.LocalRepoPath, opts.TreeFilePath)
|
||||
require.NoError(t, os.WriteFile(localFilePath, []byte(opts.TreeFileContent), 0o644))
|
||||
require.NoError(t, git.AddChanges(t.Context(), opts.LocalRepoPath, true))
|
||||
require.NoError(t, gitAddChangesDeprecated(t.Context(), opts.LocalRepoPath, true))
|
||||
signature := git.Signature{
|
||||
Email: "test@test.test",
|
||||
Name: "test",
|
||||
}
|
||||
require.NoError(t, git.CommitChanges(t.Context(), opts.LocalRepoPath, git.CommitChangesOptions{
|
||||
require.NoError(t, gitCommitChangesDeprecated(t.Context(), opts.LocalRepoPath, gitCommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: fmt.Sprintf("update %s @ %s", opts.TreeFilePath, opts.CheckoutBranch),
|
||||
|
||||
@@ -997,10 +997,10 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
|
||||
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.AddChanges(t.Context(), dstPath, true)
|
||||
err = gitAddChangesDeprecated(t.Context(), dstPath, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
|
||||
@@ -28,13 +28,13 @@ func doCheckRepositoryEmptyStatus(ctx APITestContext, isEmpty bool) func(*testin
|
||||
func doAddChangesToCheckout(dstPath, filename string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), fmt.Appendf(nil, "# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now()), 0o644))
|
||||
assert.NoError(t, git.AddChanges(t.Context(), dstPath, true))
|
||||
assert.NoError(t, gitAddChangesDeprecated(t.Context(), dstPath, true))
|
||||
signature := git.Signature{
|
||||
Email: "test@example.com",
|
||||
Name: "test",
|
||||
When: time.Now(),
|
||||
}
|
||||
assert.NoError(t, git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
|
||||
assert.NoError(t, gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "Initial Commit",
|
||||
|
||||
Reference in New Issue
Block a user