Compare commits

..

No commits in common. "main" and "v1.26.0-dev" have entirely different histories.

42 changed files with 412 additions and 533 deletions

View File

@ -417,6 +417,10 @@ func (pr *PullRequest) GetGitHeadRefName() string {
return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index) return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
} }
func (pr *PullRequest) GetGitHeadBranchRefName() string {
return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
}
// GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR) // GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int { func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int {
opts := FindCommentsOptions{ opts := FindCommentsOptions{
@ -642,8 +646,9 @@ func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error {
} }
// UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged // UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged
func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) (int64, error) { func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) error {
return db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr) _, err := db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
return err
} }
// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title // IsWorkInProgress determine if the Pull Request is a Work In Progress by its title

View File

@ -6,10 +6,11 @@ package v1_12
import ( import (
"fmt" "fmt"
"math" "math"
"path/filepath"
"strings"
"time" "time"
repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -84,9 +85,12 @@ func AddCommitDivergenceToPulls(x *xorm.Engine) error {
log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID) log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
continue continue
} }
repoStore := repo_model.StorageRepo(repo_model.RelativePath(baseRepo.OwnerName, baseRepo.Name)) userPath := filepath.Join(setting.RepoRootPath, strings.ToLower(baseRepo.OwnerName))
repoPath := filepath.Join(userPath, strings.ToLower(baseRepo.Name)+".git")
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index) gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
divergence, err := gitrepo.GetDivergingCommits(graceful.GetManager().HammerContext(), repoStore, pr.BaseBranch, gitRefName)
divergence, err := git.GetDivergingCommits(graceful.GetManager().HammerContext(), repoPath, pr.BaseBranch, gitRefName)
if err != nil { if err != nil {
log.Warn("Could not recalculate Divergence for pull: %d", pr.ID) log.Warn("Could not recalculate Divergence for pull: %d", pr.ID)
pr.CommitsAhead = 0 pr.CommitsAhead = 0

View File

@ -243,6 +243,36 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error
return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime) return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime)
} }
// DivergeObject represents commit count diverging commits
type DivergeObject struct {
Ahead int
Behind int
}
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) {
cmd := gitcmd.NewCommand("rev-list", "--count", "--left-right").
AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
return do, err
}
left, right, found := strings.Cut(strings.Trim(stdout, "\n"), "\t")
if !found {
return do, fmt.Errorf("git rev-list output is missing a tab: %q", stdout)
}
do.Behind, err = strconv.Atoi(left)
if err != nil {
return do, err
}
do.Ahead, err = strconv.Atoi(right)
if err != nil {
return do, err
}
return do, nil
}
// CreateBundle create bundle content to the target path // CreateBundle create bundle content to the target path
func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error { func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error {
tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle") tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle")

25
modules/git/repo_blame.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"fmt"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
res, _, err := gitcmd.NewCommand("blame").
AddOptionFormat("-L %d,%d", line, line).
AddOptionValues("-p", revision).
AddDashesAndList(file).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: path})
if err != nil {
return nil, err
}
if len(res) < 40 {
return nil, fmt.Errorf("invalid result of blame: %s", res)
}
return repo.GetCommit(res[:40])
}

View File

@ -5,12 +5,70 @@
package git package git
import ( import (
"context"
"errors"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/git/gitcmd"
) )
// BranchPrefix base dir of the branch information file store on git // BranchPrefix base dir of the branch information file store on git
const BranchPrefix = "refs/heads/" const BranchPrefix = "refs/heads/"
// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
_, _, err := gitcmd.NewCommand("show-ref", "--verify").AddDashesAndList(name).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
return err == nil
}
// IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(ctx context.Context, repoPath, name string) bool {
return IsReferenceExist(ctx, repoPath, BranchPrefix+name)
}
func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
stdout, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
return "", err
}
stdout = strings.TrimSpace(stdout)
if !strings.HasPrefix(stdout, BranchPrefix) {
return "", errors.New("the HEAD is not a branch: " + stdout)
}
return strings.TrimPrefix(stdout, BranchPrefix), nil
}
// DeleteBranchOptions Option(s) for delete branch
type DeleteBranchOptions struct {
Force bool
}
// DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
cmd := gitcmd.NewCommand("branch")
if opts.Force {
cmd.AddArguments("-D")
} else {
cmd.AddArguments("-d")
}
cmd.AddDashesAndList(name)
_, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err
}
// CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
cmd := gitcmd.NewCommand("branch")
cmd.AddDashesAndList(branch, oldbranchOrCommit)
_, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err
}
// AddRemote adds a new remote to repository. // AddRemote adds a new remote to repository.
func (repo *Repository) AddRemote(name, url string, fetch bool) error { func (repo *Repository) AddRemote(name, url string, fetch bool) error {
cmd := gitcmd.NewCommand("remote", "add") cmd := gitcmd.NewCommand("remote", "add")
@ -22,3 +80,9 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
_, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) _, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
// RenameBranch rename a branch
func (repo *Repository) RenameBranch(from, to string) error {
_, _, err := gitcmd.NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err
}

View File

@ -7,12 +7,14 @@ package git
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/git/gitcmd"
@ -85,8 +87,57 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
return w.numLines, nil return w.numLines, nil
} }
// GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions
// TODO: it can be merged with another "GetDiffShortStat" in the future
func GetDiffShortStatByCmdArgs(ctx context.Context, repoPath string, trustedArgs gitcmd.TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
// Now if we call:
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
// we get:
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
cmd := gitcmd.NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
return 0, 0, 0, err
}
return parseDiffStat(stdout)
}
var shortStatFormat = regexp.MustCompile(
`\s*(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`)
var patchCommits = regexp.MustCompile(`^From\s(\w+)\s`) var patchCommits = regexp.MustCompile(`^From\s(\w+)\s`)
func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, err error) {
if len(stdout) == 0 || stdout == "\n" {
return 0, 0, 0, nil
}
groups := shortStatFormat.FindStringSubmatch(stdout)
if len(groups) != 4 {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s groups: %s", stdout, groups)
}
numFiles, err = strconv.Atoi(groups[1])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %w", stdout, err)
}
if len(groups[2]) != 0 {
totalAdditions, err = strconv.Atoi(groups[2])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %w", stdout, err)
}
}
if len(groups[3]) != 0 {
totalDeletions, err = strconv.Atoi(groups[3])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %w", stdout, err)
}
}
return numFiles, totalAdditions, totalDeletions, err
}
// GetDiff generates and returns patch data between given revisions, optimized for human readability // GetDiff generates and returns patch data between given revisions, optimized for human readability
func (repo *Repository) GetDiff(compareArg string, w io.Writer) error { func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)

View File

@ -29,3 +29,27 @@ func TestRepoIsEmpty(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, isEmpty) assert.True(t, isEmpty)
} }
func TestRepoGetDivergingCommits(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
do, err := GetDivergingCommits(t.Context(), bareRepo1Path, "master", "branch2")
assert.NoError(t, err)
assert.Equal(t, DivergeObject{
Ahead: 1,
Behind: 5,
}, do)
do, err = GetDivergingCommits(t.Context(), bareRepo1Path, "master", "master")
assert.NoError(t, err)
assert.Equal(t, DivergeObject{
Ahead: 0,
Behind: 0,
}, do)
do, err = GetDivergingCommits(t.Context(), bareRepo1Path, "master", "test")
assert.NoError(t, err)
assert.Equal(t, DivergeObject{
Ahead: 0,
Behind: 2,
}, do)
}

View File

@ -1,18 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git/gitcmd"
)
func LineBlame(ctx context.Context, repo Repository, revision, file string, line uint) (string, error) {
return runCmdString(ctx, repo,
gitcmd.NewCommand("blame").
AddOptionFormat("-L %d,%d", line, line).
AddOptionValues("-p", revision).
AddDashesAndList(file))
}

View File

@ -5,8 +5,6 @@ package gitrepo
import ( import (
"context" "context"
"errors"
"strings"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/git/gitcmd"
@ -36,61 +34,23 @@ func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (str
// SetDefaultBranch sets default branch of repository. // SetDefaultBranch sets default branch of repository.
func SetDefaultBranch(ctx context.Context, repo Repository, name string) error { func SetDefaultBranch(ctx context.Context, repo Repository, name string) error {
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD"). _, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD").
AddDynamicArguments(git.BranchPrefix+name)) AddDynamicArguments(git.BranchPrefix+name).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
} }
// GetDefaultBranch gets default branch of repository. // GetDefaultBranch gets default branch of repository.
func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) { func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) {
stdout, err := runCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD")) return git.GetDefaultBranch(ctx, repoPath(repo))
if err != nil {
return "", err
}
stdout = strings.TrimSpace(stdout)
if !strings.HasPrefix(stdout, git.BranchPrefix) {
return "", errors.New("the HEAD is not a branch: " + stdout)
}
return strings.TrimPrefix(stdout, git.BranchPrefix), nil
} }
// IsReferenceExist returns true if given reference exists in the repository. // IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(ctx context.Context, repo Repository, name string) bool { func IsReferenceExist(ctx context.Context, repo Repository, name string) bool {
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("show-ref", "--verify").AddDashesAndList(name)) return git.IsReferenceExist(ctx, repoPath(repo), name)
return err == nil
} }
// IsBranchExist returns true if given branch exists in the repository. // IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(ctx context.Context, repo Repository, name string) bool { func IsBranchExist(ctx context.Context, repo Repository, name string) bool {
return IsReferenceExist(ctx, repo, git.BranchPrefix+name) return IsReferenceExist(ctx, repo, git.BranchPrefix+name)
} }
// DeleteBranch delete a branch by name on repository.
func DeleteBranch(ctx context.Context, repo Repository, name string, force bool) error {
cmd := gitcmd.NewCommand("branch")
if force {
cmd.AddArguments("-D")
} else {
cmd.AddArguments("-d")
}
cmd.AddDashesAndList(name)
_, err := runCmdString(ctx, repo, cmd)
return err
}
// CreateBranch create a new branch
func CreateBranch(ctx context.Context, repo Repository, branch, oldbranchOrCommit string) error {
cmd := gitcmd.NewCommand("branch")
cmd.AddDashesAndList(branch, oldbranchOrCommit)
_, err := runCmdString(ctx, repo, cmd)
return err
}
// RenameBranch rename a branch
func RenameBranch(ctx context.Context, repo Repository, from, to string) error {
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("branch", "-m").AddDynamicArguments(from, to))
return err
}

View File

@ -1,15 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git/gitcmd"
)
func runCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command) (string, error) {
res, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return res, err
}

View File

@ -1,44 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"fmt"
"strconv"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// DivergeObject represents commit count diverging commits
type DivergeObject struct {
Ahead int
Behind int
}
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
func GetDivergingCommits(ctx context.Context, repo Repository, baseBranch, targetBranch string) (*DivergeObject, error) {
cmd := gitcmd.NewCommand("rev-list", "--count", "--left-right").
AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
stdout, _, err1 := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
if err1 != nil {
return nil, err1
}
left, right, found := strings.Cut(strings.Trim(stdout, "\n"), "\t")
if !found {
return nil, fmt.Errorf("git rev-list output is missing a tab: %q", stdout)
}
behind, err := strconv.Atoi(left)
if err != nil {
return nil, err
}
ahead, err := strconv.Atoi(right)
if err != nil {
return nil, err
}
return &DivergeObject{Ahead: ahead, Behind: behind}, nil
}

View File

@ -1,42 +0,0 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"testing"
"github.com/stretchr/testify/assert"
)
type mockRepository struct {
path string
}
func (r *mockRepository) RelativePath() string {
return r.path
}
func TestRepoGetDivergingCommits(t *testing.T) {
repo := &mockRepository{path: "repo1_bare"}
do, err := GetDivergingCommits(t.Context(), repo, "master", "branch2")
assert.NoError(t, err)
assert.Equal(t, &DivergeObject{
Ahead: 1,
Behind: 5,
}, do)
do, err = GetDivergingCommits(t.Context(), repo, "master", "master")
assert.NoError(t, err)
assert.Equal(t, &DivergeObject{
Ahead: 0,
Behind: 0,
}, do)
do, err = GetDivergingCommits(t.Context(), repo, "master", "test")
assert.NoError(t, err)
assert.Equal(t, &DivergeObject{
Ahead: 0,
Behind: 2,
}, do)
}

View File

