gitea source for verification 2026-05-22
Some checks are pending
release-nightly / nightly-binary (push) Waiting to run
release-nightly / nightly-docker-rootful (push) Waiting to run
release-nightly / nightly-docker-rootless (push) Waiting to run

This commit is contained in:
2026-05-22 16:44:59 +08:00
commit 7a61cd3abc
5650 changed files with 690128 additions and 0 deletions

56
modules/gitrepo/branch.go Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// GetBranchesByPath returns a branch by its path
// if limit = 0 it will not limit
func GetBranchesByPath(ctx context.Context, repo Repository, skip, limit int) ([]string, int, error) {
gitRepo, err := OpenRepository(ctx, repo)
if err != nil {
return nil, 0, err
}
defer gitRepo.Close()
return gitRepo.GetBranchNames(skip, limit)
}
func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (string, error) {
gitRepo, err := OpenRepository(ctx, repo)
if err != nil {
return "", err
}
defer gitRepo.Close()
return gitRepo.GetBranchCommitID(branch)
}
// SetDefaultBranch sets default branch of repository.
func SetDefaultBranch(ctx context.Context, repo Repository, name string) error {
_, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD").
AddDynamicArguments(git.BranchPrefix+name).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err
}
// GetDefaultBranch gets default branch of repository.
func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) {
return git.GetDefaultBranch(ctx, repoPath(repo))
}
// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(ctx context.Context, repo Repository, name string) bool {
return git.IsReferenceExist(ctx, repoPath(repo), name)
}
// IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(ctx context.Context, repo Repository, name string) bool {
return IsReferenceExist(ctx, repo, git.BranchPrefix+name)
}

48
modules/gitrepo/config.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/globallock"
)
func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) {
result, _, err := gitcmd.NewCommand("config", "--get").
AddDynamicArguments(key).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
if err != nil {
return "", err
}
return strings.TrimSpace(result), nil
}
func getRepoConfigLockKey(repoStoragePath string) string {
return "repo-config:" + repoStoragePath
}
// 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 {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
_, _, err := gitcmd.NewCommand("config", "--add").
AddDynamicArguments(key, value).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err
})
}
// GitConfigSet updates a git configuration key to a specific value for the given repository.
// If the key does not exist, it will be created.
// If the key exists, it will be updated to the new value.
func GitConfigSet(ctx context.Context, repo Repository, key, value string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
_, _, err := gitcmd.NewCommand("config").
AddDynamicArguments(key, value).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err
})
}

16
modules/gitrepo/fsck.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"time"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// Fsck verifies the connectivity and validity of the objects in the database
func Fsck(ctx context.Context, repo Repository, timeout time.Duration, args gitcmd.TrustedCmdArgs) error {
return gitcmd.NewCommand("fsck").AddArguments(args...).Run(ctx, &gitcmd.RunOpts{Timeout: timeout, Dir: repoPath(repo)})
}

View File

@@ -0,0 +1,88 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"fmt"
"io"
"path/filepath"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// Repository represents a git repository which stored in a disk
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 should be an unix style path like username/reponame.git
// This method should change it according to the current OS.
func repoPath(repo Repository) string {
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repo.RelativePath()))
}
// OpenRepository opens the repository at the given relative path with the provided context.
func OpenRepository(ctx context.Context, repo Repository) (*git.Repository, error) {
return git.OpenRepository(ctx, repoPath(repo))
}
// contextKey is a value for use with context.WithValue.
type contextKey struct {
repoPath string
}
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
// The caller must call "defer gitRepo.Close()"
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
reqCtx := reqctx.FromContext(ctx)
if reqCtx != nil {
gitRepo, err := RepositoryFromRequestContextOrOpen(reqCtx, repo)
return gitRepo, util.NopCloser{}, err
}
gitRepo, err := OpenRepository(ctx, repo)
return gitRepo, gitRepo, err
}
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context.
// Caller shouldn't close the git repo manually, the git repo will be automatically closed when the request context is done.
func RepositoryFromRequestContextOrOpen(ctx reqctx.RequestContext, repo Repository) (*git.Repository, error) {
ck := contextKey{repoPath: repoPath(repo)}
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
return gitRepo, nil
}
gitRepo, err := git.OpenRepository(ctx, ck.repoPath)
if err != nil {
return nil, err
}
ctx.AddCloser(gitRepo)
ctx.SetContextValue(ck, gitRepo)
return gitRepo, nil
}
// IsRepositoryExist returns true if the repository directory exists in the disk
func IsRepositoryExist(ctx context.Context, repo Repository) (bool, error) {
return util.IsExist(repoPath(repo))
}
// DeleteRepository deletes the repository directory from the disk, it will return
// nil if the repository does not exist.
func DeleteRepository(ctx context.Context, repo Repository) error {
return util.RemoveAll(repoPath(repo))
}
// RenameRepository renames a repository's name on disk
func RenameRepository(ctx context.Context, repo, newRepo Repository) error {
if err := util.Rename(repoPath(repo), repoPath(newRepo)); err != nil {
return fmt.Errorf("rename repository directory: %w", err)
}
return nil
}
func InitRepository(ctx context.Context, repo Repository, objectFormatName string) error {
return git.InitRepository(ctx, repoPath(repo), true, objectFormatName)
}

