Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 0 additions & 44 deletions modules/git/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/view_readme.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
70 changes: 70 additions & 0 deletions routers/web/repo/view_readme_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
16 changes: 8 additions & 8 deletions tests/integration/git_general_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
54 changes: 50 additions & 4 deletions tests/integration/git_helper_for_declarative_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/pull_merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/ssh_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down