@ -12,8 +12,9 @@ import (
) )
func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) { func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) {
result, err := runCmdString(ctx, repo, gitcmd.NewCommand("config", "--get"). result, _, err := gitcmd.NewCommand("config", "--get").
AddDynamicArguments(key)) AddDynamicArguments(key).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -27,8 +28,9 @@ func getRepoConfigLockKey(repoStoragePath string) string {
// GitConfigAdd add a git configuration key to a specific value for the given repository. // GitConfigAdd add a git configuration key to a specific value for the given repository.
func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error { func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error { return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("config", "--add"). _, _, err := gitcmd.NewCommand("config", "--add").
AddDynamicArguments(key, value)) AddDynamicArguments(key, value).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }
@ -38,8 +40,9 @@ func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error
// If the key exists, it will be updated to the new value. // If the key exists, it will be updated to the new value.
func GitConfigSet(ctx context.Context, repo Repository, key, value string) error { func GitConfigSet(ctx context.Context, repo Repository, key, value string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error { return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("config"). _, _, err := gitcmd.NewCommand("config").
AddDynamicArguments(key, value)) AddDynamicArguments(key, value).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }

View File

@ -1,62 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"fmt"
"regexp"
"strconv"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions
// TODO: it can be merged with another "GetDiffShortStat" in the future
func GetDiffShortStatByCmdArgs(ctx context.Context, repo Repository, trustedArgs gitcmd.TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
// Now if we call:
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
// we get:
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
cmd := gitcmd.NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
stdout, err := runCmdString(ctx, repo, cmd)
if err != nil {
return 0, 0, 0, err
}
return parseDiffStat(stdout)
}
var shortStatFormat = regexp.MustCompile(
`\s*(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`)
func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, err error) {
if len(stdout) == 0 || stdout == "\n" {
return 0, 0, 0, nil
}
groups := shortStatFormat.FindStringSubmatch(stdout)
if len(groups) != 4 {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s groups: %s", stdout, groups)
}
numFiles, err = strconv.Atoi(groups[1])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %w", stdout, err)
}
if len(groups[2]) != 0 {
totalAdditions, err = strconv.Atoi(groups[2])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %w", stdout, err)
}
}
if len(groups[3]) != 0 {
totalDeletions, err = strconv.Atoi(groups[3])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %w", stdout, err)
}
}
return numFiles, totalAdditions, totalDeletions, err
}

View File

@ -20,9 +20,9 @@ type Repository interface {
RelativePath() string // We don't assume how the directory structure of the repository is, so we only need the relative path RelativePath() string // We don't assume how the directory structure of the repository is, so we only need the relative path
} }
// repoPath resolves the Repository.RelativePath (which is a unix-style path like "username/reponame.git") // RelativePath should be an unix style path like username/reponame.git
// to a local filesystem path according to setting.RepoRootPath // This method should change it according to the current OS.
var repoPath = func(repo Repository) string { func repoPath(repo Repository) string {
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repo.RelativePath())) return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repo.RelativePath()))
} }

View File

@ -1,32 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"os"
"path/filepath"
"testing"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/tempdir"
"code.gitea.io/gitea/modules/test"
)
func TestMain(m *testing.M) {
gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home")
if err != nil {
log.Fatal("Unable to create temp dir: %v", err)
}
defer cleanup()
// resolve repository path relative to the test directory
testRootDir := test.SetupGiteaRoot()
repoPath = func(repo Repository) string {
return filepath.Join(testRootDir, "/modules/git/tests/repos", repo.RelativePath())
}
setting.Git.HomePath = gitHomePath
os.Exit(m.Run())
}

View File