240
modules/gitrepo/hooks.go Normal file
View File

@@ -0,0 +1,240 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) {
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
// for pre-receive
fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done
`, setting.ScriptType),
// for update
fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0/..)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
"${hook}" $1 $2 $3
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done
`, setting.ScriptType),
// for post-receive
fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done
`, setting.ScriptType),
}
giteaHookTpls = []string{
// for pre-receive
fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
%s hook --config=%s pre-receive
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
// for update
fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
%s hook --config=%s update $1 $2 $3
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
// for post-receive
fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
%s hook --config=%s post-receive
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
}
// although only new git (>=2.29) supports proc-receive, it's still good to create its hook, in case the user upgrades git
hookNames = append(hookNames, "proc-receive")
hookTpls = append(hookTpls,
fmt.Sprintf(`#!/usr/bin/env %s
# AUTO GENERATED BY GITEA, DO NOT MODIFY
%s hook --config=%s proc-receive
`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)))
giteaHookTpls = append(giteaHookTpls, "")
return hookNames, hookTpls, giteaHookTpls
}
// CreateDelegateHooks creates all the hooks scripts for the repo
func CreateDelegateHooks(_ context.Context, repo Repository) (err error) {
return createDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
}
func createDelegateHooks(hookDir string) (err error) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil {
return fmt.Errorf("create hooks dir '%s': %w", filepath.Join(hookDir, hookName+".d"), err)
}
// WARNING: This will override all old server-side hooks
if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err)
}
if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil {
return fmt.Errorf("write old hook file '%s': %w", oldHookPath, err)
}
if err = ensureExecutable(oldHookPath); err != nil {
return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
}
if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err)
}
if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil {
return fmt.Errorf("write new hook file '%s': %w", newHookPath, err)
}
if err = ensureExecutable(newHookPath); err != nil {
return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err)
}
}
return nil
}
func checkExecutable(filename string) bool {
// windows has no concept of a executable bit
if runtime.GOOS == "windows" {
return true
}
fileInfo, err := os.Stat(filename)
if err != nil {
return false
}
return (fileInfo.Mode() & 0o100) > 0
}
func ensureExecutable(filename string) error {
fileInfo, err := os.Stat(filename)
if err != nil {
return err
}
if (fileInfo.Mode() & 0o100) > 0 {
return nil
}
mode := fileInfo.Mode() | 0o100
return os.Chmod(filename, mode)
}
// CheckDelegateHooks checks the hooks scripts for the repo
func CheckDelegateHooks(_ context.Context, repo Repository) ([]string, error) {
return checkDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
}
func checkDelegateHooks(hookDir string) ([]string, error) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
results := make([]string, 0, 10)
for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
cont := false
isExist, err := util.IsExist(oldHookPath)
if err != nil {
results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath, err))
}
if err == nil && !isExist {
results = append(results, fmt.Sprintf("old hook file %s does not exist", oldHookPath))
cont = true
}
isExist, err = util.IsExist(oldHookPath + ".d")
if err != nil {
results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath+".d", err))
}
if err == nil && !isExist {
results = append(results, fmt.Sprintf("hooks directory %s does not exist", oldHookPath+".d"))
cont = true
}
isExist, err = util.IsExist(newHookPath)
if err != nil {
results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", newHookPath, err))
}
if err == nil && !isExist {
results = append(results, fmt.Sprintf("new hook file %s does not exist", newHookPath))
cont = true
}
if cont {
continue
}
contents, err := os.ReadFile(oldHookPath)
if err != nil {
return results, err
}
if string(contents) != hookTpls[i] {
results = append(results, fmt.Sprintf("old hook file %s is out of date", oldHookPath))
}
if !checkExecutable(oldHookPath) {
results = append(results, fmt.Sprintf("old hook file %s is not executable", oldHookPath))
}
contents, err = os.ReadFile(newHookPath)
if err != nil {
return results, err
}
if string(contents) != giteaHookTpls[i] {
results = append(results, fmt.Sprintf("new hook file %s is out of date", newHookPath))
}
if !checkExecutable(newHookPath) {
results = append(results, fmt.Sprintf("new hook file %s is not executable", newHookPath))
}
}
return results, nil
}

21
modules/gitrepo/ref.go Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git/gitcmd"
)
func UpdateRef(ctx context.Context, repo Repository, refName, newCommitID string) error {
_, _, err := gitcmd.NewCommand("update-ref").AddDynamicArguments(refName, newCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err
}
func RemoveRef(ctx context.Context, repo Repository, refName string) error {
_, _, err := gitcmd.NewCommand("update-ref", "--no-deref", "-d").
AddDynamicArguments(refName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err
}

86
modules/gitrepo/remote.go Normal file
View File

@@ -0,0 +1,86 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"errors"
"io"
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/util"
)
type RemoteOption string
const (
RemoteOptionMirrorPush RemoteOption = "--mirror=push"
RemoteOptionMirrorFetch RemoteOption = "--mirror=fetch"
)
func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL string, options ...RemoteOption) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
cmd := gitcmd.NewCommand("remote", "add")
if len(options) > 0 {
switch options[0] {
case RemoteOptionMirrorPush:
cmd.AddArguments("--mirror=push")
case RemoteOptionMirrorFetch:
cmd.AddArguments("--mirror=fetch")
default:
return errors.New("unknown remote option: " + string(options[0]))
}
}
_, _, err := cmd.
AddDynamicArguments(remoteName, remoteURL).
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err
})
}
func GitRemoteRemove(ctx context.Context, repo Repository, remoteName string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
cmd := gitcmd.NewCommand("remote", "rm").AddDynamicArguments(remoteName)
_, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err
})
}
// GitRemoteGetURL returns the url of a specific remote of the repository.
func GitRemoteGetURL(ctx context.Context, repo Repository, remoteName string) (*giturl.GitURL, error) {
addr, err := git.GetRemoteAddress(ctx, repoPath(repo), remoteName)
if err != nil {
return nil, err
}
if addr == "" {
return nil, util.NewNotExistErrorf("remote '%s' does not exist", remoteName)
}
return giturl.ParseGitURL(addr)
}
// GitRemotePrune prunes the remote branches that no longer exist in the remote repository.
func GitRemotePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error {
return gitcmd.NewCommand("remote", "prune").AddDynamicArguments(remoteName).
Run(ctx, &gitcmd.RunOpts{
Timeout: timeout,
Dir: repoPath(repo),
Stdout: stdout,
Stderr: stderr,
})
}
// GitRemoteUpdatePrune updates the remote branches and prunes the ones that no longer exist in the remote repository.
func GitRemoteUpdatePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error {
return gitcmd.NewCommand("remote", "update", "--prune").AddDynamicArguments(remoteName).
Run(ctx, &gitcmd.RunOpts{
Timeout: timeout,
Dir: repoPath(repo),
Stdout: stdout,
Stderr: stderr,
})
}

15
modules/gitrepo/tag.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git"
)
// IsTagExist returns true if given tag exists in the repository.
func IsTagExist(ctx context.Context, repo Repository, name string) bool {
return IsReferenceExist(ctx, repo, git.TagPrefix+name)
}

8
modules/gitrepo/url.go Normal file
View File

@@ -0,0 +1,8 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
func RepoGitURL(repo Repository) string {
return repoPath(repo)
}

View File

@@ -0,0 +1,36 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build gogit
package gitrepo
import (
"context"
"github.com/go-git/go-git/v5/plumbing"
)
// WalkReferences walks all the references from the repository
// refname is empty, ObjectTag or ObjectBranch. All other values should be treated as equivalent to empty.
func WalkReferences(ctx context.Context, repo Repository, walkfn func(sha1, refname string) error) (int, error) {
gitRepo, closer, err := RepositoryFromContextOrOpen(ctx, repo)
if err != nil {
return 0, err
}
defer closer.Close()
i := 0
iter, err := gitRepo.GoGitRepo().References()
if err != nil {
return i, err
}
defer iter.Close()
err = iter.ForEach(func(ref *plumbing.Reference) error {
err := walkfn(ref.Hash().String(), string(ref.Name()))
i++
return err
})
return i, err
}

View File

@@ -0,0 +1,17 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !gogit
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git"
)
// WalkReferences walks all the references from the repository
func WalkReferences(ctx context.Context, repo Repository, walkfn func(sha1, refname string) error) (int, error) {
return git.WalkShowRef(ctx, repoPath(repo), nil, 0, 0, walkfn)
}