@ -36,7 +36,9 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st
return errors.New("unknown remote option: " + string(options[0])) return errors.New("unknown remote option: " + string(options[0]))
} }
} }
_, err := runCmdString(ctx, repo, cmd.AddDynamicArguments(remoteName, remoteURL)) _, _, err := cmd.
AddDynamicArguments(remoteName, remoteURL).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }
@ -44,7 +46,7 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st
func GitRemoteRemove(ctx context.Context, repo Repository, remoteName string) error { func GitRemoteRemove(ctx context.Context, repo Repository, remoteName string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error { return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
cmd := gitcmd.NewCommand("remote", "rm").AddDynamicArguments(remoteName) cmd := gitcmd.NewCommand("remote", "rm").AddDynamicArguments(remoteName)
_, err := runCmdString(ctx, repo, cmd) _, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }

View File

@ -1052,8 +1052,6 @@ license=Licença
license_helper=Selecione um arquivo de licença. license_helper=Selecione um arquivo de licença.
license_helper_desc=Uma licença define o que os outros podem e não podem fazer com o seu código. Não tem certeza qual é a mais adequada para o seu projeto? Veja <a target="_blank" rel="noopener noreferrer" href="%s">Escolher uma licença.</a> license_helper_desc=Uma licença define o que os outros podem e não podem fazer com o seu código. Não tem certeza qual é a mais adequada para o seu projeto? Veja <a target="_blank" rel="noopener noreferrer" href="%s">Escolher uma licença.</a>
multiple_licenses=Múltiplas Licenças multiple_licenses=Múltiplas Licenças
object_format=Formato de Objeto
object_format_helper=Formato do objeto do repositório. Não pode ser alterado mais tarde. SHA1 é mais compatível.
readme=README readme=README
readme_helper=Selecione um modelo de arquivo README. readme_helper=Selecione um modelo de arquivo README.
readme_helper_desc=Aqui você pode escrever uma descrição completa para o seu projeto. readme_helper_desc=Aqui você pode escrever uma descrição completa para o seu projeto.
@ -1103,8 +1101,6 @@ delete_preexisting=Excluir arquivos pré-existentes
delete_preexisting_content=Excluir arquivos em %s delete_preexisting_content=Excluir arquivos em %s
delete_preexisting_success=Arquivos órfãos excluídos em %s delete_preexisting_success=Arquivos órfãos excluídos em %s
blame_prior=Ver a responsabilização anterior a esta modificação blame_prior=Ver a responsabilização anterior a esta modificação
blame.ignore_revs=Ignorando revisões presentes em <a href="%s">.git-blame-ignore-revs</a>. Clique <a href="%s">aqui para ignorar</a> e ver o blame normalmente.
blame.ignore_revs.failed=Falha ao ignorar revisões em <a href="%s">.git-blame-ignore-revs</a>.
transfer.accept=Aceitar Transferência transfer.accept=Aceitar Transferência
@ -1268,7 +1264,6 @@ ambiguous_runes_header=`Este arquivo contém caracteres Unicode ambíguos`
ambiguous_runes_description=`Este arquivo contém caracteres Unicode que podem ser confundidos com outros caracteres. Se você acha que isso é intencional, pode ignorar esse aviso com segurança. Use o botão Escapar para revelá-los` ambiguous_runes_description=`Este arquivo contém caracteres Unicode que podem ser confundidos com outros caracteres. Se você acha que isso é intencional, pode ignorar esse aviso com segurança. Use o botão Escapar para revelá-los`
invisible_runes_line=`Esta linha tem caracteres unicode invisíveis` invisible_runes_line=`Esta linha tem caracteres unicode invisíveis`
ambiguous_runes_line=`Esta linha tem caracteres unicode ambíguos` ambiguous_runes_line=`Esta linha tem caracteres unicode ambíguos`
ambiguous_character=`%[1]c [U+%04[1]X] pode ser confundido com %[2]c [U+%04[2]X]`
escape_control_characters=Escapar escape_control_characters=Escapar
unescape_control_characters=Desescapar unescape_control_characters=Desescapar
@ -1278,7 +1273,6 @@ video_not_supported_in_browser=Seu navegador não suporta a tag 'video' do HTML5
audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5. audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5.
symbolic_link=Link simbólico symbolic_link=Link simbólico
executable_file=Arquivo Executável executable_file=Arquivo Executável
vendored=Externo
generated=Gerado generated=Gerado
commit_graph=Gráfico de Commits commit_graph=Gráfico de Commits
commit_graph.select=Selecionar branches commit_graph.select=Selecionar branches
@ -1398,7 +1392,6 @@ commitstatus.success=Sucesso
ext_issues=Acesso a Issues Externos ext_issues=Acesso a Issues Externos
ext_issues.desc=Link para o issue tracker externo. ext_issues.desc=Link para o issue tracker externo.
projects.desc=Gerencie issues e PRs nos quadros do projeto.
projects.description=Descrição (opcional) projects.description=Descrição (opcional)
projects.description_placeholder=Descrição projects.description_placeholder=Descrição
projects.create=Criar Projeto projects.create=Criar Projeto
@ -1426,7 +1419,6 @@ projects.column.new=Nova Coluna
projects.column.set_default=Atribuir como Padrão projects.column.set_default=Atribuir como Padrão
projects.column.set_default_desc=Definir esta coluna como padrão para pull e issues sem categoria projects.column.set_default_desc=Definir esta coluna como padrão para pull e issues sem categoria
projects.column.delete=Excluir Coluna projects.column.delete=Excluir Coluna
projects.column.deletion_desc=Excluir uma coluna do projeto move todas as issues relacionadas para a coluna padrão. Continuar?
projects.column.color=Cor projects.column.color=Cor
projects.open=Abrir projects.open=Abrir
projects.close=Fechar projects.close=Fechar
@ -1460,7 +1452,6 @@ issues.new.clear_milestone=Limpar marco
issues.new.assignees=Responsáveis issues.new.assignees=Responsáveis
issues.new.clear_assignees=Limpar responsáveis issues.new.clear_assignees=Limpar responsáveis
issues.new.no_assignees=Sem Responsáveis issues.new.no_assignees=Sem Responsáveis
issues.new.no_reviewers=Sem Revisores
issues.choose.get_started=Primeiros Passos issues.choose.get_started=Primeiros Passos
issues.choose.open_external_link=Abrir issues.choose.open_external_link=Abrir
issues.choose.blank=Padrão issues.choose.blank=Padrão
@ -1525,7 +1516,6 @@ issues.filter_type.reviewed_by_you=Revisado por você
issues.filter_sort=Ordenação issues.filter_sort=Ordenação
issues.filter_sort.latest=Mais recentes issues.filter_sort.latest=Mais recentes
issues.filter_sort.oldest=Mais antigos issues.filter_sort.oldest=Mais antigos
issues.filter_sort.recentupdate=Mais recentemente atualizados
issues.filter_sort.leastupdate=Menos recentemente atualizados issues.filter_sort.leastupdate=Menos recentemente atualizados
issues.filter_sort.mostcomment=Mais comentados issues.filter_sort.mostcomment=Mais comentados
issues.filter_sort.leastcomment=Menos comentados issues.filter_sort.leastcomment=Menos comentados
@ -1645,7 +1635,6 @@ issues.lock_no_reason=bloqueada e conversação limitada para colaboradores %s
issues.unlock_comment=desbloqueada esta conversação %s issues.unlock_comment=desbloqueada esta conversação %s
issues.lock_confirm=Bloquear issues.lock_confirm=Bloquear
issues.unlock_confirm=Desbloquear issues.unlock_confirm=Desbloquear
issues.lock.notice_1=- Outros usuários não podem adicionar novos comentários a esta issue.
issues.lock.notice_2=- Você e outros colaboradores com acesso a este repositório ainda podem deixar comentários que outros podem ver. issues.lock.notice_2=- Você e outros colaboradores com acesso a este repositório ainda podem deixar comentários que outros podem ver.
issues.lock.notice_3=- Você pode sempre desbloquear esta issue novamente no futuro. issues.lock.notice_3=- Você pode sempre desbloquear esta issue novamente no futuro.
issues.unlock.notice_1=- Todos poderão comentar mais uma vez nesta issue. issues.unlock.notice_1=- Todos poderão comentar mais uma vez nesta issue.
@ -1671,7 +1660,6 @@ issues.stop_tracking=Parar Cronômetro
issues.stop_tracking_history=trabalhou por <b>%[1]s</b> %[2]s issues.stop_tracking_history=trabalhou por <b>%[1]s</b> %[2]s
issues.cancel_tracking_history=`cronômetro cancelado %s` issues.cancel_tracking_history=`cronômetro cancelado %s`
issues.del_time=Apagar este registro de tempo issues.del_time=Apagar este registro de tempo
issues.add_time_history=adicionou tempo gasto <b>%[1]s</b> %[2]s
issues.del_time_history=`removeu tempo gasto %s` issues.del_time_history=`removeu tempo gasto %s`
issues.add_time_hours=Horas issues.add_time_hours=Horas
issues.add_time_minutes=Minutos issues.add_time_minutes=Minutos
@ -1691,7 +1679,6 @@ issues.due_date_form=dd/mm/aaaa
issues.due_date_form_add=Adicionar data limite issues.due_date_form_add=Adicionar data limite
issues.due_date_form_edit=Editar issues.due_date_form_edit=Editar
issues.due_date_form_remove=Remover issues.due_date_form_remove=Remover
issues.due_date_not_writer=Você precisa de acesso de escrita neste repositório para atualizar a data limite de uma issue.
issues.due_date_not_set=Data limite não definida. issues.due_date_not_set=Data limite não definida.
issues.due_date_added=adicionou a data limite %s %s issues.due_date_added=adicionou a data limite %s %s
issues.due_date_modified=modificou a data limite de %[2]s para %[1]s %[3]s issues.due_date_modified=modificou a data limite de %[2]s para %[1]s %[3]s
@ -1714,9 +1701,7 @@ issues.dependency.pr_closing_blockedby=Fechamento deste pull request está bloqu
issues.dependency.issue_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues issues.dependency.issue_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues
issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues
issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues
issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la.
issues.dependency.issue_batch_close_blocked=Não é possível fechar as issues que você escolheu, porque a issue #%d ainda tem dependências abertas issues.dependency.issue_batch_close_blocked=Não é possível fechar as issues que você escolheu, porque a issue #%d ainda tem dependências abertas
issues.dependency.pr_close_blocked=Você precisa fechar todas issues que bloqueiam este pull request antes de poder fazer o merge.
issues.dependency.blocks_short=Bloqueia issues.dependency.blocks_short=Bloqueia
issues.dependency.blocked_by_short=Depende de issues.dependency.blocked_by_short=Depende de
issues.dependency.remove_header=Remover Dependência issues.dependency.remove_header=Remover Dependência
@ -1727,13 +1712,11 @@ issues.dependency.add_error_same_issue=Você não pode fazer uma issue depender
issues.dependency.add_error_dep_issue_not_exist=Issue dependente não existe. issues.dependency.add_error_dep_issue_not_exist=Issue dependente não existe.
issues.dependency.add_error_dep_not_exist=Dependência não existe. issues.dependency.add_error_dep_not_exist=Dependência não existe.
issues.dependency.add_error_dep_exists=Dependência já existe. issues.dependency.add_error_dep_exists=Dependência já existe.
issues.dependency.add_error_cannot_create_circular=Você não pode criar uma dependência com duas issues que se bloqueiam mutuamente.
issues.dependency.add_error_dep_not_same_repo=Ambas as issues devem estar no mesmo repositório. issues.dependency.add_error_dep_not_same_repo=Ambas as issues devem estar no mesmo repositório.
issues.review.self.approval=Você não pode aprovar o seu próprio pull request. issues.review.self.approval=Você não pode aprovar o seu próprio pull request.
issues.review.self.rejection=Você não pode solicitar alterações em seu próprio pull request. issues.review.self.rejection=Você não pode solicitar alterações em seu próprio pull request.
issues.review.approve=aprovou estas alterações %s issues.review.approve=aprovou estas alterações %s
issues.review.comment=revisou %s issues.review.comment=revisou %s
issues.review.dismissed=dispensou a revisão de %s %s
issues.review.dismissed_label=Rejeitada issues.review.dismissed_label=Rejeitada
issues.review.left_comment=deixou um comentário issues.review.left_comment=deixou um comentário
issues.review.content.empty=Você precisa deixar um comentário indicando as alterações solicitadas. issues.review.content.empty=Você precisa deixar um comentário indicando as alterações solicitadas.
@ -1793,11 +1776,9 @@ pulls.show_all_commits=Mostrar todos os commits
pulls.show_changes_since_your_last_review=Mostrar alterações desde sua última revisão pulls.show_changes_since_your_last_review=Mostrar alterações desde sua última revisão
pulls.showing_only_single_commit=Mostrando apenas as alterações do commit %[1]s pulls.showing_only_single_commit=Mostrando apenas as alterações do commit %[1]s
pulls.showing_specified_commit_range=Mostrando apenas as alterações entre %[1]s..%[2]s pulls.showing_specified_commit_range=Mostrando apenas as alterações entre %[1]s..%[2]s
pulls.select_commit_hold_shift_for_range=Selecionar commit. Mantenha pressionado shift + clique para selecionar um intervalo
pulls.review_only_possible_for_full_diff=A revisão só é possível ao visualizar todas as diferenças pulls.review_only_possible_for_full_diff=A revisão só é possível ao visualizar todas as diferenças
pulls.filter_changes_by_commit=Filtrar por commit pulls.filter_changes_by_commit=Filtrar por commit
pulls.nothing_to_compare=Estes branches são iguais. Não há nenhuma necessidade para criar um pull request. pulls.nothing_to_compare=Estes branches são iguais. Não há nenhuma necessidade para criar um pull request.
pulls.nothing_to_compare_have_tag=Os branches/tags selecionados são iguais.
pulls.nothing_to_compare_and_allow_empty_pr=Estes branches são iguais. Este PR ficará vazio. pulls.nothing_to_compare_and_allow_empty_pr=Estes branches são iguais. Este PR ficará vazio.
pulls.has_pull_request=`Um pull request entre esses branches já existe: <a href="%[1]s">%[2]s#%[3]d</a>` pulls.has_pull_request=`Um pull request entre esses branches já existe: <a href="%[1]s">%[2]s#%[3]d</a>`
pulls.create=Criar Pull Request pulls.create=Criar Pull Request
@ -1822,13 +1803,11 @@ pulls.add_prefix=Adicione o prefixo <strong>%s</strong>
pulls.remove_prefix=Remover o prefixo <strong>%s</strong> pulls.remove_prefix=Remover o prefixo <strong>%s</strong>
pulls.data_broken=Este pull request está quebrado devido a falta de informação do fork. pulls.data_broken=Este pull request está quebrado devido a falta de informação do fork.
pulls.files_conflicted=Este pull request tem alterações conflitantes com o branch de destino. pulls.files_conflicted=Este pull request tem alterações conflitantes com o branch de destino.
pulls.is_checking=Verificando conflitos de merge…
pulls.is_ancestor=Este branch já está incluído no branch de destino. Não há nada para mesclar. pulls.is_ancestor=Este branch já está incluído no branch de destino. Não há nada para mesclar.
pulls.is_empty=As alterações neste branch já estão na branch de destino. Este será um commit vazio. pulls.is_empty=As alterações neste branch já estão na branch de destino. Este será um commit vazio.
pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas. pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas.
pulls.required_status_check_missing=Estão faltando algumas verificações necessárias. pulls.required_status_check_missing=Estão faltando algumas verificações necessárias.
pulls.required_status_check_administrator=Como administrador, você ainda pode aplicar o merge deste pull request. pulls.required_status_check_administrator=Como administrador, você ainda pode aplicar o merge deste pull request.
pulls.blocked_by_approvals=Este pull request ainda não tem aprovações suficientes. %d de %d aprovações concedidas.
pulls.blocked_by_rejection=Este pull request tem alterações solicitadas por um revisor oficial. pulls.blocked_by_rejection=Este pull request tem alterações solicitadas por um revisor oficial.
pulls.blocked_by_official_review_requests=Este pull request tem solicitações de revisão oficiais. pulls.blocked_by_official_review_requests=Este pull request tem solicitações de revisão oficiais.
pulls.blocked_by_outdated_branch=Este pull request está bloqueado porque está desatualizado. pulls.blocked_by_outdated_branch=Este pull request está bloqueado porque está desatualizado.
@ -1845,16 +1824,12 @@ pulls.reject_count_1=%d pedido de alteração
pulls.reject_count_n=%d pedidos de alteração pulls.reject_count_n=%d pedidos de alteração
pulls.waiting_count_1=aguardando %d revisão pulls.waiting_count_1=aguardando %d revisão
pulls.waiting_count_n=aguardando %d revisões pulls.waiting_count_n=aguardando %d revisões
pulls.wrong_commit_id=id de commit tem que ser um id de commit no branch de destino
pulls.no_merge_desc=O merge deste pull request não pode ser aplicado porque todas as opções de mesclagem do repositório estão desabilitadas. pulls.no_merge_desc=O merge deste pull request não pode ser aplicado porque todas as opções de mesclagem do repositório estão desabilitadas.
pulls.no_merge_helper=Habilite as opções de merge nas configurações do repositório ou faça o merge do pull request manualmente. pulls.no_merge_helper=Habilite as opções de merge nas configurações do repositório ou faça o merge do pull request manualmente.
pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque está marcado como um trabalho em andamento. pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque está marcado como um trabalho em andamento.
pulls.no_merge_not_ready=Este pull request não está pronto para o merge, verifique o status da revisão e as verificações de status.
pulls.no_merge_access=Você não está autorizado para realizar o merge deste pull request. pulls.no_merge_access=Você não está autorizado para realizar o merge deste pull request.
pulls.merge_pull_request=Criar commit de merge pulls.merge_pull_request=Criar commit de merge
pulls.rebase_merge_pull_request=Rebase e fast-forward
pulls.rebase_merge_commit_pull_request=Rebase e criar commit de merge
pulls.squash_merge_pull_request=Criar commit de squash pulls.squash_merge_pull_request=Criar commit de squash
pulls.fast_forward_only_merge_pull_request=Apenas Fast-forward pulls.fast_forward_only_merge_pull_request=Apenas Fast-forward
pulls.merge_manually=Merge feito manualmente pulls.merge_manually=Merge feito manualmente
@ -1862,17 +1837,9 @@ pulls.merge_commit_id=A ID de merge commit
pulls.require_signed_wont_sign=O branch requer commits assinados, mas este merge não será assinado pulls.require_signed_wont_sign=O branch requer commits assinados, mas este merge não será assinado
pulls.invalid_merge_option=Você não pode usar esta opção de merge neste pull request. pulls.invalid_merge_option=Você não pode usar esta opção de merge neste pull request.
pulls.merge_conflict=Merge Falhou: houve um conflito ao fazer merge. Dica: Tente uma estratégia diferente.
pulls.merge_conflict_summary=Mensagem de Erro pulls.merge_conflict_summary=Mensagem de Erro
pulls.rebase_conflict=Merge Falhou: houve um conflito durante o rebase do commit %[1]s. Dica: Tente uma estratégia diferente.
pulls.rebase_conflict_summary=Mensagem de Erro pulls.rebase_conflict_summary=Mensagem de Erro
pulls.unrelated_histories=Merge Falhou: O merge do principal e da base não compartilham uma história comum. Dica: Tente uma estratégia diferente.
pulls.merge_out_of_date=Merge Falhou: durante a geração do merge, a base não foi atualizada. Dica: Tente novamente.
pulls.head_out_of_date=Merge Falhou: qnquanto gerava o merge, a head foi atualizada. Dica: Tente novamente.
pulls.has_merged=Falha: o merge do pull request foi realizado, você não pode fazer o merge novamente ou alterar o branch de destino.
pulls.push_rejected=Merge Falhou: O push foi rejeitado. Revise os Git Hooks para este repositório.
pulls.push_rejected_summary=Mensagem Completa da Rejeição pulls.push_rejected_summary=Mensagem Completa da Rejeição
pulls.push_rejected_no_message=Merge Falhou: O push foi rejeitado mas não houve mensagem remota. Revise os Git Hooks para este repositório.
pulls.open_unmerged_pull_exists=`Não é possível executar uma operação de reabertura pois há um pull request pendente (#%d) com propriedades idênticas.` pulls.open_unmerged_pull_exists=`Não é possível executar uma operação de reabertura pois há um pull request pendente (#%d) com propriedades idênticas.`
pulls.status_checking=Algumas verificações estão pendentes pulls.status_checking=Algumas verificações estão pendentes
pulls.status_checks_success=Todas as verificações foram bem sucedidas pulls.status_checks_success=Todas as verificações foram bem sucedidas
@ -1891,13 +1858,10 @@ pulls.outdated_with_base_branch=Este branch está desatualizado com o branch bas
pulls.close=Fechar Pull Request pulls.close=Fechar Pull Request
pulls.closed_at=`fechou este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>` pulls.closed_at=`fechou este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.reopened_at=`reabriu este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>` pulls.reopened_at=`reabriu este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.cmd_instruction_hint=Ver instruções de linha de comando
pulls.cmd_instruction_checkout_title=Checkout pulls.cmd_instruction_checkout_title=Checkout
pulls.cmd_instruction_checkout_desc=No repositório do seu projeto, faça checkout de um novo branch e teste as alterações.
pulls.cmd_instruction_merge_title=Merge pulls.cmd_instruction_merge_title=Merge
pulls.cmd_instruction_merge_desc=Faça merge das alterações e atualize no Gitea. pulls.cmd_instruction_merge_desc=Faça merge das alterações e atualize no Gitea.
pulls.clear_merge_message=Limpar mensagem do merge pulls.clear_merge_message=Limpar mensagem do merge
pulls.clear_merge_message_hint=Limpar a mensagem de merge só irá remover o conteúdo da mensagem de commit e manter trailers git gerados, como "Co-Authored-By …".
pulls.auto_merge_button_when_succeed=(Quando a verificação for bem-sucedida) pulls.auto_merge_button_when_succeed=(Quando a verificação for bem-sucedida)
pulls.auto_merge_when_succeed=Mesclar automaticamente quando todas as verificações forem bem sucedidas pulls.auto_merge_when_succeed=Mesclar automaticamente quando todas as verificações forem bem sucedidas
@ -1953,15 +1917,12 @@ milestones.filter_sort.most_issues=Com mais issues
milestones.filter_sort.least_issues=Com menos issues milestones.filter_sort.least_issues=Com menos issues
signing.will_sign=Esse commit será assinado com a chave "%s". signing.will_sign=Esse commit será assinado com a chave "%s".
signing.wont_sign.error=Ocorreu um erro ao verificar se o commit poderia ser assinado.
signing.wont_sign.nokey=Não há nenhuma chave disponível para assinar esse commit. signing.wont_sign.nokey=Não há nenhuma chave disponível para assinar esse commit.
signing.wont_sign.never=Commits nunca são assinados. signing.wont_sign.never=Commits nunca são assinados.
signing.wont_sign.always=Commits são sempre assinados. signing.wont_sign.always=Commits são sempre assinados.
signing.wont_sign.pubkey=O commit não será assinado porque você não tem uma chave pública associada à sua conta. signing.wont_sign.pubkey=O commit não será assinado porque você não tem uma chave pública associada à sua conta.
signing.wont_sign.twofa=Você deve ter a autenticação de dois fatores ativada para que os commits sejam assinados.
signing.wont_sign.parentsigned=O commit não será assinado, pois o commit pai não está assinado. signing.wont_sign.parentsigned=O commit não será assinado, pois o commit pai não está assinado.
signing.wont_sign.basesigned=O merge não será assinado, pois o commit base não está assinado. signing.wont_sign.basesigned=O merge não será assinado, pois o commit base não está assinado.
signing.wont_sign.headsigned=O merge não será assinado, pois o commit mais recente não está assinado.
signing.wont_sign.commitssigned=O merge não será assinado, pois todos os commits associados não estão assinados. signing.wont_sign.commitssigned=O merge não será assinado, pois todos os commits associados não estão assinados.
signing.wont_sign.approved=O merge não será assinado porque o PR não foi aprovado. signing.wont_sign.approved=O merge não será assinado porque o PR não foi aprovado.
signing.wont_sign.not_signed_in=Você não está conectado. signing.wont_sign.not_signed_in=Você não está conectado.
@ -1998,7 +1959,6 @@ wiki.original_git_entry_tooltip=Ver o arquivo Git original em vez de usar o link
activity=Atividade activity=Atividade
activity.navbar.pulse=Pulso activity.navbar.pulse=Pulso
activity.navbar.code_frequency=Frequência de Código
activity.navbar.contributors=Contribuidores activity.navbar.contributors=Contribuidores
activity.navbar.recent_commits=Commits Recentes activity.navbar.recent_commits=Commits Recentes
activity.period.filter_label=Período: activity.period.filter_label=Período:
@ -2044,7 +2004,6 @@ activity.title.releases_1=%d Versão
activity.title.releases_n=%d Versões activity.title.releases_n=%d Versões
activity.title.releases_published_by=%s publicada(s) por %s activity.title.releases_published_by=%s publicada(s) por %s
activity.published_release_label=Publicado activity.published_release_label=Publicado
activity.no_git_activity=Não houve nenhuma atividade de commit neste período.
activity.git_stats_exclude_merges=Excluindo merges, activity.git_stats_exclude_merges=Excluindo merges,
activity.git_stats_author_1=%d autor activity.git_stats_author_1=%d autor
activity.git_stats_author_n=%d autores activity.git_stats_author_n=%d autores
@ -2072,7 +2031,6 @@ contributors.contribution_type.additions=Adições
contributors.contribution_type.deletions=Exclusões contributors.contribution_type.deletions=Exclusões
settings=Configurações settings=Configurações
settings.desc=Configurações é onde você pode gerenciar as opções para o repositório.
settings.options=Repositório settings.options=Repositório
settings.public_access=Acesso Público settings.public_access=Acesso Público
settings.collaboration=Colaboradores settings.collaboration=Colaboradores
@ -2086,19 +2044,10 @@ settings.githooks=Hooks do Git
settings.basic_settings=Configurações Básicas settings.basic_settings=Configurações Básicas
settings.mirror_settings=Opções de Espelhamento settings.mirror_settings=Opções de Espelhamento
settings.mirror_settings.docs=Configure o seu repositório para sincronizar automaticamente commits, tags e branches de outro repositório. settings.mirror_settings.docs=Configure o seu repositório para sincronizar automaticamente commits, tags e branches de outro repositório.
settings.mirror_settings.docs.disabled_pull_mirror.instructions=Configure seu projeto para enviar automaticamente commits, tags e branches para outro repositório. Os espelhamentos pull foram desativados pelo administrador do site.
settings.mirror_settings.docs.disabled_push_mirror.instructions=Configure seu projeto para puxar automaticamente commits, tags e branches de outro repositório.
settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=No momento, isso só pode ser feito no menu "Nova migração". Para obter mais informações, consulte: settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=No momento, isso só pode ser feito no menu "Nova migração". Para obter mais informações, consulte:
settings.mirror_settings.docs.disabled_push_mirror.info=Os espelhamentos push foram desativados pelo administrador do site.
settings.mirror_settings.docs.no_new_mirrors=Seu repositório está espelhando alterações de ou para outro repositório. Lembre-se de que, no momento, não é possível criar novos espelhamentos.
settings.mirror_settings.docs.can_still_use=Embora não seja possível modificar os espelhos existentes ou criar novos espelhos, você ainda pode usar o espelho existente. settings.mirror_settings.docs.can_still_use=Embora não seja possível modificar os espelhos existentes ou criar novos espelhos, você ainda pode usar o espelho existente.
settings.mirror_settings.docs.pull_mirror_instructions=Para configurar um espelhamento de pull, consulte:
settings.mirror_settings.docs.more_information_if_disabled=Você pode saber mais sobre espelhos de push e pull aqui:
settings.mirror_settings.docs.doc_link_title=Como posso espelhar repositórios? settings.mirror_settings.docs.doc_link_title=Como posso espelhar repositórios?
settings.mirror_settings.docs.doc_link_pull_section=a seção "Extração de um repositório remoto" da documentação.
settings.mirror_settings.docs.pulling_remote_title=Extração de um repositório remoto
settings.mirror_settings.mirrored_repository=Repositório espelhado settings.mirror_settings.mirrored_repository=Repositório espelhado
settings.mirror_settings.pushed_repository=Repositório enviado
settings.mirror_settings.direction=Sentido settings.mirror_settings.direction=Sentido
settings.mirror_settings.direction.pull=Pull settings.mirror_settings.direction.pull=Pull
settings.mirror_settings.direction.push=Push settings.mirror_settings.direction.push=Push
@ -2109,8 +2058,6 @@ settings.mirror_settings.push_mirror.add=Adicionar Espelho de Push
settings.mirror_settings.push_mirror.edit_sync_time=Editar intervalo de sincronização de espelhos settings.mirror_settings.push_mirror.edit_sync_time=Editar intervalo de sincronização de espelhos
settings.sync_mirror=Sincronizar Agora settings.sync_mirror=Sincronizar Agora
settings.pull_mirror_sync_in_progress=Puxando modificações a partir do repositório remoto %s, neste momento.
settings.push_mirror_sync_in_progress=Enviando modificações para o repositório remoto %s, neste momento.
settings.site=Site settings.site=Site
settings.update_settings=Atualizar Configurações settings.update_settings=Atualizar Configurações
settings.update_mirror_settings=Atualizar Configurações de Espelhamento settings.update_mirror_settings=Atualizar Configurações de Espelhamento
@ -2126,24 +2073,24 @@ settings.external_wiki_url=URL Externa da Wiki
settings.external_wiki_url_error=A URL da wiki externa não é válida. settings.external_wiki_url_error=A URL da wiki externa não é válida.
settings.external_wiki_url_desc=Visitantes são redirecionados para a URL da wiki externa ao clicar na aba da wiki. settings.external_wiki_url_desc=Visitantes são redirecionados para a URL da wiki externa ao clicar na aba da wiki.
settings.issues_desc=Habilitar Issue Tracker para o Repositório settings.issues_desc=Habilitar Issue Tracker para o Repositório
settings.use_internal_issue_tracker=Usar o Issue Tracker Nativo settings.use_internal_issue_tracker=Usar o issue tracker nativo
settings.use_external_issue_tracker=Usar Issue Tracker Externo settings.use_external_issue_tracker=Usar issue tracker externo
settings.external_tracker_url=URL do Issue Tracker Externo settings.external_tracker_url=URL do Issue Tracker Externo
settings.external_tracker_url_error=A URL do issue tracker externo não é válida. settings.external_tracker_url_error=A URL do issue tracker externo não é válida.
settings.external_tracker_url_desc=Visitantes são redirecionados para a URL do issue tracker externo ao clicar na aba de issues. settings.external_tracker_url_desc=Visitantes são redirecionados para a URL do issue tracker externo ao clicar na aba de issues.
settings.tracker_url_format=Formato de URL do issue tracker externo settings.tracker_url_format=Formato de URL do issue tracker externo
settings.tracker_url_format_error=O formato da URL do issue tracker externo não é válido. settings.tracker_url_format_error=O formato da URL do issue tracker externo não é válido.
settings.tracker_issue_style=Formato de Número do Issue Tracker Externo settings.tracker_issue_style=Formato de número do issue tracker externo
settings.tracker_issue_style.numeric=Numérico settings.tracker_issue_style.numeric=Numérico
settings.tracker_issue_style.alphanumeric=Alfanumérico settings.tracker_issue_style.alphanumeric=Alfanumérico
settings.tracker_issue_style.regexp=Expressão Regular settings.tracker_issue_style.regexp=Expressão Regular
settings.tracker_issue_style.regexp_pattern=Padrão de Expressão Regular settings.tracker_issue_style.regexp_pattern=Padrão de expressão regular
settings.tracker_issue_style.regexp_pattern_desc=O primeiro grupo capturado será usado no lugar de <code>{index}</code>. settings.tracker_issue_style.regexp_pattern_desc=O primeiro grupo capturado será usado no lugar de <code>{index}</code>.
settings.tracker_url_format_desc=Use os espaços reservados <code>{user}</code>, <code>{repo}</code> e <code>{index}</code> para o nome de usuário, nome do repositório e o índice de problemas. settings.tracker_url_format_desc=Use os espaços reservados <code>{user}</code>, <code>{repo}</code> e <code>{index}</code> para o nome de usuário, nome do repositório e o índice de problemas.
settings.enable_timetracker=Habilitar Cronômetro settings.enable_timetracker=Habilitar Cronômetro
settings.allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo settings.allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo
settings.pulls_desc=Habilitar Pull Requests no Repositório settings.pulls_desc=Habilitar pull requests no repositório
settings.pulls.ignore_whitespace=Ignorar Espaço em Branco em Conflitos settings.pulls.ignore_whitespace=Ignorar espaço em branco em conflitos
settings.pulls.enable_autodetect_manual_merge=Habilitar a detecção automática de merge manual (Nota: Em alguns casos especiais, podem ocorrer julgamentos errados) settings.pulls.enable_autodetect_manual_merge=Habilitar a detecção automática de merge manual (Nota: Em alguns casos especiais, podem ocorrer julgamentos errados)
settings.pulls.allow_rebase_update=Ativar atualização do branch do pull request por rebase settings.pulls.allow_rebase_update=Ativar atualização do branch do pull request por rebase
settings.pulls.default_delete_branch_after_merge=Excluir o branch de pull request após o merge por padrão settings.pulls.default_delete_branch_after_merge=Excluir o branch de pull request após o merge por padrão
@ -2151,8 +2098,6 @@ settings.pulls.default_allow_edits_from_maintainers=Permitir edições de manten
settings.releases_desc=Habilitar versões do Repositório settings.releases_desc=Habilitar versões do Repositório
settings.packages_desc=Habilitar Registro de Pacotes de Repositório settings.packages_desc=Habilitar Registro de Pacotes de Repositório
settings.projects_desc=Habilitar Projetos settings.projects_desc=Habilitar Projetos
settings.projects_mode_repo=Somente projetos no repositório
settings.projects_mode_owner=Apenas projetos do usuário ou org
settings.projects_mode_all=Todos os projetos settings.projects_mode_all=Todos os projetos
settings.actions_desc=Habilitar Ações do Repositório settings.actions_desc=Habilitar Ações do Repositório
settings.admin_settings=Configurações do Administrador settings.admin_settings=Configurações do Administrador
@ -2166,7 +2111,7 @@ settings.reindex_requested=Reindexação Requisitada
settings.admin_enable_close_issues_via_commit_in_any_branch=Fechar issue via commit em um branch não padrão settings.admin_enable_close_issues_via_commit_in_any_branch=Fechar issue via commit em um branch não padrão
settings.danger_zone=Zona de Perigo settings.danger_zone=Zona de Perigo
settings.new_owner_has_same_repo=O novo proprietário já tem um repositório com o mesmo nome. Por favor, escolha outro nome. settings.new_owner_has_same_repo=O novo proprietário já tem um repositório com o mesmo nome. Por favor, escolha outro nome.
settings.convert=Converter para Repositório Tradicional settings.convert=Converter para repositório tradicional
settings.convert_desc=Você pode converter este espelhamento em um repositório tradicional. Esta ação não pode ser revertida. settings.convert_desc=Você pode converter este espelhamento em um repositório tradicional. Esta ação não pode ser revertida.
settings.convert_notices_1=Esta operação vai converter este espelhamento em um repositório tradicional. Esta ação não pode ser desfeita. settings.convert_notices_1=Esta operação vai converter este espelhamento em um repositório tradicional. Esta ação não pode ser desfeita.
settings.convert_confirm=Converter o Repositório settings.convert_confirm=Converter o Repositório
@ -2184,7 +2129,6 @@ settings.transfer_abort_invalid=Não é possível cancelar uma transferência de
settings.transfer_abort_success=A transferência do repositório para %s foi cancelada com sucesso. settings.transfer_abort_success=A transferência do repositório para %s foi cancelada com sucesso.
settings.transfer_desc=Transferir este repositório para outro usuário ou para uma organização onde você tem direitos de administrador. settings.transfer_desc=Transferir este repositório para outro usuário ou para uma organização onde você tem direitos de administrador.
settings.transfer_form_title=Digite o nome do repositório para confirmar: settings.transfer_form_title=Digite o nome do repositório para confirmar:
settings.transfer_in_progress=Há uma transferência em andamento. Por favor, cancele se você gostaria de transferir este repositório para outro usuário.
settings.transfer_notices_1=- Você perderá o acesso ao repositório se transferir para um usuário individual. settings.transfer_notices_1=- Você perderá o acesso ao repositório se transferir para um usuário individual.
settings.transfer_notices_2=- Você manterá acesso ao repositório se transferi-lo para uma organização que você também é proprietário. settings.transfer_notices_2=- Você manterá acesso ao repositório se transferi-lo para uma organização que você também é proprietário.
settings.transfer_notices_3=- Se o repositório for privado e for transferido para um usuário individual, esta ação certifica que o usuário tem pelo menos permissão de leitura (e altera as permissões se necessário). settings.transfer_notices_3=- Se o repositório for privado e for transferido para um usuário individual, esta ação certifica que o usuário tem pelo menos permissão de leitura (e altera as permissões se necessário).
@ -2198,13 +2142,9 @@ settings.trust_model.default=Modelo Padrão de Confiança
settings.trust_model.default.desc=Use o modelo de confiança de repositório padrão para esta instalação. settings.trust_model.default.desc=Use o modelo de confiança de repositório padrão para esta instalação.
settings.trust_model.collaborator=Colaborador settings.trust_model.collaborator=Colaborador
settings.trust_model.collaborator.long=Colaborador: Confiar em assinaturas feitas por colaboradores settings.trust_model.collaborator.long=Colaborador: Confiar em assinaturas feitas por colaboradores
settings.trust_model.collaborator.desc=Assinaturas válidas dos colaboradores deste repositório serão marcadas como "confiáveis" - (quer correspondam ao autor do commit ou não). Caso contrário, assinaturas válidas serão marcadas como "não confiáveis" se a assinatura corresponder ao autor do commit e "não corresponde" se não corresponder.
settings.trust_model.committer=Committer settings.trust_model.committer=Committer
settings.trust_model.committer.long=Committer: Confiar nas assinaturas que correspondam aos autores de commits (isso corresponde ao GitHub e forçará commits assinados pelo Gitea a ter o Gitea como o autor do commit).
settings.trust_model.committer.desc=As assinaturas válidas só serão marcadas como "confiáveis" se corresponderem ao autor do commit; caso contrário, serão marcadas como "não correspondentes". Isso força o Gitea a ser o autor do commit em commits assinados com o autor do commit real marcado como Co-authored-by: e Co-committed-by: no rodapé do commit. A chave padrão do Gitea deve corresponder a um usuário no banco de dados.
settings.trust_model.collaboratorcommitter=Colaborador+Commiter settings.trust_model.collaboratorcommitter=Colaborador+Commiter
settings.trust_model.collaboratorcommitter.long=Colaborador+Committer: Confiar na assinatura dos colaboradores que correspondem ao autor do commit settings.trust_model.collaboratorcommitter.long=Colaborador+Committer: Confiar na assinatura dos colaboradores que correspondem ao autor do commit
settings.trust_model.collaboratorcommitter.desc=Assinaturas válidas dos colaboradores deste repositório serão marcadas como "confiáveis" se corresponderem ao autor do commit. Caso contrário, as assinaturas válidas serão marcadas como "não confiáveis" se a assinatura corresponder ao autor do commit e "não corresponde" caso contrário. Isso forçará o Gitea a ser marcado como o autor do commit nos commits assinados com o autor marcado como Co-Authored-By: e o Committed-By: resumo do commit. A chave padrão do Gitea tem que corresponder a um usuário no banco de dados.
settings.wiki_delete=Excluir Dados da Wiki settings.wiki_delete=Excluir Dados da Wiki
settings.wiki_delete_desc=A exclusão de dados da wiki é permanente e não pode ser desfeita. settings.wiki_delete_desc=A exclusão de dados da wiki é permanente e não pode ser desfeita.
settings.wiki_delete_notices_1=- Isso excluirá e desabilitará permanentemente a wiki do repositório %s. settings.wiki_delete_notices_1=- Isso excluirá e desabilitará permanentemente a wiki do repositório %s.
@ -2213,11 +2153,9 @@ settings.wiki_deletion_success=Os dados da wiki do repositório foi excluídos.
settings.delete=Excluir Este Repositório settings.delete=Excluir Este Repositório
settings.delete_desc=A exclusão de um repositório é permanente e não pode ser desfeita. settings.delete_desc=A exclusão de um repositório é permanente e não pode ser desfeita.
settings.delete_notices_1=- Esta operação <strong>NÃO PODERÁ</strong> ser desfeita. settings.delete_notices_1=- Esta operação <strong>NÃO PODERÁ</strong> ser desfeita.
settings.delete_notices_2=- Essa operação excluirá permanentemente o repositório <strong>%s</strong>, incluindo código, issues, comentários, dados da wiki e configurações do colaborador.
settings.delete_notices_fork_1=- Forks deste repositório se tornarão independentes após a exclusão. settings.delete_notices_fork_1=- Forks deste repositório se tornarão independentes após a exclusão.
settings.deletion_success=O repositório foi excluído. settings.deletion_success=O repositório foi excluído.
settings.update_settings_success=As configurações do repositório foram atualizadas. settings.update_settings_success=As configurações do repositório foram atualizadas.
settings.update_settings_no_unit=O repositório deve permitir pelo menos algum tipo de interação.
settings.confirm_delete=Excluir Repositório settings.confirm_delete=Excluir Repositório
settings.add_collaborator=Adicionar Colaborador settings.add_collaborator=Adicionar Colaborador
settings.add_collaborator_success=O colaborador foi adicionado. settings.add_collaborator_success=O colaborador foi adicionado.
@ -2234,8 +2172,6 @@ settings.team_not_in_organization=A equipe não está na mesma organização que
settings.teams=Equipes settings.teams=Equipes
settings.add_team=Adicionar Equipe settings.add_team=Adicionar Equipe
settings.add_team_duplicate=A equipe já tem o repositório settings.add_team_duplicate=A equipe já tem o repositório
settings.add_team_success=A equipe agora tem acesso ao repositório.
settings.change_team_permission_tip=A permissão da equipe está definida na página de configurações da equipe e não pode ser alterada por repositório
settings.delete_team_tip=Esta equipe tem acesso a todos os repositórios e não pode ser removida settings.delete_team_tip=Esta equipe tem acesso a todos os repositórios e não pode ser removida
settings.remove_team_success=O acesso da equipe ao repositório foi removido. settings.remove_team_success=O acesso da equipe ao repositório foi removido.
settings.add_webhook=Adicionar Webhook settings.add_webhook=Adicionar Webhook
@ -2245,15 +2181,12 @@ settings.webhook_deletion=Remover Webhook
settings.webhook_deletion_desc=A exclusão de um webhook exclui suas configurações e o histórico de entrega. Continuar? settings.webhook_deletion_desc=A exclusão de um webhook exclui suas configurações e o histórico de entrega. Continuar?
settings.webhook_deletion_success=O webhook foi removido. settings.webhook_deletion_success=O webhook foi removido.
settings.webhook.test_delivery=Testar Evento de Push settings.webhook.test_delivery=Testar Evento de Push
settings.webhook.test_delivery_desc=Teste este webhook com um evento falso.
settings.webhook.test_delivery_desc_disabled=Para testar este webhook com um evento falso, ative-o.
settings.webhook.request=Solicitação settings.webhook.request=Solicitação
settings.webhook.response=Resposta settings.webhook.response=Resposta
settings.webhook.headers=Cabeçalhos settings.webhook.headers=Cabeçalhos
settings.webhook.payload=Conteúdo settings.webhook.payload=Conteúdo
settings.webhook.body=Corpo settings.webhook.body=Corpo
settings.webhook.replay.description=Executar novamente esse webhook. settings.webhook.replay.description=Executar novamente esse webhook.
settings.webhook.replay.description_disabled=Para repetir este webhook, ative-o.
settings.webhook.delivery.success=Um evento foi adicionado à fila de envio. Pode levar alguns segundos até que ele apareça no histórico de envio. settings.webhook.delivery.success=Um evento foi adicionado à fila de envio. Pode levar alguns segundos até que ele apareça no histórico de envio.
settings.githooks_desc=Hooks do Git são executados pelo próprio Git. Você pode editar arquivos de hook abaixo para configurar operações personalizadas. settings.githooks_desc=Hooks do Git são executados pelo próprio Git. Você pode editar arquivos de hook abaixo para configurar operações personalizadas.
settings.githook_edit_desc=Se o hook não estiver ativo, o conteúdo de exemplo será apresentado. Deixar o conteúdo em branco irá desabilitar esse hook. settings.githook_edit_desc=Se o hook não estiver ativo, o conteúdo de exemplo será apresentado. Deixar o conteúdo em branco irá desabilitar esse hook.
@ -2316,8 +2249,6 @@ settings.event_pull_request_review=Pull Request Revisado
settings.event_pull_request_review_desc=Pull request aprovado, rejeitado ou revisão comentada. settings.event_pull_request_review_desc=Pull request aprovado, rejeitado ou revisão comentada.
settings.event_pull_request_sync=Pull Request Sincronizado settings.event_pull_request_sync=Pull Request Sincronizado
settings.event_pull_request_sync_desc=Pull request sincronizado. settings.event_pull_request_sync_desc=Pull request sincronizado.
settings.event_pull_request_review_request=Revisão de Pull Request Solicitada
settings.event_pull_request_review_request_desc=Revisão de pull request solicitada ou solicitação de revisão removida.
settings.event_package=Pacote settings.event_package=Pacote
settings.event_package_desc=Pacote criado ou excluído em um repositório. settings.event_package_desc=Pacote criado ou excluído em um repositório.
settings.branch_filter=Filtro de branch settings.branch_filter=Filtro de branch
@ -2386,7 +2317,7 @@ settings.protect_check_status_contexts_list=Verificações de status encontradas
settings.protect_required_approvals=Aprovações necessárias: settings.protect_required_approvals=Aprovações necessárias:
settings.dismiss_stale_approvals=Descartar aprovações obsoletas settings.dismiss_stale_approvals=Descartar aprovações obsoletas
settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas. settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas.
settings.require_signed_commits=Exigir Commits Assinados settings.require_signed_commits=Exigir commits assinados
settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis. settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis.
settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida
settings.protect_patterns=Padrões settings.protect_patterns=Padrões
@ -2397,7 +2328,6 @@ settings.delete_protected_branch=Desabilitar proteção
settings.update_protect_branch_success=Proteção do branch "%s" foi atualizada. settings.update_protect_branch_success=Proteção do branch "%s" foi atualizada.
settings.remove_protected_branch_success=Proteção do branch "%s" foi desabilitada. settings.remove_protected_branch_success=Proteção do branch "%s" foi desabilitada.
settings.remove_protected_branch_failed=Removendo regra de proteção de branch "%s" falhou. settings.remove_protected_branch_failed=Removendo regra de proteção de branch "%s" falhou.
settings.protected_branch_deletion=Excluir Proteção de Branch
settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar? settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar?
settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas
settings.block_rejected_reviews_desc=O merge não será possível quando são solicitadas alterações pelos revisores oficiais, mesmo que haja aprovação suficiente. settings.block_rejected_reviews_desc=O merge não será possível quando são solicitadas alterações pelos revisores oficiais, mesmo que haja aprovação suficiente.
@ -2429,7 +2359,6 @@ settings.matrix.room_id=ID da Sala
settings.matrix.message_type=Tipo de Mensagem settings.matrix.message_type=Tipo de Mensagem
settings.visibility.private.button=Tornar Privado settings.visibility.private.button=Tornar Privado
settings.visibility.public.button=Tornar Público settings.visibility.public.button=Tornar Público
settings.visibility.public.bullet_one=Deixe o repositório visível para qualquer pessoa.
settings.archive.button=Arquivar Repositório settings.archive.button=Arquivar Repositório
settings.archive.header=Arquivar Este Repositório settings.archive.header=Arquivar Este Repositório
settings.archive.success=O repositório foi arquivado com sucesso. settings.archive.success=O repositório foi arquivado com sucesso.
@ -2858,7 +2787,6 @@ users.activated=Ativado
users.admin=Administrador users.admin=Administrador
users.restricted=Restrito users.restricted=Restrito
users.reserved=Reservado users.reserved=Reservado
users.bot=Bot
users.remote=Remoto users.remote=Remoto
users.2fa=2FA users.2fa=2FA
users.repos=Repositórios users.repos=Repositórios
@ -3608,7 +3536,6 @@ variables.update.success=A variável foi editada.
type-1.display_name=Projeto Individual type-1.display_name=Projeto Individual
type-2.display_name=Projeto do Repositório type-2.display_name=Projeto do Repositório
type-3.display_name=Projeto da Organização type-3.display_name=Projeto da Organização
exit_fullscreen=Sair da Tela Cheia
[git.filemode] [git.filemode]
changed_filemode=%[1]s → %[2]s changed_filemode=%[1]s → %[2]s

View File

@ -551,13 +551,6 @@ repo.transfer.body=访问 %s 以接受或拒绝转移,亦可忽略此邮件。
repo.collaborator.added.subject=%s 把您添加到 %s repo.collaborator.added.subject=%s 把您添加到 %s
repo.collaborator.added.text=您已被添加为仓库的协作者: repo.collaborator.added.text=您已被添加为仓库的协作者:
repo.actions.run.failed=运行失败
repo.actions.run.succeeded=运行成功
repo.actions.run.cancelled=运行已取消
repo.actions.jobs.all_succeeded=所有任务已成功
repo.actions.jobs.all_failed=所有任务已失败
repo.actions.jobs.some_not_successful=一些任务未成功
repo.actions.jobs.all_cancelled=所有任务已取消
team_invite.subject=%[1]s 邀请您加入组织 %[2]s team_invite.subject=%[1]s 邀请您加入组织 %[2]s
team_invite.text_1=%[1]s 邀请您加入组织 %[3]s 中的团队 %[2]s。 team_invite.text_1=%[1]s 邀请您加入组织 %[3]s 中的团队 %[2]s。
@ -3730,14 +3723,10 @@ swift.install=在您的 <code>Package.swift</code> 文件中添加该包:
swift.install2=并运行以下命令: swift.install2=并运行以下命令:
vagrant.install=若要添加一个 Vagrant box请运行以下命令 vagrant.install=若要添加一个 Vagrant box请运行以下命令
settings.link=将此软件包链接到仓库 settings.link=将此软件包链接到仓库
settings.link.description=如果您将一个软件包与一个仓库链接起来,软件包将显示在仓库的软件包列表中。
settings.link.select=选择仓库 settings.link.select=选择仓库
settings.link.button=更新仓库链接 settings.link.button=更新仓库链接
settings.link.success=仓库链接已成功更新。 settings.link.success=仓库链接已成功更新。
settings.link.error=更新仓库链接失败。 settings.link.error=更新仓库链接失败。
settings.link.repo_not_found=仓库 %s 未找到。
settings.unlink.error=删除仓库链接失败。
settings.unlink.success=仓库链接已成功删除。
settings.delete=删除软件包 settings.delete=删除软件包
settings.delete.description=删除软件包是永久性的,无法撤消。 settings.delete.description=删除软件包是永久性的,无法撤消。
settings.delete.notice=您将要删除 %s (%s)。此操作是不可逆的,您确定吗? settings.delete.notice=您将要删除 %s (%s)。此操作是不可逆的,您确定吗?

View File

@ -351,7 +351,7 @@ func ListIssues(ctx *context.APIContext) {
// enum: [closed, open, all] // enum: [closed, open, all]
// - name: labels // - name: labels
// in: query // in: query
// description: comma separated list of label names. Fetch only issues that have any of this label names. Non existent labels are discarded. // description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
// type: string // type: string
// - name: q // - name: q
// in: query // in: query

View File

@ -1620,7 +1620,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
return return
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, baseGitRepo, startCommitID, endCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(baseGitRepo, startCommitID, endCommitID)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return

View File

@ -8,7 +8,7 @@ import (
"time" "time"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
@ -56,7 +56,7 @@ func Activity(ctx *context.Context) {
canReadCode := ctx.Repo.CanRead(unit.TypeCode) canReadCode := ctx.Repo.CanRead(unit.TypeCode)
if canReadCode { if canReadCode {
// GetActivityStats needs to read the default branch to get some information // GetActivityStats needs to read the default branch to get some information
branchExist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, ctx.Repo.Repository.DefaultBranch) branchExist, _ := git.IsBranchExist(ctx, ctx.Repo.Repository.ID, ctx.Repo.Repository.DefaultBranch)
if !branchExist { if !branchExist {
ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch) ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch)
ctx.NotFound(nil) ctx.NotFound(nil)

View File

@ -324,7 +324,7 @@ func Diff(ctx *context.Context) {
ctx.NotFound(err) ctx.NotFound(err)
return return
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, gitRepo, "", commitID) diffShortStat, err := gitdiff.GetDiffShortStat(gitRepo, "", commitID)
if err != nil { if err != nil {
ctx.ServerError("GetDiffShortStat", err) ctx.ServerError("GetDiffShortStat", err)
return return

View File

@ -631,7 +631,7 @@ func PrepareCompareDiff(
ctx.ServerError("GetDiff", err) ctx.ServerError("GetDiff", err)
return false return false
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ci.HeadRepo, ci.HeadGitRepo, beforeCommitID, headCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(ci.HeadGitRepo, beforeCommitID, headCommitID)
if err != nil { if err != nil {
ctx.ServerError("GetDiffShortStat", err) ctx.ServerError("GetDiffShortStat", err)
return false return false

View File

@ -9,14 +9,12 @@ import (
"html/template" "html/template"
"net/http" "net/http"
"strconv" "strconv"
"strings"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/renderhelper" "code.gitea.io/gitea/models/renderhelper"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
@ -126,8 +124,8 @@ func NewComment(ctx *context.Context) {
ctx.JSONError("The origin branch is delete, cannot reopen.") ctx.JSONError("The origin branch is delete, cannot reopen.")
return return
} }
headBranchRef := git.RefNameFromBranch(pull.HeadBranch) headBranchRef := pull.GetGitHeadBranchRefName()
headBranchCommitID, err := git.GetFullCommitID(ctx, pull.HeadRepo.RepoPath(), headBranchRef.String()) headBranchCommitID, err := git.GetFullCommitID(ctx, pull.HeadRepo.RepoPath(), headBranchRef)
if err != nil { if err != nil {
ctx.ServerError("Get head commit Id of head branch fail", err) ctx.ServerError("Get head commit Id of head branch fail", err)
return return
@ -289,10 +287,9 @@ func UpdateCommentContent(ctx *context.Context) {
ctx.ServerError("RenderString", err) ctx.ServerError("RenderString", err)
return return
} }
} } else {
contentEmpty := fmt.Sprintf(`<span class="no-content">%s</span>`, ctx.Tr("repo.issues.no_content"))
if strings.TrimSpace(string(renderedContent)) == "" { renderedContent = template.HTML(contentEmpty)
renderedContent = htmlutil.HTMLFormat(`<span class="no-content">%s</span>`, ctx.Tr("repo.issues.no_content"))
} }
ctx.JSON(http.StatusOK, map[string]any{ ctx.JSON(http.StatusOK, map[string]any{

View File

@ -201,7 +201,7 @@ func GetPullDiffStats(ctx *context.Context) {
log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName()) log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName())
return return
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID)
if err != nil { if err != nil {
log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName()) log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName())
return return
@ -761,7 +761,7 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
} }
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, beforeCommitID, afterCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, beforeCommitID, afterCommitID)
if err != nil { if err != nil {
ctx.ServerError("GetDiffShortStat", err) ctx.ServerError("GetDiffShortStat", err)
return return

View File

@ -210,7 +210,7 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
// Get diff stats for commit // Get diff stats for commit
if opts.Stat { if opts.Stat {
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, repo, gitRepo, "", commit.ID.String()) diffShortStat, err := gitdiff.GetDiffShortStat(gitRepo, "", commit.ID.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -235,7 +235,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
// Calculate diff // Calculate diff
startCommitID = pr.MergeBase startCommitID = pr.MergeBase
diffShortStats, err := gitdiff.GetDiffShortStat(ctx, pr.BaseRepo, gitRepo, startCommitID, endCommitID) diffShortStats, err := gitdiff.GetDiffShortStat(gitRepo, startCommitID, endCommitID)
if err != nil { if err != nil {
log.Error("GetDiffShortStat: %v", err) log.Error("GetDiffShortStat: %v", err)
} else { } else {

View File

@ -21,14 +21,12 @@ import (
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
pull_model "code.gitea.io/gitea/models/pull" pull_model "code.gitea.io/gitea/models/pull"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/git/attribute"
"code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -1273,7 +1271,9 @@ type DiffShortStat struct {
NumFiles, TotalAddition, TotalDeletion int NumFiles, TotalAddition, TotalDeletion int
} }
func GetDiffShortStat(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, beforeCommitID, afterCommitID string) (*DiffShortStat, error) { func GetDiffShortStat(gitRepo *git.Repository, beforeCommitID, afterCommitID string) (*DiffShortStat, error) {
repoPath := gitRepo.Path
afterCommit, err := gitRepo.GetCommit(afterCommitID) afterCommit, err := gitRepo.GetCommit(afterCommitID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1285,7 +1285,7 @@ func GetDiffShortStat(ctx context.Context, repo *repo_model.Repository, gitRepo
} }
diff := &DiffShortStat{} diff := &DiffShortStat{}
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = gitrepo.GetDiffShortStatByCmdArgs(ctx, repo, nil, actualBeforeCommitID.String(), afterCommitID) diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStatByCmdArgs(gitRepo.Ctx, repoPath, nil, actualBeforeCommitID.String(), afterCommitID)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -50,7 +50,7 @@ var (
func markPullRequestStatusAsChecking(ctx context.Context, pr *issues_model.PullRequest) bool { func markPullRequestStatusAsChecking(ctx context.Context, pr *issues_model.PullRequest) bool {
pr.Status = issues_model.PullRequestStatusChecking pr.Status = issues_model.PullRequestStatusChecking
_, err := pr.UpdateColsIfNotMerged(ctx, "status") err := pr.UpdateColsIfNotMerged(ctx, "status")
if err != nil { if err != nil {
log.Error("UpdateColsIfNotMerged failed, pr: %-v, err: %v", pr, err) log.Error("UpdateColsIfNotMerged failed, pr: %-v, err: %v", pr, err)
return false return false
@ -256,7 +256,7 @@ func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullReques
return return
} }
if _, err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil { if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil {
log.Error("Update[%-v]: %v", pr, err) log.Error("Update[%-v]: %v", pr, err)
} }

View File

@ -99,6 +99,13 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
return err return err
} }
divergence, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
if err != nil {
return err
}
pr.CommitsAhead = divergence.Ahead
pr.CommitsBehind = divergence.Behind
assigneeCommentMap := make(map[int64]*issues_model.Comment) assigneeCommentMap := make(map[int64]*issues_model.Comment)
var reviewNotifiers []*issue_service.ReviewRequestNotifier var reviewNotifiers []*issue_service.ReviewRequestNotifier
@ -127,12 +134,6 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
return err return err
} }
// Update Commit Divergence
err = syncCommitDivergence(ctx, pr)
if err != nil {
return err
}
// add first push codes comment // add first push codes comment
if _, err := CreatePushPullComment(ctx, issue.Poster, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false); err != nil { if _, err := CreatePushPullComment(ctx, issue.Poster, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false); err != nil {
return err return err
@ -286,20 +287,24 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
pr.Status = issues_model.PullRequestStatusMergeable pr.Status = issues_model.PullRequestStatusMergeable
} }
// add first push codes comment // Update Commit Divergence
return db.WithTx(ctx, func(ctx context.Context) error { divergence, err := GetDiverging(ctx, pr)
// The UPDATE acquires the transaction lock, if the UPDATE succeeds, it should have updated one row (the "base_branch" is changed) if err != nil {
// If no row is updated, it means the PR has been merged or closed in the meantime return err
updated, err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch") }
if err != nil { pr.CommitsAhead = divergence.Ahead
return err pr.CommitsBehind = divergence.Behind
}
if updated == 0 {
return util.ErrorWrap(util.ErrInvalidArgument, "pull request status has changed")
}
if err := syncCommitDivergence(ctx, pr); err != nil { // add first push codes comment
return fmt.Errorf("syncCommitDivergence: %w", err) baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
if err != nil {
return err
}
defer baseGitRepo.Close()
return db.WithTx(ctx, func(ctx context.Context) error {
if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch", "commits_ahead", "commits_behind"); err != nil {
return err
} }
// Create comment // Create comment
@ -339,7 +344,7 @@ func checkForInvalidation(ctx context.Context, requests issues_model.PullRequest
} }
go func() { go func() {
// FIXME: graceful: We need to tell the manager we're doing something... // FIXME: graceful: We need to tell the manager we're doing something...
err := InvalidateCodeComments(ctx, requests, doer, repo, gitRepo, branch) err := InvalidateCodeComments(ctx, requests, doer, gitRepo, branch)
if err != nil { if err != nil {
log.Error("PullRequestList.InvalidateCodeComments: %v", err) log.Error("PullRequestList.InvalidateCodeComments: %v", err)
} }
@ -367,21 +372,15 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
// If you don't let it run all the way then you will lose data // If you don't let it run all the way then you will lose data
// TODO: graceful: AddTestPullRequestTask needs to become a queue! // TODO: graceful: AddTestPullRequestTask needs to become a queue!
repo, err := repo_model.GetRepositoryByID(ctx, opts.RepoID)
if err != nil {
log.Error("GetRepositoryByID: %v", err)
return
}
// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR. // GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
headBranchPRs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, opts.RepoID, opts.Branch) prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, opts.RepoID, opts.Branch)
if err != nil { if err != nil {
log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", opts.RepoID, opts.Branch, err) log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", opts.RepoID, opts.Branch, err)
return return
} }
for _, pr := range headBranchPRs { for _, pr := range prs {
log.Trace("Updating PR[%d]: composing new test task", pr.ID) log.Trace("Updating PR[%d]: composing new test task", pr.ID)
pr.HeadRepo = repo // avoid loading again
if pr.Flow == issues_model.PullRequestFlowGithub { if pr.Flow == issues_model.PullRequestFlowGithub {
if err := PushToBaseRepo(ctx, pr); err != nil { if err := PushToBaseRepo(ctx, pr); err != nil {
log.Error("PushToBaseRepo: %v", err) log.Error("PushToBaseRepo: %v", err)
@ -399,14 +398,14 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
} }
if opts.IsSync { if opts.IsSync {
if err = headBranchPRs.LoadAttributes(ctx); err != nil { if err = prs.LoadAttributes(ctx); err != nil {
log.Error("PullRequestList.LoadAttributes: %v", err) log.Error("PullRequestList.LoadAttributes: %v", err)
} }
if invalidationErr := checkForInvalidation(ctx, headBranchPRs, opts.RepoID, opts.Doer, opts.Branch); invalidationErr != nil { if invalidationErr := checkForInvalidation(ctx, prs, opts.RepoID, opts.Doer, opts.Branch); invalidationErr != nil {
log.Error("checkForInvalidation: %v", invalidationErr) log.Error("checkForInvalidation: %v", invalidationErr)
} }
if err == nil { if err == nil {
for _, pr := range headBranchPRs { for _, pr := range prs {
objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
if opts.NewCommitID != "" && opts.NewCommitID != objectFormat.EmptyObjectID().String() { if opts.NewCommitID != "" && opts.NewCommitID != objectFormat.EmptyObjectID().String() {
changed, err := checkIfPRContentChanged(ctx, pr, opts.OldCommitID, opts.NewCommitID) changed, err := checkIfPRContentChanged(ctx, pr, opts.OldCommitID, opts.NewCommitID)
@ -433,8 +432,14 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitID); err != nil { if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitID); err != nil {
log.Error("MarkReviewsAsNotStale: %v", err) log.Error("MarkReviewsAsNotStale: %v", err)
} }
if err = syncCommitDivergence(ctx, pr); err != nil { divergence, err := GetDiverging(ctx, pr)
log.Error("syncCommitDivergence: %v", err) if err != nil {
log.Error("GetDiverging: %v", err)
} else {
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
if err != nil {
log.Error("UpdateCommitDivergence: %v", err)
}
} }
} }
@ -454,22 +459,24 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
} }
log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", opts.RepoID, opts.Branch) log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", opts.RepoID, opts.Branch)
// The base repositories of baseBranchPRs are the same one (opts.RepoID) prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, opts.RepoID, opts.Branch)
baseBranchPRs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, opts.RepoID, opts.Branch)
if err != nil { if err != nil {
log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", opts.RepoID, opts.Branch, err) log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", opts.RepoID, opts.Branch, err)
return return
} }
for _, pr := range baseBranchPRs { for _, pr := range prs {
pr.BaseRepo = repo // avoid loading again divergence, err := GetDiverging(ctx, pr)
err = syncCommitDivergence(ctx, pr)
if err != nil { if err != nil {
if errors.Is(err, util.ErrNotExist) { if git_model.IsErrBranchNotExist(err) && !gitrepo.IsBranchExist(ctx, pr.HeadRepo, pr.HeadBranch) {
log.Warn("Cannot test PR %s/%d with base=%s head=%s: no longer exists", pr.BaseRepo.FullName(), pr.IssueID, pr.BaseBranch, pr.HeadBranch) log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
} else { } else {
log.Error("syncCommitDivergence: %v", err) log.Error("GetDiverging: %v", err)
}
} else {
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
if err != nil {
log.Error("UpdateCommitDivergence: %v", err)
} }
continue
} }
StartPullRequestCheckDelayable(ctx, pr) StartPullRequestCheckDelayable(ctx, pr)
} }
@ -479,7 +486,7 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
// checkIfPRContentChanged checks if diff to target branch has changed by push // checkIfPRContentChanged checks if diff to target branch has changed by push
// A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) { func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) // FIXME: why it still needs to create a temp repo, since the alongside calls like GetDiverging doesn't do so anymore prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
if err != nil { if err != nil {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err) log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
return false, err return false, err

View File

@ -47,25 +47,11 @@ func (err ErrDismissRequestOnClosedPR) Unwrap() error {
// ErrSubmitReviewOnClosedPR represents an error when an user tries to submit an approve or reject review associated to a closed or merged PR. // ErrSubmitReviewOnClosedPR represents an error when an user tries to submit an approve or reject review associated to a closed or merged PR.
var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR") var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR")
// LineBlame returns the latest commit at the given line
func lineBlame(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, branch, file string, line uint) (*git.Commit, error) {
sha, err := gitrepo.LineBlame(ctx, repo, branch, file, line)
if err != nil {
return nil, err
}
if len(sha) < 40 {
return nil, fmt.Errorf("invalid result of blame: %s", sha)
}
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
return gitRepo.GetCommit(sha[:objectFormat.FullLength()])
}
// checkInvalidation checks if the line of code comment got changed by another commit. // checkInvalidation checks if the line of code comment got changed by another commit.
// If the line got changed the comment is going to be invalidated. // If the line got changed the comment is going to be invalidated.
func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *repo_model.Repository, gitRepo *git.Repository, branch string) error { func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *git.Repository, branch string) error {
// FIXME differentiate between previous and proposed line // FIXME differentiate between previous and proposed line
commit, err := lineBlame(ctx, repo, gitRepo, branch, c.TreePath, uint(c.UnsignedLine())) commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
if err != nil && (strings.Contains(err.Error(), "fatal: no such path") || notEnoughLines.MatchString(err.Error())) { if err != nil && (strings.Contains(err.Error(), "fatal: no such path") || notEnoughLines.MatchString(err.Error())) {
c.Invalidated = true c.Invalidated = true
return issues_model.UpdateCommentInvalidate(ctx, c) return issues_model.UpdateCommentInvalidate(ctx, c)
@ -81,7 +67,7 @@ func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *repo_
} }
// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change // InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestList, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branch string) error { func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestList, doer *user_model.User, repo *git.Repository, branch string) error {
if len(prs) == 0 { if len(prs) == 0 {
return nil return nil
} }
@ -97,7 +83,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
return fmt.Errorf("find code comments: %v", err) return fmt.Errorf("find code comments: %v", err)
} }
for _, comment := range codeComments { for _, comment := range codeComments {
if err := checkInvalidation(ctx, comment, repo, gitRepo, branch); err != nil { if err := checkInvalidation(ctx, comment, repo, branch); err != nil {
return err return err
} }
} }
@ -247,7 +233,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo
// FIXME validate treePath // FIXME validate treePath
// Get latest commit referencing the commented line // Get latest commit referencing the commented line
// No need for get commit for base branch changes // No need for get commit for base branch changes
commit, err := lineBlame(ctx, pr.BaseRepo, gitRepo, head, treePath, uint(line)) commit, err := gitRepo.LineBlame(head, gitRepo.Path, treePath, uint(line))
if err == nil { if err == nil {
commitID = commit.ID.String() commitID = commit.ID.String()
} else if !(strings.Contains(err.Error(), "exit status 128 - fatal: no such path") || notEnoughLines.MatchString(err.Error())) { } else if !(strings.Contains(err.Error(), "exit status 128 - fatal: no such path") || notEnoughLines.MatchString(err.Error())) {

View File

@ -14,7 +14,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
@ -34,21 +34,17 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
} }
defer releaser() defer releaser()
diffCount, err := GetDiverging(ctx, pr)
if err != nil {
return err
} else if diffCount.Behind == 0 {
return fmt.Errorf("HeadBranch of PR %d is up to date", pr.Index)
}
if err := pr.LoadBaseRepo(ctx); err != nil { if err := pr.LoadBaseRepo(ctx); err != nil {
log.Error("unable to load BaseRepo for %-v during update-by-merge: %v", pr, err) log.Error("unable to load BaseRepo for %-v during update-by-merge: %v", pr, err)
return fmt.Errorf("unable to load BaseRepo for PR[%d] during update-by-merge: %w", pr.ID, err) return fmt.Errorf("unable to load BaseRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
} }
// TODO: FakePR: if the PR is a fake PR (for example: from Merge Upstream), then no need to check diverging
if pr.ID > 0 {
diffCount, err := gitrepo.GetDivergingCommits(ctx, pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
if err != nil {
return err
} else if diffCount.Behind == 0 {
return fmt.Errorf("HeadBranch of PR %d is up to date", pr.Index)
}
}
if err := pr.LoadHeadRepo(ctx); err != nil { if err := pr.LoadHeadRepo(ctx); err != nil {
log.Error("unable to load HeadRepo for PR %-v during update-by-merge: %v", pr, err) log.Error("unable to load HeadRepo for PR %-v during update-by-merge: %v", pr, err)
return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err) return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
@ -176,13 +172,18 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest,
return mergeAllowed, rebaseAllowed, nil return mergeAllowed, rebaseAllowed, nil
} }
func syncCommitDivergence(ctx context.Context, pr *issues_model.PullRequest) error { // GetDiverging determines how many commits a PR is ahead or behind the PR base branch
if err := pr.LoadBaseRepo(ctx); err != nil { func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.DivergeObject, error) {
return err log.Trace("GetDiverging[%-v]: compare commits", pr)
} prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
divergence, err := gitrepo.GetDivergingCommits(ctx, pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
if err != nil { if err != nil {
return err if !git_model.IsErrBranchNotExist(err) {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
}
return nil, err
} }
return pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind) defer cancel()
diff, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
return &diff, err
} }

View File

@ -33,6 +33,7 @@ import (
actions_service "code.gitea.io/gitea/services/actions" actions_service "code.gitea.io/gitea/services/actions"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
release_service "code.gitea.io/gitea/services/release" release_service "code.gitea.io/gitea/services/release"
files_service "code.gitea.io/gitea/services/repository/files"
"xorm.io/builder" "xorm.io/builder"
) )
@ -122,9 +123,9 @@ func getDivergenceCacheKey(repoID int64, branchName string) string {
} }
// getDivergenceFromCache gets the divergence from cache // getDivergenceFromCache gets the divergence from cache
func getDivergenceFromCache(repoID int64, branchName string) (*gitrepo.DivergeObject, bool) { func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName)) data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
res := gitrepo.DivergeObject{ res := git.DivergeObject{
Ahead: -1, Ahead: -1,
Behind: -1, Behind: -1,
} }
@ -138,7 +139,7 @@ func getDivergenceFromCache(repoID int64, branchName string) (*gitrepo.DivergeOb
return &res, true return &res, true
} }
func putDivergenceFromCache(repoID int64, branchName string, divergence *gitrepo.DivergeObject) error { func putDivergenceFromCache(repoID int64, branchName string, divergence *git.DivergeObject) error {
bs, err := json.Marshal(divergence) bs, err := json.Marshal(divergence)
if err != nil { if err != nil {
return err return err
@ -177,7 +178,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
p := protectedBranches.GetFirstMatched(branchName) p := protectedBranches.GetFirstMatched(branchName)
isProtected := p != nil isProtected := p != nil
var divergence *gitrepo.DivergeObject var divergence *git.DivergeObject
// it's not default branch // it's not default branch
if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted { if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
@ -185,9 +186,9 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
divergence, cached = getDivergenceFromCache(repo.ID, dbBranch.Name) divergence, cached = getDivergenceFromCache(repo.ID, dbBranch.Name)
if !cached { if !cached {
var err error var err error
divergence, err = gitrepo.GetDivergingCommits(ctx, repo, repo.DefaultBranch, git.BranchPrefix+branchName) divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
if err != nil { if err != nil {
log.Error("GetDivergingCommits: %v", err) log.Error("CountDivergingCommits: %v", err)
} else { } else {
if err = putDivergenceFromCache(repo.ID, dbBranch.Name, divergence); err != nil { if err = putDivergenceFromCache(repo.ID, dbBranch.Name, divergence); err != nil {
log.Error("putDivergenceFromCache: %v", err) log.Error("putDivergenceFromCache: %v", err)
@ -198,7 +199,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
if divergence == nil { if divergence == nil {
// tolerate the error that we cannot get divergence // tolerate the error that we cannot get divergence
divergence = &gitrepo.DivergeObject{Ahead: -1, Behind: -1} divergence = &git.DivergeObject{Ahead: -1, Behind: -1}
} }
pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName) pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName)
@ -441,7 +442,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
} }
if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error { if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
err2 := gitrepo.RenameBranch(ctx, repo, from, to) err2 := gitRepo.RenameBranch(from, to)
if err2 != nil { if err2 != nil {
return err2 return err2
} }
@ -552,7 +553,9 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return nil return nil
} }
return gitrepo.DeleteBranch(ctx, repo, branchName, true) return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
Force: true,
})
}); err != nil { }); err != nil {
return err return err
} }
@ -717,7 +720,7 @@ func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repo
// if the fork repo has new commits, this call will fail because they are not in the base repo // if the fork repo has new commits, this call will fail because they are not in the base repo
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb // exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
// so at the moment, we first check the update time, then check whether the fork branch has base's head // so at the moment, we first check the update time, then check whether the fork branch has base's head
diff, err := gitrepo.GetDivergingCommits(ctx, baseRepo, baseGitBranch.CommitID, headGitBranch.CommitID) diff, err := git.GetDivergingCommits(ctx, baseRepo.RepoPath(), baseGitBranch.CommitID, headGitBranch.CommitID)
if err != nil { if err != nil {
info.BaseHasNewCommits = baseGitBranch.UpdatedUnix > headGitBranch.UpdatedUnix info.BaseHasNewCommits = baseGitBranch.UpdatedUnix > headGitBranch.UpdatedUnix
if headRepo.IsFork && info.BaseHasNewCommits { if headRepo.IsFork && info.BaseHasNewCommits {

View File

@ -6,11 +6,21 @@ package files
import ( import (
"context" "context"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
) )
// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
func CountDivergingCommits(ctx context.Context, repo *repo_model.Repository, branch string) (*git.DivergeObject, error) {
divergence, err := git.GetDivergingCommits(ctx, repo.RepoPath(), repo.DefaultBranch, branch)
if err != nil {
return nil, err
}
return &divergence, nil
}
// GetPayloadCommitVerification returns the verification information of a commit // GetPayloadCommitVerification returns the verification information of a commit
func GetPayloadCommitVerification(ctx context.Context, commit *git.Commit) *structs.PayloadCommitVerification { func GetPayloadCommitVerification(ctx context.Context, commit *git.Commit) *structs.PayloadCommitVerification {
verification := &structs.PayloadCommitVerification{} verification := &structs.PayloadCommitVerification{}

View File

@ -27,8 +27,8 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
func cloneWiki(ctx context.Context, repo *repo_model.Repository, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) { func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) {
wikiPath := repo.WikiPath() wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr) wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr)
if wikiRemotePath == "" { if wikiRemotePath == "" {
return "", nil return "", nil
@ -59,7 +59,7 @@ func cloneWiki(ctx context.Context, repo *repo_model.Repository, opts migration.
return "", err return "", err
} }
defaultBranch, err := gitrepo.GetDefaultBranch(ctx, repo.WikiStorageRepo()) defaultBranch, err := git.GetDefaultBranch(ctx, wikiPath)
if err != nil { if err != nil {
cleanIncompleteWikiPath() cleanIncompleteWikiPath()
return "", fmt.Errorf("failed to get wiki repo default branch for %q, err: %w", wikiPath, err) return "", fmt.Errorf("failed to get wiki repo default branch for %q, err: %w", wikiPath, err)
@ -73,7 +73,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
repo *repo_model.Repository, opts migration.MigrateOptions, repo *repo_model.Repository, opts migration.MigrateOptions,
httpTransport *http.Transport, httpTransport *http.Transport,
) (*repo_model.Repository, error) { ) (*repo_model.Repository, error) {
repoPath := repo.RepoPath() repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
if u.IsOrganization() { if u.IsOrganization() {
t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx) t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
@ -108,7 +108,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
} }
if opts.Wiki { if opts.Wiki {
defaultWikiBranch, err := cloneWiki(ctx, repo, opts, migrateTimeout) defaultWikiBranch, err := cloneWiki(ctx, u, opts, migrateTimeout)
if err != nil { if err != nil {
return repo, fmt.Errorf("clone wiki error: %w", err) return repo, fmt.Errorf("clone wiki error: %w", err)
} }
@ -137,7 +137,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
if !repo.IsEmpty { if !repo.IsEmpty {
if len(repo.DefaultBranch) == 0 { if len(repo.DefaultBranch) == 0 {
// Try to get HEAD branch and set it as default branch. // Try to get HEAD branch and set it as default branch.
headBranchName, err := gitrepo.GetDefaultBranch(ctx, repo) headBranchName, err := git.GetDefaultBranch(ctx, repoPath)
if err != nil { if err != nil {
return repo, fmt.Errorf("GetHEADBranch: %w", err) return repo, fmt.Errorf("GetHEADBranch: %w", err)
} }

View File

@ -6,6 +6,7 @@ package wiki
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -21,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
) )
@ -391,7 +393,15 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
return nil return nil
} }
err = gitrepo.RenameBranch(ctx, repo.WikiStorageRepo(), oldDefBranch, newBranch) gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
if errors.Is(err, util.ErrNotExist) {
return nil // no git repo on storage, no need to do anything else
} else if err != nil {
return fmt.Errorf("unable to open repository: %w", err)
}
defer gitRepo.Close()
err = gitRepo.RenameBranch(oldDefBranch, newBranch)
if err != nil { if err != nil {
return fmt.Errorf("unable to rename default branch: %w", err) return fmt.Errorf("unable to rename default branch: %w", err)
} }

View File

@ -168,6 +168,10 @@
{{template "repo/issue/view_content/reference_issue_dialog" .}} {{template "repo/issue/view_content/reference_issue_dialog" .}}
{{template "shared/user/block_user_dialog" .}} {{template "shared/user/block_user_dialog" .}}
<div class="tw-hidden" id="no-content">
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
</div>
<div class="ui g-modal-confirm delete modal"> <div class="ui g-modal-confirm delete modal">
<div class="header"> <div class="header">
{{svg "octicon-trash"}} {{svg "octicon-trash"}}

View File

@ -9109,7 +9109,7 @@
}, },
{ {
"type": "string", "type": "string",
"description": "comma separated list of label names. Fetch only issues that have any of this label names. Non existent labels are discarded.", "description": "comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded",
"name": "labels", "name": "labels",
"in": "query" "in": "query"
}, },

View File

@ -12,7 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -43,7 +43,7 @@ func TestChangeDefaultBranch(t *testing.T) {
session.MakeRequest(t, req, http.StatusNotFound) session.MakeRequest(t, req, http.StatusNotFound)
} }
func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]*gitrepo.DivergeObject) { func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]git.DivergeObject) {
req := NewRequest(t, "GET", branchesURL) req := NewRequest(t, "GET", branchesURL)
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
@ -92,7 +92,7 @@ func TestChangeDefaultBranchDivergence(t *testing.T) {
settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name)
// check branch divergence before switching default branch // check branch divergence before switching default branch
expectedBranchToDivergenceBefore := map[string]*gitrepo.DivergeObject{ expectedBranchToDivergenceBefore := map[string]git.DivergeObject{
"not-signed": { "not-signed": {
Ahead: 0, Ahead: 0,
Behind: 0, Behind: 0,
@ -119,7 +119,7 @@ func TestChangeDefaultBranchDivergence(t *testing.T) {
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
// check branch divergence after switching default branch // check branch divergence after switching default branch
expectedBranchToDivergenceAfter := map[string]*gitrepo.DivergeObject{ expectedBranchToDivergenceAfter := map[string]git.DivergeObject{
"master": { "master": {
Ahead: 1, Ahead: 1,
Behind: 0, Behind: 0,

View File

@ -14,13 +14,11 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/gitrepo"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestAPIPullUpdate(t *testing.T) { func TestAPIPullUpdate(t *testing.T) {
@ -29,16 +27,14 @@ func TestAPIPullUpdate(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
pr := createOutdatedPR(t, user, org26) pr := createOutdatedPR(t, user, org26)
require.NoError(t, pr.LoadBaseRepo(t.Context()))
require.NoError(t, pr.LoadIssue(t.Context()))
// Test GetDiverging // Test GetDiverging
diffCount, err := gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName()) diffCount, err := pull_service.GetDiverging(t.Context(), pr)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, diffCount.Behind) assert.Equal(t, 1, diffCount.Behind)
assert.Equal(t, 1, diffCount.Ahead) assert.Equal(t, 1, diffCount.Ahead)
assert.Equal(t, diffCount.Behind, pr.CommitsBehind) assert.NoError(t, pr.LoadBaseRepo(t.Context()))
assert.Equal(t, diffCount.Ahead, pr.CommitsAhead) assert.NoError(t, pr.LoadIssue(t.Context()))
session := loginUser(t, "user2") session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
@ -47,14 +43,10 @@ func TestAPIPullUpdate(t *testing.T) {
session.MakeRequest(t, req, http.StatusOK) session.MakeRequest(t, req, http.StatusOK)
// Test GetDiverging after update // Test GetDiverging after update
diffCount, err = gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName()) diffCount, err = pull_service.GetDiverging(t.Context(), pr)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, diffCount.Behind) assert.Equal(t, 0, diffCount.Behind)
assert.Equal(t, 2, diffCount.Ahead) assert.Equal(t, 2, diffCount.Ahead)
assert.Eventually(t, func() bool {
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
return diffCount.Behind == pr.CommitsBehind && diffCount.Ahead == pr.CommitsAhead
}, 5*time.Second, 20*time.Millisecond)
}) })
} }
@ -64,13 +56,13 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
pr := createOutdatedPR(t, user, org26) pr := createOutdatedPR(t, user, org26)
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
// Test GetDiverging // Test GetDiverging
diffCount, err := gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName()) diffCount, err := pull_service.GetDiverging(t.Context(), pr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, diffCount.Behind) assert.Equal(t, 1, diffCount.Behind)
assert.Equal(t, 1, diffCount.Ahead) assert.Equal(t, 1, diffCount.Ahead)
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
assert.NoError(t, pr.LoadIssue(t.Context())) assert.NoError(t, pr.LoadIssue(t.Context()))
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -80,7 +72,7 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
session.MakeRequest(t, req, http.StatusOK) session.MakeRequest(t, req, http.StatusOK)
// Test GetDiverging after update // Test GetDiverging after update
diffCount, err = gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName()) diffCount, err = pull_service.GetDiverging(t.Context(), pr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, diffCount.Behind) assert.Equal(t, 0, diffCount.Behind)
assert.Equal(t, 1, diffCount.Ahead) assert.Equal(t, 1, diffCount.Ahead)

View File

@ -13,10 +13,10 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
if (!clickTarget) return; if (!clickTarget) return;
e.preventDefault(); e.preventDefault();
const commentContent = clickTarget.closest('.comment-header').nextElementSibling; const segment = clickTarget.closest('.comment-header').nextElementSibling;
const editContentZone = commentContent.querySelector('.edit-content-zone'); const editContentZone = segment.querySelector('.edit-content-zone');
let renderContent = commentContent.querySelector('.render-content'); const renderContent = segment.querySelector('.render-content');
const rawContent = commentContent.querySelector('.raw-content'); const rawContent = segment.querySelector('.raw-content');
let comboMarkdownEditor : ComboMarkdownEditor; let comboMarkdownEditor : ComboMarkdownEditor;
@ -47,32 +47,30 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
const response = await POST(editContentZone.getAttribute('data-update-url'), {data: params}); const response = await POST(editContentZone.getAttribute('data-update-url'), {data: params});
const data = await response.json(); const data = await response.json();
if (!response.ok) { if (response.status === 400) {
showErrorToast(data?.errorMessage ?? window.config.i18n.error_occurred); showErrorToast(data.errorMessage);
return; return;
} }
reinitializeAreYouSure(editContentZone.querySelector('form')); // the form is no longer dirty reinitializeAreYouSure(editContentZone.querySelector('form')); // the form is no longer dirty
editContentZone.setAttribute('data-content-version', data.contentVersion); editContentZone.setAttribute('data-content-version', data.contentVersion);
if (!data.content) {
// replace the render content with new one, to trigger re-initialization of all features renderContent.innerHTML = document.querySelector('#no-content').innerHTML;
const newRenderContent = renderContent.cloneNode(false) as HTMLElement; rawContent.textContent = '';
newRenderContent.innerHTML = data.content; } else {
renderContent.replaceWith(newRenderContent); renderContent.innerHTML = data.content;
renderContent = newRenderContent; rawContent.textContent = comboMarkdownEditor.value();
const refIssues = renderContent.querySelectorAll<HTMLElement>('p .ref-issue');
rawContent.textContent = comboMarkdownEditor.value(); attachRefIssueContextPopup(refIssues);
const refIssues = renderContent.querySelectorAll<HTMLElement>('p .ref-issue'); }
attachRefIssueContextPopup(refIssues); const content = segment;
if (!content.querySelector('.dropzone-attachments')) {
if (!commentContent.querySelector('.dropzone-attachments')) {
if (data.attachments !== '') { if (data.attachments !== '') {
commentContent.insertAdjacentHTML('beforeend', data.attachments); content.insertAdjacentHTML('beforeend', data.attachments);
} }
} else if (data.attachments === '') { } else if (data.attachments === '') {
commentContent.querySelector('.dropzone-attachments').remove(); content.querySelector('.dropzone-attachments').remove();
} else { } else {
commentContent.querySelector('.dropzone-attachments').outerHTML = data.attachments; content.querySelector('.dropzone-attachments').outerHTML = data.attachments;
} }
comboMarkdownEditor.dropzoneSubmitReload(); comboMarkdownEditor.dropzoneSubmitReload();
} catch (error) { } catch (error) {