0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-14 00:01:05 +02:00

Merge branch 'main' into lunny/refactor_review_request

This commit is contained in:
Lunny Xiao 2025-09-17 18:02:18 -07:00 committed by GitHub
commit a7016ce8d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
234 changed files with 3252 additions and 2512 deletions

File diff suppressed because it is too large Load Diff

View File

@ -162,7 +162,7 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST)
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.ts *.cjs tests/e2e ESLINT_FILES := web_src/js tools *.ts tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*)) SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
@ -346,12 +346,12 @@ lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backen
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules ## lint js files lint-js: node_modules ## lint js files
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) $(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES)
$(NODE_VARS) pnpm exec vue-tsc $(NODE_VARS) pnpm exec vue-tsc
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules ## lint js files and fix issues lint-js-fix: node_modules ## lint js files and fix issues
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix $(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) --fix
$(NODE_VARS) pnpm exec vue-tsc $(NODE_VARS) pnpm exec vue-tsc
.PHONY: lint-css .PHONY: lint-css

View File

@ -579,11 +579,6 @@
"path": "github.com/go-webauthn/x/revoke/LICENSE", "path": "github.com/go-webauthn/x/revoke/LICENSE",
"licenseText": "Copyright (c) 2014 CloudFlare Inc.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\nRedistributions of source code must retain the above copyright notice,\nthis list of conditions and the following disclaimer.\n\nRedistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation\nand/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" "licenseText": "Copyright (c) 2014 CloudFlare Inc.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\nRedistributions of source code must retain the above copyright notice,\nthis list of conditions and the following disclaimer.\n\nRedistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation\nand/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
}, },
{
"name": "github.com/gobwas/glob",
"path": "github.com/gobwas/glob/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Sergey Kamardin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
},
{ {
"name": "github.com/goccy/go-json", "name": "github.com/goccy/go-json",
"path": "github.com/goccy/go-json/LICENSE", "path": "github.com/goccy/go-json/LICENSE",

View File

@ -265,6 +265,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
excludes = append(excludes, setting.LFS.Storage.Path) excludes = append(excludes, setting.LFS.Storage.Path)
excludes = append(excludes, setting.Attachment.Storage.Path) excludes = append(excludes, setting.Attachment.Storage.Path)
excludes = append(excludes, setting.Packages.Storage.Path) excludes = append(excludes, setting.Packages.Storage.Path)
excludes = append(excludes, setting.RepoArchive.Storage.Path)
excludes = append(excludes, setting.Log.RootPath) excludes = append(excludes, setting.Log.RootPath)
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil { if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
fatal("Failed to include data directory: %v", err) fatal("Failed to include data directory: %v", err)

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/assetfs" "code.gitea.io/gitea/modules/assetfs"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/public"
@ -19,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )

View File

@ -15,6 +15,7 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
@ -312,7 +313,7 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what // First of all run update-server-info no matter what
if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil {
return fmt.Errorf("failed to call 'git update-server-info': %w", err) return fmt.Errorf("failed to call 'git update-server-info': %w", err)
} }

View File

@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer" "code.gitea.io/gitea/modules/lfstransfer"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -312,30 +313,30 @@ func runServ(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
var gitcmd *exec.Cmd var command *exec.Cmd
gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin gitBinPath := filepath.Dir(gitcmd.GitExecutable) // e.g. /usr/bin
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
if _, err := os.Stat(gitBinVerb); err != nil { if _, err := os.Stat(gitBinVerb); err != nil {
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git // if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way // ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
verbFields := strings.SplitN(verb, "-", 2) verbFields := strings.SplitN(verb, "-", 2)
if len(verbFields) == 2 { if len(verbFields) == 2 {
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ... // use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath) command = exec.CommandContext(ctx, gitcmd.GitExecutable, verbFields[1], repoPath)
} }
} }
if gitcmd == nil { if command == nil {
// by default, use the verb (it has been checked above by allowedCommands) // by default, use the verb (it has been checked above by allowedCommands)
gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath) command = exec.CommandContext(ctx, gitBinVerb, repoPath)
} }
process.SetSysProcAttribute(gitcmd) process.SetSysProcAttribute(command)
gitcmd.Dir = setting.RepoRootPath command.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout command.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin command.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr command.Stderr = os.Stderr
gitcmd.Env = append(gitcmd.Env, os.Environ()...) command.Env = append(command.Env, os.Environ()...)
gitcmd.Env = append(gitcmd.Env, command.Env = append(command.Env,
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki), repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
repo_module.EnvRepoName+"="+results.RepoName, repo_module.EnvRepoName+"="+results.RepoName,
repo_module.EnvRepoUsername+"="+results.OwnerName, repo_module.EnvRepoUsername+"="+results.OwnerName,
@ -350,9 +351,9 @@ func runServ(ctx context.Context, c *cli.Command) error {
) )
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command. // to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later. // it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...) command.Env = append(command.Env, gitcmd.CommonCmdServEnvs()...)
if err = gitcmd.Run(); err != nil { if err = command.Run(); err != nil {
return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err) return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err)
} }

View File

@ -11,7 +11,7 @@ The default version will read from `docs/config.yml`. You can override this
using the option `--version`. using the option `--version`.
The upstream branches will be fetched, using the remote `origin`. This can The upstream branches will be fetched, using the remote `origin`. This can
be overrided using `--upstream`, and fetching can be avoided using be overridden using `--upstream`, and fetching can be avoided using
`--no-fetch`. `--no-fetch`.
By default the branch created will be called `backport-$PR-$VERSION`. You By default the branch created will be called `backport-$PR-$VERSION`. You

View File

@ -150,7 +150,7 @@
<p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p> <p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p>
<p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the bassis of consent within 30 days.</p> <p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the basis of consent within 30 days.</p>
<p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p> <p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p>

1029
eslint.config.ts Normal file

File diff suppressed because it is too large Load Diff

1
go.mod
View File

@ -61,7 +61,6 @@ require (
github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.9.3 github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.13.4 github.com/go-webauthn/webauthn v0.13.4
github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.0

2
go.sum
View File

@ -364,8 +364,6 @@ github.com/go-webauthn/webauthn v0.13.4 h1:q68qusWPcqHbg9STSxBLBHnsKaLxNO0RnVKaA
github.com/go-webauthn/webauthn v0.13.4/go.mod h1:MglN6OH9ECxvhDqoq1wMoF6P6JRYDiQpC9nc5OomQmI= github.com/go-webauthn/webauthn v0.13.4/go.mod h1:MglN6OH9ECxvhDqoq1wMoF6P6JRYDiQpC9nc5OomQmI=
github.com/go-webauthn/x v0.1.24 h1:6LaWf2zzWqbyKT8IyQkhje1/1KCGhlEkMz4V1tDnt/A= github.com/go-webauthn/x v0.1.24 h1:6LaWf2zzWqbyKT8IyQkhje1/1KCGhlEkMz4V1tDnt/A=
github.com/go-webauthn/x v0.1.24/go.mod h1:2o5XKJ+X1AKqYKGgHdKflGnoQFQZ6flJ2IFCBKSbSOw= github.com/go-webauthn/x v0.1.24/go.mod h1:2o5XKJ+X1AKqYKGgHdKflGnoQFQZ6flJ2IFCBKSbSOw=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=

View File

@ -25,7 +25,7 @@ type CommitVerification struct {
SigningUser *user_model.User // if Verified, then SigningUser is non-nil SigningUser *user_model.User // if Verified, then SigningUser is non-nil
CommittingUser *user_model.User // if Verified, then CommittingUser is non-nil CommittingUser *user_model.User // if Verified, then CommittingUser is non-nil
SigningEmail string SigningEmail string
SigningKey *GPGKey SigningKey *GPGKey // FIXME: need to refactor it to a new name like "SigningGPGKey", it is also used in some templates
SigningSSHKey *PublicKey SigningSSHKey *PublicKey
TrustStatus string TrustStatus string
} }

View File

@ -0,0 +1,37 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package asymkey
import (
"os"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
func GetDisplaySigningKey(key *git.SigningKey) string {
if key == nil || key.Format == "" {
return ""
}
switch key.Format {
case git.SigningKeyFormatOpenPGP:
return key.KeyID
case git.SigningKeyFormatSSH:
content, err := os.ReadFile(key.KeyID)
if err != nil {
log.Error("Unable to read SSH key %s: %v", key.KeyID, err)
return "(Unable to read SSH key)"
}
display, err := CalcFingerprint(string(content))
if err != nil {
log.Error("Unable to calculate fingerprint for SSH key %s: %v", key.KeyID, err)
return "(Unable to calculate fingerprint for SSH key)"
}
return display
}
setting.PanicInDevOrTesting("Unknown signing key format: %s", key.Format)
return "(Unknown key format)"
}

View File

@ -67,7 +67,7 @@ func (l *XORMLogBridge) Warn(v ...any) {
l.Log(stackLevel, log.WARN, "%s", fmt.Sprint(v...)) l.Log(stackLevel, log.WARN, "%s", fmt.Sprint(v...))
} }
// Warnf show warnning log // Warnf show warning log
func (l *XORMLogBridge) Warnf(format string, v ...any) { func (l *XORMLogBridge) Warnf(format string, v ...any) {
l.Log(stackLevel, log.WARN, format, v...) l.Log(stackLevel, log.WARN, format, v...)
} }

View File

@ -17,12 +17,11 @@ 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/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
"github.com/gobwas/glob/syntax"
"xorm.io/builder" "xorm.io/builder"
) )
@ -77,7 +76,7 @@ func init() {
// IsRuleNameSpecial return true if it contains special character // IsRuleNameSpecial return true if it contains special character
func IsRuleNameSpecial(ruleName string) bool { func IsRuleNameSpecial(ruleName string) bool {
for i := 0; i < len(ruleName); i++ { for i := 0; i < len(ruleName); i++ {
if syntax.Special(ruleName[i]) { if glob.IsSpecialByte(ruleName[i]) {
return true return true
} }
} }

View File

@ -8,9 +8,8 @@ import (
"sort" "sort"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"github.com/gobwas/glob"
) )
type ProtectedBranchRules []*ProtectedBranch type ProtectedBranchRules []*ProtectedBranch

View File

@ -11,9 +11,8 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"github.com/gobwas/glob"
) )
// ProtectedTag struct // ProtectedTag struct

View File

@ -408,7 +408,7 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
official, err := isOfficialReviewer(sess, review.IssueID, reviewer) official, err := isOfficialReviewer(sess, review.IssueID, reviewer)
if err != nil { if err != nil {
// Branch might not be proteced or other error, ignore it. // Branch might not be protected or other error, ignore it.
continue continue
} }
review.Official = official review.Official = official

View File

@ -12,6 +12,7 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -83,17 +84,17 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
if !pr.HasMerged { if !pr.HasMerged {
var err error var err error
pr.MergeBase, _, err = git.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
var err2 error var err2 error
pr.MergeBase, _, err2 = git.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err2 != nil { if err2 != nil {
log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2) log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2)
continue continue
} }
} }
} else { } else {
parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue
@ -105,9 +106,9 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
refs := append([]string{}, parents[1:]...) refs := append([]string{}, parents[1:]...)
refs = append(refs, gitRefName) refs = append(refs, gitRefName)
cmd := git.NewCommand("merge-base").AddDashesAndList(refs...) cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
pr.MergeBase, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue

View File

@ -11,7 +11,7 @@ import (
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -80,7 +80,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index) gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue
@ -93,9 +93,9 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
// we should recalculate // we should recalculate
refs := append([]string{}, parents[1:]...) refs := append([]string{}, parents[1:]...)
refs = append(refs, gitRefName) refs = append(refs, gitRefName)
cmd := git.NewCommand("merge-base").AddDashesAndList(refs...) cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
pr.MergeBase, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath}) pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue

View File

@ -348,10 +348,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
for _, u := range repo.Units { for _, u := range repo.Units {
for _, team := range teams { for _, team := range teams {
unitAccessMode := minAccessMode teamMode, _ := team.UnitAccessModeEx(ctx, u.Type)
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist { unitAccessMode := max(perm.unitsMode[u.Type], minAccessMode, teamMode)
unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode)
}
perm.unitsMode[u.Type] = unitAccessMode perm.unitsMode[u.Type] = unitAccessMode
} }
} }

View File

@ -197,4 +197,37 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode]) assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues]) assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
}) })
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // org private repo, same org as repo 32
require.NoError(t, repo3.LoadOwner(ctx))
require.True(t, repo3.Owner.IsOrganization())
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}, &Access{})) // The user has access set of that repo, remove it, it is useless for our test
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo3.ID}))
t.Run("DoerWithNoopTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeIssues])
})
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeIssues, AccessMode: perm_model.AccessModeRead}))
t.Run("DoerWithReadIssueTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
})
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
require.NoError(t, db.Insert(ctx, Access{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
t.Run("DoerWithReadIssueTeamAndWriteCollaboratorOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
})
} }

View File

@ -8,7 +8,7 @@ const (
SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types" SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
// SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff // SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff
SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour" SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour"
// SettingsKeyShowOutdatedComments is the setting key wether or not to show outdated comments in PRs // SettingsKeyShowOutdatedComments is the setting key whether or not to show outdated comments in PRs
SettingsKeyShowOutdatedComments = "comment_code.show_outdated" SettingsKeyShowOutdatedComments = "comment_code.show_outdated"
// UserActivityPubPrivPem is user's private key // UserActivityPubPrivPem is user's private key

View File

@ -10,11 +10,11 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/gobwas/glob"
"github.com/nektos/act/pkg/jobparser" "github.com/nektos/act/pkg/jobparser"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/workflowpattern" "github.com/nektos/act/pkg/workflowpattern"

View File

@ -70,7 +70,7 @@ func (i *Identicon) render(c, b1, b2, b1Angle, b2Angle, foreColor int) image.Ima
/* /*
# Algorithm # Algorithm
Origin: An image is splitted into 9 areas Origin: An image is split into 9 areas
``` ```
------------- -------------

View File

@ -12,6 +12,7 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -23,7 +24,7 @@ type BatchChecker struct {
stdOut *nulSeparatedAttributeWriter stdOut *nulSeparatedAttributeWriter
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
cmd *git.Command cmd *gitcmd.Command
} }
// NewBatchChecker creates a check attribute reader for the current repository and provided commit ID // NewBatchChecker creates a check attribute reader for the current repository and provided commit ID
@ -76,7 +77,7 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes []string)
_ = lw.Close() _ = lw.Close()
}() }()
stdErr := new(bytes.Buffer) stdErr := new(bytes.Buffer)
err := cmd.Run(ctx, &git.RunOpts{ err := cmd.Run(ctx, &gitcmd.RunOpts{
Env: envs, Env: envs,
Dir: repo.Path, Dir: repo.Path,
Stdin: stdinReader, Stdin: stdinReader,

View File

@ -11,12 +11,13 @@ import (
"os" "os"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*git.Command, []string, func(), error) { func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*gitcmd.Command, []string, func(), error) {
cancel := func() {} cancel := func() {}
envs := []string{"GIT_FLUSH=1"} envs := []string{"GIT_FLUSH=1"}
cmd := git.NewCommand("check-attr", "-z") cmd := gitcmd.NewCommand("check-attr", "-z")
if len(attributes) == 0 { if len(attributes) == 0 {
cmd.AddArguments("--all") cmd.AddArguments("--all")
} }
@ -70,7 +71,7 @@ func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish strin
stdOut := new(bytes.Buffer) stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer) stdErr := new(bytes.Buffer)
if err := cmd.Run(ctx, &git.RunOpts{ if err := cmd.Run(ctx, &gitcmd.RunOpts{
Env: append(os.Environ(), envs...), Env: append(os.Environ(), envs...),
Dir: gitRepo.Path, Dir: gitRepo.Path,
Stdout: stdOut, Stdout: stdOut,

View File

@ -12,6 +12,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/djherbis/buffer" "github.com/djherbis/buffer"
@ -29,13 +30,13 @@ type WriteCloserError interface {
// This is needed otherwise the git cat-file will hang for invalid repositories. // This is needed otherwise the git cat-file will hang for invalid repositories.
func ensureValidGitRepository(ctx context.Context, repoPath string) error { func ensureValidGitRepository(ctx context.Context, repoPath string) error {
stderr := strings.Builder{} stderr := strings.Builder{}
err := NewCommand("rev-parse"). err := gitcmd.NewCommand("rev-parse").
Run(ctx, &RunOpts{ Run(ctx, &gitcmd.RunOpts{
Dir: repoPath, Dir: repoPath,
Stderr: &stderr, Stderr: &stderr,
}) })
if err != nil { if err != nil {
return ConcatenateError(err, (&stderr).String()) return gitcmd.ConcatenateError(err, (&stderr).String())
} }
return nil return nil
} }
@ -61,8 +62,8 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
go func() { go func() {
stderr := strings.Builder{} stderr := strings.Builder{}
err := NewCommand("cat-file", "--batch-check"). err := gitcmd.NewCommand("cat-file", "--batch-check").
Run(ctx, &RunOpts{ Run(ctx, &gitcmd.RunOpts{
Dir: repoPath, Dir: repoPath,
Stdin: batchStdinReader, Stdin: batchStdinReader,
Stdout: batchStdoutWriter, Stdout: batchStdoutWriter,
@ -71,8 +72,8 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
UseContextTimeout: true, UseContextTimeout: true,
}) })
if err != nil { if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
_ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else { } else {
_ = batchStdoutWriter.Close() _ = batchStdoutWriter.Close()
_ = batchStdinReader.Close() _ = batchStdinReader.Close()
@ -109,8 +110,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
go func() { go func() {
stderr := strings.Builder{} stderr := strings.Builder{}
err := NewCommand("cat-file", "--batch"). err := gitcmd.NewCommand("cat-file", "--batch").
Run(ctx, &RunOpts{ Run(ctx, &gitcmd.RunOpts{
Dir: repoPath, Dir: repoPath,
Stdin: batchStdinReader, Stdin: batchStdinReader,
Stdout: batchStdoutWriter, Stdout: batchStdoutWriter,
@ -119,8 +120,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
UseContextTimeout: true, UseContextTimeout: true,
}) })
if err != nil { if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
_ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else { } else {
_ = batchStdoutWriter.Close() _ = batchStdoutWriter.Close()
_ = batchStdinReader.Close() _ = batchStdinReader.Close()

View File

@ -10,6 +10,7 @@ import (
"io" "io"
"os" "os"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -141,7 +142,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
} }
}() }()
cmd := NewCommand("blame", "--porcelain") cmd := gitcmd.NewCommand("blame", "--porcelain")
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore { if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit) ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit)
@ -165,7 +166,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
go func() { go func() {
stderr := bytes.Buffer{} stderr := bytes.Buffer{}
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close" // TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
err := cmd.Run(ctx, &RunOpts{ err := cmd.Run(ctx, &gitcmd.RunOpts{
UseContextTimeout: true, UseContextTimeout: true,
Dir: repoPath, Dir: repoPath,
Stdout: stdout, Stdout: stdout,

View File

@ -14,6 +14,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -87,12 +88,12 @@ func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
// AddChanges marks local changes to be ready for commit. // AddChanges marks local changes to be ready for commit.
func AddChanges(ctx context.Context, repoPath string, all bool, files ...string) error { func AddChanges(ctx context.Context, repoPath string, all bool, files ...string) error {
cmd := NewCommand().AddArguments("add") cmd := gitcmd.NewCommand().AddArguments("add")
if all { if all {
cmd.AddArguments("--all") cmd.AddArguments("--all")
} }
cmd.AddDashesAndList(files...) cmd.AddDashesAndList(files...)
_, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) _, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
return err return err
} }
@ -106,7 +107,7 @@ type CommitChangesOptions struct {
// CommitChanges commits local changes with given committer, author and message. // CommitChanges commits local changes with given committer, author and message.
// If author is nil, it will be the same as committer. // If author is nil, it will be the same as committer.
func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptions) error { func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptions) error {
cmd := NewCommand() cmd := gitcmd.NewCommand()
if opts.Committer != nil { if opts.Committer != nil {
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name) cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name)
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email) cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email)
@ -121,7 +122,7 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
} }
cmd.AddOptionFormat("--message=%s", opts.Message) cmd.AddOptionFormat("--message=%s", opts.Message)
_, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) _, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
// No stderr but exit status 1 means nothing to commit. // No stderr but exit status 1 means nothing to commit.
if err != nil && err.Error() == "exit status 1" { if err != nil && err.Error() == "exit status 1" {
return nil return nil
@ -131,7 +132,7 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
// AllCommitsCount returns count of all commits in repository // AllCommitsCount returns count of all commits in repository
func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) { func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) {
cmd := NewCommand("rev-list") cmd := gitcmd.NewCommand("rev-list")
if hidePRRefs { if hidePRRefs {
cmd.AddArguments("--exclude=" + PullPrefix + "*") cmd.AddArguments("--exclude=" + PullPrefix + "*")
} }
@ -140,7 +141,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file
cmd.AddDashesAndList(files...) cmd.AddDashesAndList(files...)
} }
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -160,7 +161,7 @@ type CommitsCountOptions struct {
// CommitsCount returns number of total commits of until given revision. // CommitsCount returns number of total commits of until given revision.
func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) { func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) {
cmd := NewCommand("rev-list", "--count") cmd := gitcmd.NewCommand("rev-list", "--count")
cmd.AddDynamicArguments(opts.Revision...) cmd.AddDynamicArguments(opts.Revision...)
@ -172,7 +173,7 @@ func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error)
cmd.AddDashesAndList(opts.RelPath...) cmd.AddDashesAndList(opts.RelPath...)
} }
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: opts.RepoPath}) stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: opts.RepoPath})
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -207,7 +208,7 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
return false, nil return false, nil
} }
_, _, err := NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path}) _, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path})
if err == nil { if err == nil {
return true, nil return true, nil
} }
@ -348,12 +349,12 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
func (c *Commit) GetBranchName() (string, error) { func (c *Commit) GetBranchName() (string, error) {
cmd := NewCommand("name-rev") cmd := gitcmd.NewCommand("name-rev")
if DefaultFeatures().CheckVersionAtLeast("2.13.0") { if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
cmd.AddArguments("--exclude", "refs/tags/*") cmd.AddArguments("--exclude", "refs/tags/*")
} }
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
data, _, err := cmd.RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path}) data, _, err := cmd.RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path})
if err != nil { if err != nil {
// handle special case where git can not describe commit // handle special case where git can not describe commit
if strings.Contains(err.Error(), "cannot describe") { if strings.Contains(err.Error(), "cannot describe") {
@ -431,14 +432,14 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
}() }()
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &RunOpts{ err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &gitcmd.RunOpts{
Dir: repoPath, Dir: repoPath,
Stdout: w, Stdout: w,
Stderr: stderr, Stderr: stderr,
}) })
w.Close() // Close writer to exit parsing goroutine w.Close() // Close writer to exit parsing goroutine
if err != nil { if err != nil {
return nil, ConcatenateError(err, stderr.String()) return nil, gitcmd.ConcatenateError(err, stderr.String())
} }
<-done <-done
@ -447,7 +448,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository. // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) { func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
commitID, _, err := NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &RunOpts{Dir: repoPath}) commitID, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
if strings.Contains(err.Error(), "exit status 128") { if strings.Contains(err.Error(), "exit status 128") {
return "", ErrNotExist{shortID, ""} return "", ErrNotExist{shortID, ""}

View File

@ -11,13 +11,14 @@ import (
"runtime" "runtime"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem) // syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
func syncGitConfig(ctx context.Context) (err error) { func syncGitConfig(ctx context.Context) (err error) {
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil { if err = os.MkdirAll(gitcmd.HomeDir(), os.ModePerm); err != nil {
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err) return fmt.Errorf("unable to prepare git home directory %s, err: %w", gitcmd.HomeDir(), err)
} }
// first, write user's git config options to git config file // first, write user's git config options to git config file
@ -117,8 +118,8 @@ func syncGitConfig(ctx context.Context) (err error) {
} }
func configSet(ctx context.Context, key, value string) error { func configSet(ctx context.Context, key, value string) error {
stdout, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil) stdout, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
if err != nil && !IsErrorExitCode(err, 1) { if err != nil && !gitcmd.IsErrorExitCode(err, 1) {
return fmt.Errorf("failed to get git config %s, err: %w", key, err) return fmt.Errorf("failed to get git config %s, err: %w", key, err)
} }
@ -127,7 +128,7 @@ func configSet(ctx context.Context, key, value string) error {
return nil return nil
} }
_, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil) _, _, err = gitcmd.NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to set git global config %s, err: %w", key, err) return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
} }
@ -136,14 +137,14 @@ func configSet(ctx context.Context, key, value string) error {
} }
func configSetNonExist(ctx context.Context, key, value string) error { func configSetNonExist(ctx context.Context, key, value string) error {
_, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil) _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
if err == nil { if err == nil {
// already exist // already exist
return nil return nil
} }
if IsErrorExitCode(err, 1) { if gitcmd.IsErrorExitCode(err, 1) {
// not exist, set new config // not exist, set new config
_, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil) _, _, err = gitcmd.NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to set git global config %s, err: %w", key, err) return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
} }
@ -154,14 +155,14 @@ func configSetNonExist(ctx context.Context, key, value string) error {
} }
func configAddNonExist(ctx context.Context, key, value string) error { func configAddNonExist(ctx context.Context, key, value string) error {
_, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil) _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil)
if err == nil { if err == nil {
// already exist // already exist
return nil return nil
} }
if IsErrorExitCode(err, 1) { if gitcmd.IsErrorExitCode(err, 1) {
// not exist, add new config // not exist, add new config
_, _, err = NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(ctx, nil) _, _, err = gitcmd.NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(ctx, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to add git global config %s, err: %w", key, err) return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
} }
@ -171,16 +172,16 @@ func configAddNonExist(ctx context.Context, key, value string) error {
} }
func configUnsetAll(ctx context.Context, key, value string) error { func configUnsetAll(ctx context.Context, key, value string) error {
_, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil) _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
if err == nil { if err == nil {
// exist, need to remove // exist, need to remove
_, _, err = NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil) _, _, err = gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err) return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
} }
return nil return nil
} }
if IsErrorExitCode(err, 1) { if gitcmd.IsErrorExitCode(err, 1) {
// not exist // not exist
return nil return nil
} }

View File

@ -8,13 +8,14 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func gitConfigContains(sub string) bool { func gitConfigContains(sub string) bool {
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { if b, err := os.ReadFile(gitcmd.HomeDir() + "/.gitconfig"); err == nil {
return strings.Contains(string(b), sub) return strings.Contains(string(b), sub)
} }
return false return false

View File

@ -14,6 +14,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -34,8 +35,8 @@ func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer
// GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer. // GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer.
func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error { func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error {
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
cmd := NewCommand("show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID) cmd := gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID)
if err := cmd.Run(ctx, &RunOpts{ if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repoPath, Dir: repoPath,
Stdout: writer, Stdout: writer,
Stderr: stderr, Stderr: stderr,
@ -56,7 +57,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
files = append(files, file) files = append(files, file)
} }
cmd := NewCommand() cmd := gitcmd.NewCommand()
switch diffType { switch diffType {
case RawDiffNormal: case RawDiffNormal:
if len(startCommit) != 0 { if len(startCommit) != 0 {
@ -89,7 +90,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
} }
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
if err = cmd.Run(repo.Ctx, &RunOpts{ if err = cmd.Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: writer, Stdout: writer,
Stderr: stderr, Stderr: stderr,
@ -312,8 +313,8 @@ func GetAffectedFiles(repo *Repository, branchName, oldCommitID, newCommitID str
affectedFiles := make([]string, 0, 32) affectedFiles := make([]string, 0, 32)
// Run `git diff --name-only` to get the names of the changed files // Run `git diff --name-only` to get the names of the changed files
err = NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID). err = gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID).
Run(repo.Ctx, &RunOpts{ Run(repo.Ctx, &gitcmd.RunOpts{
Env: env, Env: env,
Dir: repo.Path, Dir: repo.Path,
Stdout: stdoutWriter, Stdout: stdoutWriter,

View File

@ -9,12 +9,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -33,10 +33,7 @@ type Features struct {
SupportCheckAttrOnBare bool // >= 2.40 SupportCheckAttrOnBare bool // >= 2.40
} }
var ( var defaultFeatures *Features
GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
defaultFeatures *Features
)
func (f *Features) CheckVersionAtLeast(atLeast string) bool { func (f *Features) CheckVersionAtLeast(atLeast string) bool {
return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0 return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
@ -60,7 +57,7 @@ func DefaultFeatures() *Features {
} }
func loadGitVersionFeatures() (*Features, error) { func loadGitVersionFeatures() (*Features, error) {
stdout, _, runErr := NewCommand("version").RunStdString(context.Background(), nil) stdout, _, runErr := gitcmd.NewCommand("version").RunStdString(context.Background(), nil)
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
@ -129,32 +126,6 @@ func ensureGitVersion() error {
return nil return nil
} }
// SetExecutablePath changes the path of git executable and checks the file permission and version.
func SetExecutablePath(path string) error {
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
if path != "" {
GitExecutable = path
}
absPath, err := exec.LookPath(GitExecutable)
if err != nil {
return fmt.Errorf("git not found: %w", err)
}
GitExecutable = absPath
return nil
}
// HomeDir is the home dir for git to store the global config file used by Gitea internally
func HomeDir() string {
if setting.Git.HomePath == "" {
// strict check, make sure the git module is initialized correctly.
// attention: when the git module is called in gitea sub-command (serv/hook), the log module might not obviously show messages to users/developers.
// for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
return ""
}
return setting.Git.HomePath
}
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments. // InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands. // This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
func InitSimple() error { func InitSimple() error {
@ -167,10 +138,10 @@ func InitSimple() error {
} }
if setting.Git.Timeout.Default > 0 { if setting.Git.Timeout.Default > 0 {
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second gitcmd.SetDefaultCommandExecutionTimeout(time.Duration(setting.Git.Timeout.Default) * time.Second)
} }
if err := SetExecutablePath(setting.Git.Path); err != nil { if err := gitcmd.SetExecutablePath(setting.Git.Path); err != nil {
return err return err
} }
@ -185,7 +156,7 @@ func InitSimple() error {
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands // when git works with gnupg (commit signing), there should be a stable home for gnupg commands
if _, ok := os.LookupEnv("GNUPGHOME"); !ok { if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) _ = os.Setenv("GNUPGHOME", filepath.Join(gitcmd.HomeDir(), ".gnupg"))
} }
return nil return nil
} }

View File

@ -2,7 +2,7 @@
// Copyright 2016 The Gitea Authors. All rights reserved. // Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package git package gitcmd
import ( import (
"bytes" "bytes"
@ -32,6 +32,10 @@ type TrustedCmdArgs []internal.CmdArg
// defaultCommandExecutionTimeout default command execution timeout duration // defaultCommandExecutionTimeout default command execution timeout duration
var defaultCommandExecutionTimeout = 360 * time.Second var defaultCommandExecutionTimeout = 360 * time.Second
func SetDefaultCommandExecutionTimeout(timeout time.Duration) {
defaultCommandExecutionTimeout = timeout
}
// DefaultLocale is the default LC_ALL to run git commands in. // DefaultLocale is the default LC_ALL to run git commands in.
const DefaultLocale = "C" const DefaultLocale = "C"

View File

@ -3,7 +3,7 @@
//go:build race //go:build race
package git package gitcmd
import ( import (
"context" "context"

View File

@ -1,14 +1,30 @@
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package git package gitcmd
import ( import (
"fmt"
"os"
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/tempdir"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestMain(m *testing.M) {
gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "unable to create temp dir: %v", err)
os.Exit(1)
}
defer cleanup()
setting.Git.HomePath = gitHomePath
}
func TestRunWithContextStd(t *testing.T) { func TestRunWithContextStd(t *testing.T) {
cmd := NewCommand("--version") cmd := NewCommand("--version")
stdout, stderr, err := cmd.RunStdString(t.Context(), &RunOpts{}) stdout, stderr, err := cmd.RunStdString(t.Context(), &RunOpts{})

40
modules/git/gitcmd/env.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitcmd
import (
"fmt"
"os/exec"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
var GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
// SetExecutablePath changes the path of git executable and checks the file permission and version.
func SetExecutablePath(path string) error {
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
if path != "" {
GitExecutable = path
}
absPath, err := exec.LookPath(GitExecutable)
if err != nil {
return fmt.Errorf("git not found: %w", err)
}
GitExecutable = absPath
return nil
}
// HomeDir is the home dir for git to store the global config file used by Gitea internally
func HomeDir() string {
if setting.Git.HomePath == "" {
// strict check, make sure the git module is initialized correctly.
// attention: when the git module is called in gitea sub-command (serv/hook), the log module might not obviously show messages to users/developers.
// for example: if there is gitea git hook code calling NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
return ""
}
return setting.Git.HomePath
}

View File

@ -0,0 +1,14 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitcmd
import "fmt"
// ConcatenateError concatenats an error with stderr string
func ConcatenateError(err error, stderr string) error {
if len(stderr) == 0 {
return err
}
return fmt.Errorf("%w - %s", err, stderr)
}

View File

@ -14,6 +14,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -60,7 +61,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
2^@repo: go-gitea/gitea 2^@repo: go-gitea/gitea
*/ */
var results []*GrepResult var results []*GrepResult
cmd := NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name") cmd := gitcmd.NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name")
cmd.AddOptionValues("--context", strconv.Itoa(opts.ContextLineNumber)) cmd.AddOptionValues("--context", strconv.Itoa(opts.ContextLineNumber))
switch opts.GrepMode { switch opts.GrepMode {
case GrepModeExact: case GrepModeExact:
@ -83,7 +84,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
cmd.AddDashesAndList(opts.PathspecList...) cmd.AddDashesAndList(opts.PathspecList...)
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50) opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
stderr := bytes.Buffer{} stderr := bytes.Buffer{}
err = cmd.Run(ctx, &RunOpts{ err = cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: stdoutWriter, Stdout: stdoutWriter,
Stderr: &stderr, Stderr: &stderr,
@ -135,11 +136,11 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
}, },
}) })
// git grep exits by cancel (killed), usually it is caused by the limit of results // git grep exits by cancel (killed), usually it is caused by the limit of results
if IsErrorExitCode(err, -1) && stderr.Len() == 0 { if gitcmd.IsErrorExitCode(err, -1) && stderr.Len() == 0 {
return results, nil return results, nil
} }
// git grep exits with 1 if no results are found // git grep exits with 1 if no results are found
if IsErrorExitCode(err, 1) && stderr.Len() == 0 { if gitcmd.IsErrorExitCode(err, 1) && stderr.Len() == 0 {
return nil, nil return nil, nil
} }
if err != nil && !errors.Is(err, context.Canceled) { if err != nil && !errors.Is(err, context.Canceled) {

View File

@ -3,13 +3,24 @@
package git package git
import "code.gitea.io/gitea/modules/setting"
// Based on https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat // Based on https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat
const ( const (
SigningKeyFormatOpenPGP = "openpgp" // for GPG keys, the expected default of git cli SigningKeyFormatOpenPGP = "openpgp" // for GPG keys, the expected default of git cli
SigningKeyFormatSSH = "ssh" SigningKeyFormatSSH = "ssh"
) )
// SigningKey represents an instance key info which will be used to sign git commits.
// FIXME: need to refactor it to a new name, this name conflicts with the variable names for "asymkey.GPGKey" in many places.
type SigningKey struct { type SigningKey struct {
KeyID string KeyID string
Format string Format string
} }
func (s *SigningKey) String() string {
// Do not expose KeyID
// In case the key is a file path and the struct is rendered in a template, then the server path will be exposed.
setting.PanicInDevOrTesting("don't call SigningKey.String() - it exposes the KeyID which might be a local file path")
return "SigningKey:" + s.Format
}

View File

@ -14,6 +14,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git/gitcmd"
"github.com/djherbis/buffer" "github.com/djherbis/buffer"
"github.com/djherbis/nio/v3" "github.com/djherbis/nio/v3"
@ -34,7 +35,7 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
} }
cmd := NewCommand() cmd := gitcmd.NewCommand()
cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head) cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head)
var files []string var files []string
@ -64,13 +65,13 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
go func() { go func() {
stderr := strings.Builder{} stderr := strings.Builder{}
err := cmd.Run(ctx, &RunOpts{ err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repository, Dir: repository,
Stdout: stdoutWriter, Stdout: stdoutWriter,
Stderr: &stderr, Stderr: &stderr,
}) })
if err != nil { if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
return return
} }

View File

@ -13,7 +13,7 @@ import (
"strings" "strings"
"sync" "sync"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -25,8 +25,8 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
var errbuf strings.Builder var errbuf strings.Builder
cmd := git.NewCommand("cat-file", "--batch-check") cmd := gitcmd.NewCommand("cat-file", "--batch-check")
if err := cmd.Run(ctx, &git.RunOpts{ if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath, Dir: tmpBasePath,
Stdin: shasToCheckReader, Stdin: shasToCheckReader,
Stdout: catFileCheckWriter, Stdout: catFileCheckWriter,
@ -43,8 +43,8 @@ func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.Pip
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
var errbuf strings.Builder var errbuf strings.Builder
cmd := git.NewCommand("cat-file", "--batch-check", "--batch-all-objects") cmd := gitcmd.NewCommand("cat-file", "--batch-check", "--batch-all-objects")
if err := cmd.Run(ctx, &git.RunOpts{ if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath, Dir: tmpBasePath,
Stdout: catFileCheckWriter, Stdout: catFileCheckWriter,
Stderr: stderr, Stderr: stderr,
@ -64,7 +64,7 @@ func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFile
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
var errbuf strings.Builder var errbuf strings.Builder
if err := git.NewCommand("cat-file", "--batch").Run(ctx, &git.RunOpts{ if err := gitcmd.NewCommand("cat-file", "--batch").Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath, Dir: tmpBasePath,
Stdout: catFileBatchWriter, Stdout: catFileBatchWriter,
Stdin: shasToBatchReader, Stdin: shasToBatchReader,

View File

@ -14,6 +14,7 @@ import (
"sync" "sync"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// FindLFSFile finds commits that contain a provided pointer file hash // FindLFSFile finds commits that contain a provided pointer file hash
@ -32,13 +33,13 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
go func() { go func() {
stderr := strings.Builder{} stderr := strings.Builder{}
err := git.NewCommand("rev-list", "--all").Run(repo.Ctx, &git.RunOpts{ err := gitcmd.NewCommand("rev-list", "--all").Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: revListWriter, Stdout: revListWriter,
Stderr: &stderr, Stderr: &stderr,
}) })
if err != nil { if err != nil {
_ = revListWriter.CloseWithError(git.ConcatenateError(err, (&stderr).String())) _ = revListWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else { } else {
_ = revListWriter.Close() _ = revListWriter.Close()
} }

View File

@ -11,7 +11,7 @@ import (
"strings" "strings"
"sync" "sync"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd"
) )
// NameRevStdin runs name-rev --stdin // NameRevStdin runs name-rev --stdin
@ -22,7 +22,7 @@ func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevS
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
var errbuf strings.Builder var errbuf strings.Builder
if err := git.NewCommand("name-rev", "--stdin", "--name-only", "--always").Run(ctx, &git.RunOpts{ if err := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always").Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath, Dir: tmpBasePath,
Stdout: nameRevStdinWriter, Stdout: nameRevStdinWriter,
Stdin: shasToNameReader, Stdin: shasToNameReader,

View File

@ -12,7 +12,7 @@ import (
"strings" "strings"
"sync" "sync"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -23,8 +23,8 @@ func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sy
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
var errbuf strings.Builder var errbuf strings.Builder
cmd := git.NewCommand("rev-list", "--objects", "--all") cmd := gitcmd.NewCommand("rev-list", "--objects", "--all")
if err := cmd.Run(ctx, &git.RunOpts{ if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: basePath, Dir: basePath,
Stdout: revListWriter, Stdout: revListWriter,
Stderr: stderr, Stderr: stderr,
@ -42,11 +42,11 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.
defer revListWriter.Close() defer revListWriter.Close()
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
var errbuf strings.Builder var errbuf strings.Builder
cmd := git.NewCommand("rev-list", "--objects").AddDynamicArguments(headSHA) cmd := gitcmd.NewCommand("rev-list", "--objects").AddDynamicArguments(headSHA)
if baseSHA != "" { if baseSHA != "" {
cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA) cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA)
} }
if err := cmd.Run(ctx, &git.RunOpts{ if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath, Dir: tmpBasePath,
Stdout: revListWriter, Stdout: revListWriter,
Stderr: stderr, Stderr: stderr,

View File

@ -9,19 +9,20 @@ import (
"net/url" "net/url"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name // GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) { func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
var cmd *Command var cmd *gitcmd.Command
if DefaultFeatures().CheckVersionAtLeast("2.7") { if DefaultFeatures().CheckVersionAtLeast("2.7") {
cmd = NewCommand("remote", "get-url").AddDynamicArguments(remoteName) cmd = gitcmd.NewCommand("remote", "get-url").AddDynamicArguments(remoteName)
} else { } else {
cmd = NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url") cmd = gitcmd.NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
} }
result, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) result, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -17,6 +17,7 @@ import (
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -40,9 +41,9 @@ func (repo *Repository) GetAllCommitsCount() (int64, error) {
func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) { func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) {
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]' // avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
logs, _, err := NewCommand("log").AddArguments(prettyLogFormat). logs, _, err := gitcmd.NewCommand("log").AddArguments(prettyLogFormat).
AddDynamicArguments(revisionRange).AddArguments("--"). AddDynamicArguments(revisionRange).AddArguments("--").
RunStdBytes(ctx, &RunOpts{Dir: repo.Path}) RunStdBytes(ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,7 +71,7 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro
// IsRepoURLAccessible checks if given repository URL is accessible. // IsRepoURLAccessible checks if given repository URL is accessible.
func IsRepoURLAccessible(ctx context.Context, url string) bool { func IsRepoURLAccessible(ctx context.Context, url string) bool {
_, _, err := NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx, nil) _, _, err := gitcmd.NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx, nil)
return err == nil return err == nil
} }
@ -81,7 +82,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
return err return err
} }
cmd := NewCommand("init") cmd := gitcmd.NewCommand("init")
if !IsValidObjectFormat(objectFormatName) { if !IsValidObjectFormat(objectFormatName) {
return fmt.Errorf("invalid object format: %s", objectFormatName) return fmt.Errorf("invalid object format: %s", objectFormatName)
@ -93,15 +94,15 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
if bare { if bare {
cmd.AddArguments("--bare") cmd.AddArguments("--bare")
} }
_, _, err = cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) _, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
return err return err
} }
// IsEmpty Check if repository is empty. // IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) { func (repo *Repository) IsEmpty() (bool, error) {
var errbuf, output strings.Builder var errbuf, output strings.Builder
if err := NewCommand().AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all"). if err := gitcmd.NewCommand().AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all").
Run(repo.Ctx, &RunOpts{ Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: &output, Stdout: &output,
Stderr: &errbuf, Stderr: &errbuf,
@ -137,7 +138,7 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
return err return err
} }
cmd := NewCommand().AddArguments("clone") cmd := gitcmd.NewCommand().AddArguments("clone")
if opts.SkipTLSVerify { if opts.SkipTLSVerify {
cmd.AddArguments("-c", "http.sslVerify=false") cmd.AddArguments("-c", "http.sslVerify=false")
} }
@ -178,13 +179,13 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
} }
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
if err = cmd.Run(ctx, &RunOpts{ if err = cmd.Run(ctx, &gitcmd.RunOpts{
Timeout: opts.Timeout, Timeout: opts.Timeout,
Env: envs, Env: envs,
Stdout: io.Discard, Stdout: io.Discard,
Stderr: stderr, Stderr: stderr,
}); err != nil { }); err != nil {
return ConcatenateError(err, stderr.String()) return gitcmd.ConcatenateError(err, stderr.String())
} }
return nil return nil
} }
@ -201,7 +202,7 @@ type PushOptions struct {
// Push pushs local commits to given remote branch. // Push pushs local commits to given remote branch.
func Push(ctx context.Context, repoPath string, opts PushOptions) error { func Push(ctx context.Context, repoPath string, opts PushOptions) error {
cmd := NewCommand("push") cmd := gitcmd.NewCommand("push")
if opts.Force { if opts.Force {
cmd.AddArguments("-f") cmd.AddArguments("-f")
} }
@ -214,7 +215,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
} }
cmd.AddDashesAndList(remoteBranchArgs...) cmd.AddDashesAndList(remoteBranchArgs...)
stdout, stderr, err := cmd.RunStdString(ctx, &RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath}) stdout, stderr, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath})
if err != nil { if err != nil {
if strings.Contains(stderr, "non-fast-forward") { if strings.Contains(stderr, "non-fast-forward") {
return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err} return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
@ -233,8 +234,8 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
// GetLatestCommitTime returns time for latest commit in repository (across all branches) // GetLatestCommitTime returns time for latest commit in repository (across all branches)
func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) { func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) {
cmd := NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)") cmd := gitcmd.NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)")
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
return time.Time{}, err return time.Time{}, err
} }
@ -250,9 +251,9 @@ type DivergeObject struct {
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch // 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) { func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) {
cmd := NewCommand("rev-list", "--count", "--left-right"). cmd := gitcmd.NewCommand("rev-list", "--count", "--left-right").
AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--") AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
return do, err return do, err
} }
@ -281,23 +282,23 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.
defer cleanup() defer cleanup()
env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects"))
_, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) _, _, err = gitcmd.NewCommand("init", "--bare").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil { if err != nil {
return err return err
} }
_, _, err = NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) _, _, err = gitcmd.NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil { if err != nil {
return err return err
} }
_, _, err = NewCommand("branch", "-m", "bundle").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) _, _, err = gitcmd.NewCommand("branch", "-m", "bundle").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil { if err != nil {
return err return err
} }
tmpFile := filepath.Join(tmp, "bundle") tmpFile := filepath.Join(tmp, "bundle")
_, _, err = NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) _, _, err = gitcmd.NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil { if err != nil {
return err return err
} }

View File

@ -10,6 +10,8 @@ import (
"io" "io"
"path/filepath" "path/filepath"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// ArchiveType archive types // ArchiveType archive types
@ -53,7 +55,7 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
return fmt.Errorf("unknown format: %v", format) return fmt.Errorf("unknown format: %v", format)
} }
cmd := NewCommand("archive") cmd := gitcmd.NewCommand("archive")
if usePrefix { if usePrefix {
cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/") cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/")
} }
@ -61,13 +63,13 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
cmd.AddDynamicArguments(commitID) cmd.AddDynamicArguments(commitID)
var stderr strings.Builder var stderr strings.Builder
err := cmd.Run(ctx, &RunOpts{ err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: target, Stdout: target,
Stderr: &stderr, Stderr: &stderr,
}) })
if err != nil { if err != nil {
return ConcatenateError(err, stderr.String()) return gitcmd.ConcatenateError(err, stderr.String())
} }
return nil return nil
} }

View File

@ -5,14 +5,16 @@ package git
import ( import (
"fmt" "fmt"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// LineBlame returns the latest commit at the given line // LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
res, _, err := NewCommand("blame"). res, _, err := gitcmd.NewCommand("blame").
AddOptionFormat("-L %d,%d", line, line). AddOptionFormat("-L %d,%d", line, line).
AddOptionValues("-p", revision). AddOptionValues("-p", revision).
AddDashesAndList(file).RunStdString(repo.Ctx, &RunOpts{Dir: path}) AddDashesAndList(file).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: path})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -8,6 +8,8 @@ import (
"context" "context"
"errors" "errors"
"strings" "strings"
"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
@ -15,7 +17,7 @@ const BranchPrefix = "refs/heads/"
// 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, repoPath, name string) bool { func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
_, _, err := NewCommand("show-ref", "--verify").AddDashesAndList(name).RunStdString(ctx, &RunOpts{Dir: repoPath}) _, _, err := gitcmd.NewCommand("show-ref", "--verify").AddDashesAndList(name).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
return err == nil return err == nil
} }
@ -25,7 +27,7 @@ func IsBranchExist(ctx context.Context, repoPath, name string) bool {
} }
func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) { func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
stdout, _, err := NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &RunOpts{Dir: repoPath}) stdout, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -43,7 +45,7 @@ type DeleteBranchOptions struct {
// DeleteBranch delete a branch by name on repository. // DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error { func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
cmd := NewCommand("branch") cmd := gitcmd.NewCommand("branch")
if opts.Force { if opts.Force {
cmd.AddArguments("-D") cmd.AddArguments("-D")
@ -52,35 +54,35 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro
} }
cmd.AddDashesAndList(name) cmd.AddDashesAndList(name)
_, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
// CreateBranch create a new branch // CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error { func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
cmd := NewCommand("branch") cmd := gitcmd.NewCommand("branch")
cmd.AddDashesAndList(branch, oldbranchOrCommit) cmd.AddDashesAndList(branch, oldbranchOrCommit)
_, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err 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 := NewCommand("remote", "add") cmd := gitcmd.NewCommand("remote", "add")
if fetch { if fetch {
cmd.AddArguments("-f") cmd.AddArguments("-f")
} }
cmd.AddDynamicArguments(name, url) cmd.AddDynamicArguments(name, url)
_, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
// RenameBranch rename a branch // RenameBranch rename a branch
func (repo *Repository) RenameBranch(from, to string) error { func (repo *Repository) RenameBranch(from, to string) error {
_, _, err := NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }

View File

@ -13,6 +13,7 @@ import (
"io" "io"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -70,25 +71,25 @@ func (repo *Repository) IsBranchExist(name string) bool {
// GetBranchNames returns branches from the repository, skipping "skip" initial branches and // GetBranchNames returns branches from the repository, skipping "skip" initial branches and
// returning at most "limit" branches, or all branches if "limit" is 0. // returning at most "limit" branches, or all branches if "limit" is 0.
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
return callShowRef(repo.Ctx, repo.Path, BranchPrefix, TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) return callShowRef(repo.Ctx, repo.Path, BranchPrefix, gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit)
} }
// WalkReferences walks all the references from the repository // WalkReferences walks all the references from the repository
// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. // refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty.
func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) {
var args TrustedCmdArgs var args gitcmd.TrustedCmdArgs
switch refType { switch refType {
case ObjectTag: case ObjectTag:
args = TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} args = gitcmd.TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}
case ObjectBranch: case ObjectBranch:
args = TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} args = gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}
} }
return WalkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn) return WalkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn)
} }
// callShowRef return refs, if limit = 0 it will not limit // callShowRef return refs, if limit = 0 it will not limit
func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) {
countAll, err = WalkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error { countAll, err = WalkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error {
branchName = strings.TrimPrefix(branchName, trimPrefix) branchName = strings.TrimPrefix(branchName, trimPrefix)
branchNames = append(branchNames, branchName) branchNames = append(branchNames, branchName)
@ -98,7 +99,7 @@ func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs Tru
return branchNames, countAll, err return branchNames, countAll, err
} }
func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { func WalkShowRef(ctx context.Context, repoPath string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe() stdoutReader, stdoutWriter := io.Pipe()
defer func() { defer func() {
_ = stdoutReader.Close() _ = stdoutReader.Close()
@ -107,9 +108,9 @@ func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs,
go func() { go func() {
stderrBuilder := &strings.Builder{} stderrBuilder := &strings.Builder{}
args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"}
args = append(args, extraArgs...) args = append(args, extraArgs...)
err := NewCommand(args...).Run(ctx, &RunOpts{ err := gitcmd.NewCommand(args...).Run(ctx, &gitcmd.RunOpts{
Dir: repoPath, Dir: repoPath,
Stdout: stdoutWriter, Stdout: stdoutWriter,
Stderr: stderrBuilder, Stderr: stderrBuilder,
@ -119,7 +120,7 @@ func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs,
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
return return
} }
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderrBuilder.String()))
} else { } else {
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
} }

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -59,7 +60,7 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com
relpath = `\` + relpath relpath = `\` + relpath
} }
stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
@ -74,7 +75,7 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com
// GetCommitByPath returns the last commit of relative path. // GetCommitByPath returns the last commit of relative path.
func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
@ -91,7 +92,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
// commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support // commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support
func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) { func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) {
cmd := NewCommand("log"). cmd := gitcmd.NewCommand("log").
AddOptionFormat("--skip=%d", (page-1)*pageSize). AddOptionFormat("--skip=%d", (page-1)*pageSize).
AddOptionFormat("--max-count=%d", pageSize). AddOptionFormat("--max-count=%d", pageSize).
AddArguments(prettyLogFormat). AddArguments(prettyLogFormat).
@ -107,7 +108,7 @@ func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int,
cmd.AddOptionFormat("--until=%s", until) cmd.AddOptionFormat("--until=%s", until)
} }
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -117,7 +118,7 @@ func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int,
func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([]*Commit, error) { func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([]*Commit, error) {
// add common arguments to git command // add common arguments to git command
addCommonSearchArgs := func(c *Command) { addCommonSearchArgs := func(c *gitcmd.Command) {
// ignore case // ignore case
c.AddArguments("-i") c.AddArguments("-i")
@ -141,7 +142,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
} }
// create new git log command with limit of 100 commits // create new git log command with limit of 100 commits
cmd := NewCommand("log", "-100", prettyLogFormat).AddDynamicArguments(id.String()) cmd := gitcmd.NewCommand("log", "-100", prettyLogFormat).AddDynamicArguments(id.String())
// pretend that all refs along with HEAD were listed on command line as <commis> // pretend that all refs along with HEAD were listed on command line as <commis>
// https://git-scm.com/docs/git-log#Documentation/git-log.txt---all // https://git-scm.com/docs/git-log#Documentation/git-log.txt---all
@ -161,7 +162,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// search for commits matching given constraints and keywords in commit msg // search for commits matching given constraints and keywords in commit msg
addCommonSearchArgs(cmd) addCommonSearchArgs(cmd)
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -175,14 +176,14 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// ignore anything not matching a valid sha pattern // ignore anything not matching a valid sha pattern
if id.Type().IsValid(v) { if id.Type().IsValid(v) {
// create new git log command with 1 commit limit // create new git log command with 1 commit limit
hashCmd := NewCommand("log", "-1", prettyLogFormat) hashCmd := gitcmd.NewCommand("log", "-1", prettyLogFormat)
// add previous arguments except for --grep and --all // add previous arguments except for --grep and --all
addCommonSearchArgs(hashCmd) addCommonSearchArgs(hashCmd)
// add keyword as <commit> // add keyword as <commit>
hashCmd.AddDynamicArguments(v) hashCmd.AddDynamicArguments(v)
// search with given constraints for commit matching sha hash of v // search with given constraints for commit matching sha hash of v
hashMatching, _, err := hashCmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) hashMatching, _, err := hashCmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil || bytes.Contains(stdout, hashMatching) { if err != nil || bytes.Contains(stdout, hashMatching) {
continue continue
} }
@ -197,7 +198,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2 // FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2
// You must ensure that id1 and id2 are valid commit ids. // You must ensure that id1 and id2 are valid commit ids.
func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) { func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) {
stdout, _, err := NewCommand("diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := gitcmd.NewCommand("diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return false, err return false, err
} }
@ -232,7 +233,7 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
}() }()
go func() { go func() {
stderr := strings.Builder{} stderr := strings.Builder{}
gitCmd := NewCommand("rev-list"). gitCmd := gitcmd.NewCommand("rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize). AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize) AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision) gitCmd.AddDynamicArguments(opts.Revision)
@ -248,13 +249,13 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
} }
gitCmd.AddDashesAndList(opts.File) gitCmd.AddDashesAndList(opts.File)
err := gitCmd.Run(repo.Ctx, &RunOpts{ err := gitCmd.Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: stdoutWriter, Stdout: stdoutWriter,
Stderr: &stderr, Stderr: &stderr,
}) })
if err != nil { if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else { } else {
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
} }
@ -290,11 +291,11 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
// FilesCountBetween return the number of files changed between two commits // FilesCountBetween return the number of files changed between two commits
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) { func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
stdout, _, err := NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID+"..."+endCommitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID+"..."+endCommitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
// git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated. // git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated.
// previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that... // previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that...
stdout, _, err = NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err = gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} }
if err != nil { if err != nil {
return 0, err return 0, err
@ -308,13 +309,13 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error)
var stdout []byte var stdout []byte
var err error var err error
if before == nil { if before == nil {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} else { } else {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that... // previously it would return the results of git rev-list before last so let's try that...
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} }
} }
if err != nil { if err != nil {
@ -328,22 +329,22 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in
var stdout []byte var stdout []byte
var err error var err error
if before == nil { if before == nil {
stdout, _, err = NewCommand("rev-list"). stdout, _, err = gitcmd.NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)). AddOptionValues("--skip", strconv.Itoa(skip)).
AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} else { } else {
stdout, _, err = NewCommand("rev-list"). stdout, _, err = gitcmd.NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)). AddOptionValues("--skip", strconv.Itoa(skip)).
AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list --max-count n before last so let's try that... // previously it would return the results of git rev-list --max-count n before last so let's try that...
stdout, _, err = NewCommand("rev-list"). stdout, _, err = gitcmd.NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)). AddOptionValues("--skip", strconv.Itoa(skip)).
AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} }
} }
if err != nil { if err != nil {
@ -358,13 +359,13 @@ func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch s
var stdout []byte var stdout []byte
var err error var err error
if before == nil { if before == nil {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} else { } else {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that... // previously it would return the results of git rev-list before last so let's try that...
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} }
} }
if err != nil { if err != nil {
@ -410,13 +411,13 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
// commitsBefore the limit is depth, not total number of returned commits. // commitsBefore the limit is depth, not total number of returned commits.
func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) { func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) {
cmd := NewCommand("log", prettyLogFormat) cmd := gitcmd.NewCommand("log", prettyLogFormat)
if limit > 0 { if limit > 0 {
cmd.AddOptionFormat("-%d", limit) cmd.AddOptionFormat("-%d", limit)
} }
cmd.AddDynamicArguments(id.String()) cmd.AddDynamicArguments(id.String())
stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
@ -453,10 +454,10 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
func (repo *Repository) getBranches(env []string, commitID string, limit int) ([]string, error) { func (repo *Repository) getBranches(env []string, commitID string, limit int) ([]string, error) {
if DefaultFeatures().CheckVersionAtLeast("2.7.0") { if DefaultFeatures().CheckVersionAtLeast("2.7.0") {
stdout, _, err := NewCommand("for-each-ref", "--format=%(refname:strip=2)"). stdout, _, err := gitcmd.NewCommand("for-each-ref", "--format=%(refname:strip=2)").
AddOptionFormat("--count=%d", limit). AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commitID, BranchPrefix). AddOptionValues("--contains", commitID, BranchPrefix).
RunStdString(repo.Ctx, &RunOpts{ RunStdString(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Env: env, Env: env,
}) })
@ -468,7 +469,7 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([
return branches, nil return branches, nil
} }
stdout, _, err := NewCommand("branch").AddOptionValues("--contains", commitID).RunStdString(repo.Ctx, &RunOpts{ stdout, _, err := gitcmd.NewCommand("branch").AddOptionValues("--contains", commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Env: env, Env: env,
}) })
@ -510,7 +511,7 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit {
// IsCommitInBranch check if the commit is on the branch // IsCommitInBranch check if the commit is on the branch
func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) { func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) {
stdout, _, err := NewCommand("branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := gitcmd.NewCommand("branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return false, err return false, err
} }
@ -536,10 +537,10 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
// GetCommitBranchStart returns the commit where the branch diverged // GetCommitBranchStart returns the commit where the branch diverged
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) { func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
cmd := NewCommand("log", prettyLogFormat) cmd := gitcmd.NewCommand("log", prettyLogFormat)
cmd.AddDynamicArguments(endCommitID) cmd.AddDynamicArguments(endCommitID)
stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{ stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Env: env, Env: env,
}) })

View File

@ -9,6 +9,8 @@ package git
import ( import (
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/plumbing/hash"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
@ -59,7 +61,7 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
} }
} }
actualCommitID, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) actualCommitID, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
actualCommitID = strings.TrimSpace(actualCommitID) actualCommitID = strings.TrimSpace(actualCommitID)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") || if strings.Contains(err.Error(), "unknown revision or path") ||

View File

@ -11,12 +11,13 @@ import (
"io" "io"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
// ResolveReference resolves a name to a reference // ResolveReference resolves a name to a reference
func (repo *Repository) ResolveReference(name string) (string, error) { func (repo *Repository) ResolveReference(name string) (string, error) {
stdout, _, err := NewCommand("show-ref", "--hash").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := gitcmd.NewCommand("show-ref", "--hash").AddDynamicArguments(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
if strings.Contains(err.Error(), "not a valid ref") { if strings.Contains(err.Error(), "not a valid ref") {
return "", ErrNotExist{name, ""} return "", ErrNotExist{name, ""}
@ -52,13 +53,13 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
// SetReference sets the commit ID string of given reference (e.g. branch or tag). // SetReference sets the commit ID string of given reference (e.g. branch or tag).
func (repo *Repository) SetReference(name, commitID string) error { func (repo *Repository) SetReference(name, commitID string) error {
_, _, err := NewCommand("update-ref").AddDynamicArguments(name, commitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("update-ref").AddDynamicArguments(name, commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
// RemoveReference removes the given reference (e.g. branch or tag). // RemoveReference removes the given reference (e.g. branch or tag).
func (repo *Repository) RemoveReference(name string) error { func (repo *Repository) RemoveReference(name string) error {
_, _, err := NewCommand("update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
@ -68,7 +69,7 @@ func (repo *Repository) IsCommitExist(name string) bool {
log.Error("IsCommitExist: %v", err) log.Error("IsCommitExist: %v", err)
return false return false
} }
_, _, err := NewCommand("cat-file", "-e").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("cat-file", "-e").AddDynamicArguments(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err == nil return err == nil
} }

View File

@ -6,13 +6,15 @@ package git
import ( import (
"context" "context"
"fmt" "fmt"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// WriteCommitGraph write commit graph to speed up repo access // WriteCommitGraph write commit graph to speed up repo access
// this requires git v2.18 to be installed // this requires git v2.18 to be installed
func WriteCommitGraph(ctx context.Context, repoPath string) error { func WriteCommitGraph(ctx context.Context, repoPath string) error {
if DefaultFeatures().CheckVersionAtLeast("2.18") { if DefaultFeatures().CheckVersionAtLeast("2.18") {
if _, _, err := NewCommand("commit-graph", "write").RunStdString(ctx, &RunOpts{Dir: repoPath}); err != nil { if _, _, err := gitcmd.NewCommand("commit-graph", "write").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil {
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
} }
} }

View File

@ -16,6 +16,8 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// GetMergeBase checks and returns merge base of two branches and the reference used as base. // GetMergeBase checks and returns merge base of two branches and the reference used as base.
@ -27,13 +29,13 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
if tmpRemote != "origin" { if tmpRemote != "origin" {
tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags // Fetch commit into a temporary branch in order to be able to handle commits and tags
_, _, err := NewCommand("fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base+":"+tmpBaseName).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base+":"+tmpBaseName).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err == nil { if err == nil {
base = tmpBaseName base = tmpBaseName
} }
} }
stdout, _, err := NewCommand("merge-base").AddDashesAndList(base, head).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := gitcmd.NewCommand("merge-base").AddDashesAndList(base, head).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return strings.TrimSpace(stdout), base, err return strings.TrimSpace(stdout), base, err
} }
@ -61,8 +63,8 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
} }
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]' // avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
if err := NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base+separator+head).AddArguments("--"). if err := gitcmd.NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base+separator+head).AddArguments("--").
Run(repo.Ctx, &RunOpts{ Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
Stderr: stderr, Stderr: stderr,
@ -72,7 +74,7 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
// previously it would return the results of git diff -z --name-only base head so let's try that... // previously it would return the results of git diff -z --name-only base head so let's try that...
w = &lineCountWriter{} w = &lineCountWriter{}
stderr.Reset() stderr.Reset()
if err = NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base, head).AddArguments("--").Run(repo.Ctx, &RunOpts{ if err = gitcmd.NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base, head).AddArguments("--").Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
Stderr: stderr, Stderr: stderr,
@ -87,13 +89,13 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
// GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions // GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions
// TODO: it can be merged with another "GetDiffShortStat" in the future // TODO: it can be merged with another "GetDiffShortStat" in the future
func GetDiffShortStatByCmdArgs(ctx context.Context, repoPath string, trustedArgs TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) { func GetDiffShortStatByCmdArgs(ctx context.Context, repoPath string, trustedArgs gitcmd.TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
// Now if we call: // Now if we call:
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875 // $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
// we get: // we get:
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n" // " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
cmd := NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...) cmd := gitcmd.NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
return 0, 0, 0, err return 0, 0, 0, err
} }
@ -139,8 +141,8 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int,
// 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)
return NewCommand("diff", "-p").AddDynamicArguments(compareArg). return gitcmd.NewCommand("diff", "-p").AddDynamicArguments(compareArg).
Run(repo.Ctx, &RunOpts{ Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
Stderr: stderr, Stderr: stderr,
@ -149,7 +151,7 @@ func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs. // GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error { func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
return NewCommand("diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(repo.Ctx, &RunOpts{ return gitcmd.NewCommand("diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
}) })
@ -158,8 +160,8 @@ func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply` // GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`
func (repo *Repository) GetPatch(compareArg string, w io.Writer) error { func (repo *Repository) GetPatch(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
return NewCommand("format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg). return gitcmd.NewCommand("format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg).
Run(repo.Ctx, &RunOpts{ Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
Stderr: stderr, Stderr: stderr,
@ -174,13 +176,13 @@ func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
cmd := NewCommand("diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z") cmd := gitcmd.NewCommand("diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
if base == objectFormat.EmptyObjectID().String() { if base == objectFormat.EmptyObjectID().String() {
cmd.AddDynamicArguments(head) cmd.AddDynamicArguments(head)
} else { } else {
cmd.AddDynamicArguments(base, head) cmd.AddDynamicArguments(base, head)
} }
stdout, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,6 +9,7 @@ import (
"os" "os"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
) )
@ -42,7 +43,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
Sign: true, Sign: true,
} }
value, _, _ := NewCommand("config", "--get", "commit.gpgsign").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) value, _, _ := gitcmd.NewCommand("config", "--get", "commit.gpgsign").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
sign, valid := ParseBool(strings.TrimSpace(value)) sign, valid := ParseBool(strings.TrimSpace(value))
if !sign || !valid { if !sign || !valid {
gpgSettings.Sign = false gpgSettings.Sign = false
@ -50,16 +51,16 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
return gpgSettings, nil return gpgSettings, nil
} }
signingKey, _, _ := NewCommand("config", "--get", "user.signingkey").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) signingKey, _, _ := gitcmd.NewCommand("config", "--get", "user.signingkey").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
gpgSettings.KeyID = strings.TrimSpace(signingKey) gpgSettings.KeyID = strings.TrimSpace(signingKey)
format, _, _ := NewCommand("config", "--default", SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) format, _, _ := gitcmd.NewCommand("config", "--default", SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
gpgSettings.Format = strings.TrimSpace(format) gpgSettings.Format = strings.TrimSpace(format)
defaultEmail, _, _ := NewCommand("config", "--get", "user.email").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) defaultEmail, _, _ := gitcmd.NewCommand("config", "--get", "user.email").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
gpgSettings.Email = strings.TrimSpace(defaultEmail) gpgSettings.Email = strings.TrimSpace(defaultEmail)
defaultName, _, _ := NewCommand("config", "--get", "user.name").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) defaultName, _, _ := gitcmd.NewCommand("config", "--get", "user.name").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
gpgSettings.Name = strings.TrimSpace(defaultName) gpgSettings.Name = strings.TrimSpace(defaultName)
if err := gpgSettings.LoadPublicKeyContent(); err != nil { if err := gpgSettings.LoadPublicKeyContent(); err != nil {

View File

@ -10,6 +10,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -21,7 +22,7 @@ func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string)
} }
if len(treeish) != objectFormat.FullLength() { if len(treeish) != objectFormat.FullLength() {
res, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) res, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return err return err
} }
@ -41,7 +42,7 @@ func (repo *Repository) readTreeToIndex(id ObjectID, indexFilename ...string) er
if len(indexFilename) > 0 { if len(indexFilename) > 0 {
env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0]) env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0])
} }
_, _, err := NewCommand("read-tree").AddDynamicArguments(id.String()).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path, Env: env}) _, _, err := gitcmd.NewCommand("read-tree").AddDynamicArguments(id.String()).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path, Env: env})
if err != nil { if err != nil {
return err return err
} }
@ -74,14 +75,14 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (tmpIndexFilena
// EmptyIndex empties the index // EmptyIndex empties the index
func (repo *Repository) EmptyIndex() error { func (repo *Repository) EmptyIndex() error {
_, _, err := NewCommand("read-tree", "--empty").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("read-tree", "--empty").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
// LsFiles checks if the given filenames are in the index // LsFiles checks if the given filenames are in the index
func (repo *Repository) LsFiles(filenames ...string) ([]string, error) { func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
cmd := NewCommand("ls-files", "-z").AddDashesAndList(filenames...) cmd := gitcmd.NewCommand("ls-files", "-z").AddDashesAndList(filenames...)
res, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) res, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -99,7 +100,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
if err != nil { if err != nil {
return err return err
} }
cmd := NewCommand("update-index", "--remove", "-z", "--index-info") cmd := gitcmd.NewCommand("update-index", "--remove", "-z", "--index-info")
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
@ -109,7 +110,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
buffer.WriteString("0 blob " + objectFormat.EmptyObjectID().String() + "\t" + file + "\000") buffer.WriteString("0 blob " + objectFormat.EmptyObjectID().String() + "\t" + file + "\000")
} }
} }
return cmd.Run(repo.Ctx, &RunOpts{ return cmd.Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdin: bytes.NewReader(buffer.Bytes()), Stdin: bytes.NewReader(buffer.Bytes()),
Stdout: stdout, Stdout: stdout,
@ -125,7 +126,7 @@ type IndexObjectInfo struct {
// AddObjectsToIndex adds the provided object hashes to the index at the provided filenames // AddObjectsToIndex adds the provided object hashes to the index at the provided filenames
func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error { func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error {
cmd := NewCommand("update-index", "--add", "--replace", "-z", "--index-info") cmd := gitcmd.NewCommand("update-index", "--add", "--replace", "-z", "--index-info")
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
@ -133,7 +134,7 @@ func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error {
// using format: mode SP type SP sha1 TAB path // using format: mode SP type SP sha1 TAB path
buffer.WriteString(object.Mode + " blob " + object.Object.String() + "\t" + object.Filename + "\000") buffer.WriteString(object.Mode + " blob " + object.Object.String() + "\t" + object.Filename + "\000")
} }
return cmd.Run(repo.Ctx, &RunOpts{ return cmd.Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdin: bytes.NewReader(buffer.Bytes()), Stdin: bytes.NewReader(buffer.Bytes()),
Stdout: stdout, Stdout: stdout,
@ -148,7 +149,7 @@ func (repo *Repository) AddObjectToIndex(mode string, object ObjectID, filename
// WriteTree writes the current index as a tree to the object db and returns its hash // WriteTree writes the current index as a tree to the object db and returns its hash
func (repo *Repository) WriteTree() (*Tree, error) { func (repo *Repository) WriteTree() (*Tree, error) {
stdout, _, runErr := NewCommand("write-tree").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, runErr := gitcmd.NewCommand("write-tree").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }

View File

@ -8,6 +8,8 @@ import (
"bytes" "bytes"
"io" "io"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// ObjectType git object type // ObjectType git object type
@ -66,15 +68,15 @@ func (repo *Repository) HashObject(reader io.Reader) (ObjectID, error) {
} }
func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error) { func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error) {
var cmd *Command var cmd *gitcmd.Command
if save { if save {
cmd = NewCommand("hash-object", "-w", "--stdin") cmd = gitcmd.NewCommand("hash-object", "-w", "--stdin")
} else { } else {
cmd = NewCommand("hash-object", "--stdin") cmd = gitcmd.NewCommand("hash-object", "--stdin")
} }
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := cmd.Run(repo.Ctx, &RunOpts{ err := cmd.Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdin: reader, Stdin: reader,
Stdout: stdout, Stdout: stdout,

View File

@ -7,6 +7,7 @@ import (
"context" "context"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -18,7 +19,7 @@ func (repo *Repository) GetRefs() ([]*Reference, error) {
// ListOccurrences lists all refs of the given refType the given commit appears in sorted by creation date DESC // ListOccurrences lists all refs of the given refType the given commit appears in sorted by creation date DESC
// refType should only be a literal "branch" or "tag" and nothing else // refType should only be a literal "branch" or "tag" and nothing else
func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA string) ([]string, error) { func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA string) ([]string, error) {
cmd := NewCommand() cmd := gitcmd.NewCommand()
switch refType { switch refType {
case "branch": case "branch":
cmd.AddArguments("branch") cmd.AddArguments("branch")
@ -27,7 +28,7 @@ func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA
default: default:
return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType)
} }
stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,6 +9,8 @@ import (
"bufio" "bufio"
"io" "io"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with. // GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with.
@ -21,13 +23,13 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
go func() { go func() {
stderrBuilder := &strings.Builder{} stderrBuilder := &strings.Builder{}
err := NewCommand("for-each-ref").Run(repo.Ctx, &RunOpts{ err := gitcmd.NewCommand("for-each-ref").Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: stdoutWriter, Stdout: stdoutWriter,
Stderr: stderrBuilder, Stderr: stderrBuilder,
}) })
if err != nil { if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderrBuilder.String()))
} else { } else {
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
} }

View File

@ -14,6 +14,7 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// CodeActivityStats represents git statistics data // CodeActivityStats represents git statistics data
@ -40,9 +41,9 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
since := fromTime.Format(time.RFC3339) since := fromTime.Format(time.RFC3339)
stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso"). stdout, _, runErr := gitcmd.NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").
AddOptionFormat("--since=%s", since). AddOptionFormat("--since=%s", since).
RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
@ -62,7 +63,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
}() }()
gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso"). gitCmd := gitcmd.NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").
AddOptionFormat("--since=%s", since) AddOptionFormat("--since=%s", since)
if len(branch) == 0 { if len(branch) == 0 {
gitCmd.AddArguments("--branches=*") gitCmd.AddArguments("--branches=*")
@ -71,7 +72,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
} }
stderr := new(strings.Builder) stderr := new(strings.Builder)
err = gitCmd.Run(repo.Ctx, &RunOpts{ err = gitCmd.Run(repo.Ctx, &gitcmd.RunOpts{
Env: []string{}, Env: []string{},
Dir: repo.Path, Dir: repo.Path,
Stdout: stdoutWriter, Stdout: stdoutWriter,

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/git/foreachref" "code.gitea.io/gitea/modules/git/foreachref"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -18,13 +19,13 @@ const TagPrefix = "refs/tags/"
// CreateTag create one tag in the repository // CreateTag create one tag in the repository
func (repo *Repository) CreateTag(name, revision string) error { func (repo *Repository) CreateTag(name, revision string) error {
_, _, err := NewCommand("tag").AddDashesAndList(name, revision).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("tag").AddDashesAndList(name, revision).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
// CreateAnnotatedTag create one annotated tag in the repository // CreateAnnotatedTag create one annotated tag in the repository
func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error { func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
_, _, err := NewCommand("tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := gitcmd.NewCommand("tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err return err
} }
@ -34,7 +35,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
return "", fmt.Errorf("SHA is too short: %s", sha) return "", fmt.Errorf("SHA is too short: %s", sha)
} }
stdout, _, err := NewCommand("show-ref", "--tags", "-d").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := gitcmd.NewCommand("show-ref", "--tags", "-d").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -57,7 +58,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
// GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA) // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
func (repo *Repository) GetTagID(name string) (string, error) { func (repo *Repository) GetTagID(name string) (string, error) {
stdout, _, err := NewCommand("show-ref", "--tags").AddDashesAndList(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) stdout, _, err := gitcmd.NewCommand("show-ref", "--tags").AddDashesAndList(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -114,14 +115,14 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
defer stdoutReader.Close() defer stdoutReader.Close()
defer stdoutWriter.Close() defer stdoutWriter.Close()
stderr := strings.Builder{} stderr := strings.Builder{}
rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr} rc := &gitcmd.RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr}
go func() { go func() {
err := NewCommand("for-each-ref"). err := gitcmd.NewCommand("for-each-ref").
AddOptionFormat("--format=%s", forEachRefFmt.Flag()). AddOptionFormat("--format=%s", forEachRefFmt.Flag()).
AddArguments("--sort", "-*creatordate", "refs/tags").Run(repo.Ctx, rc) AddArguments("--sort", "-*creatordate", "refs/tags").Run(repo.Ctx, rc)
if err != nil { if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String())) _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderr.String()))
} else { } else {
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
} }

View File

@ -9,6 +9,8 @@ import (
"os" "os"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// CommitTreeOpts represents the possible options to CommitTree // CommitTreeOpts represents the possible options to CommitTree
@ -33,7 +35,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
"GIT_COMMITTER_EMAIL="+committer.Email, "GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr, "GIT_COMMITTER_DATE="+commitTimeStr,
) )
cmd := NewCommand("commit-tree").AddDynamicArguments(tree.ID.String()) cmd := gitcmd.NewCommand("commit-tree").AddDynamicArguments(tree.ID.String())
for _, parent := range opts.Parents { for _, parent := range opts.Parents {
cmd.AddArguments("-p").AddDynamicArguments(parent) cmd.AddArguments("-p").AddDynamicArguments(parent)
@ -58,7 +60,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := cmd.Run(repo.Ctx, &RunOpts{ err := cmd.Run(repo.Ctx, &gitcmd.RunOpts{
Env: env, Env: env,
Dir: repo.Path, Dir: repo.Path,
Stdin: messageBytes, Stdin: messageBytes,
@ -66,7 +68,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
Stderr: stderr, Stderr: stderr,
}) })
if err != nil { if err != nil {
return nil, ConcatenateError(err, stderr.String()) return nil, gitcmd.ConcatenateError(err, stderr.String())
} }
return NewIDFromString(strings.TrimSpace(stdout.String())) return NewIDFromString(strings.TrimSpace(stdout.String()))
} }

View File

@ -9,6 +9,8 @@ package git
import ( import (
"errors" "errors"
"code.gitea.io/gitea/modules/git/gitcmd"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
) )
@ -36,7 +38,7 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
} }
if len(idStr) != objectFormat.FullLength() { if len(idStr) != objectFormat.FullLength() {
res, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) res, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"os" "os"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -24,7 +25,7 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul
if err != nil { if err != nil {
return nil, err return nil, err
} }
opts := &RunOpts{ opts := &gitcmd.RunOpts{
Dir: repoPath, Dir: repoPath,
Stdout: stdoutWriter, Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
@ -45,7 +46,7 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul
return scanner.Err() return scanner.Err()
}, },
} }
err = NewCommand("ls-tree", "-r", "--", "HEAD").Run(ctx, opts) err = gitcmd.NewCommand("ls-tree", "-r", "--", "HEAD").Run(ctx, opts)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err) return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err)
} }
@ -56,8 +57,8 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul
// It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir. // It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir.
func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error { func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error {
for _, submodule := range submodules { for _, submodule := range submodules {
cmd := NewCommand("update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path) cmd := gitcmd.NewCommand("update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path)
if stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}); err != nil { if stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil {
log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err) log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err)
return err return err
} }

View File

@ -8,6 +8,8 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"code.gitea.io/gitea/modules/git/gitcmd"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -30,14 +32,14 @@ func TestAddTemplateSubmoduleIndexes(t *testing.T) {
ctx := t.Context() ctx := t.Context()
tmpDir := t.TempDir() tmpDir := t.TempDir()
var err error var err error
_, _, err = NewCommand("init").RunStdString(ctx, &RunOpts{Dir: tmpDir}) _, _, err = gitcmd.NewCommand("init").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir})
require.NoError(t, err) require.NoError(t, err)
_ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755) _ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755)
err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}}) err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}})
require.NoError(t, err) require.NoError(t, err)
_, _, err = NewCommand("add", "--all").RunStdString(ctx, &RunOpts{Dir: tmpDir}) _, _, err = gitcmd.NewCommand("add", "--all").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir})
require.NoError(t, err) require.NoError(t, err)
_, _, err = NewCommand("-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(ctx, &RunOpts{Dir: tmpDir}) _, _, err = gitcmd.NewCommand("-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir})
require.NoError(t, err) require.NoError(t, err)
submodules, err := GetTemplateSubmoduleCommits(t.Context(), tmpDir) submodules, err := GetTemplateSubmoduleCommits(t.Context(), tmpDir)
require.NoError(t, err) require.NoError(t, err)

View File

@ -7,6 +7,8 @@ package git
import ( import (
"bytes" "bytes"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// NewTree create a new tree according the repository and tree id // NewTree create a new tree according the repository and tree id
@ -48,10 +50,10 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
// LsTree checks if the given filenames are in the tree // LsTree checks if the given filenames are in the tree
func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) { func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) {
cmd := NewCommand("ls-tree", "-z", "--name-only"). cmd := gitcmd.NewCommand("ls-tree", "-z", "--name-only").
AddDashesAndList(append([]string{ref}, filenames...)...) AddDashesAndList(append([]string{ref}, filenames...)...)
res, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) res, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -65,9 +67,9 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error
// GetTreePathLatestCommit returns the latest commit of a tree path // GetTreePathLatestCommit returns the latest commit of a tree path
func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) {
stdout, _, err := NewCommand("rev-list", "-1"). stdout, _, err := gitcmd.NewCommand("rev-list", "-1").
AddDynamicArguments(refName).AddDashesAndList(treePath). AddDynamicArguments(refName).AddDashesAndList(treePath).
RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -8,6 +8,8 @@ package git
import ( import (
"io" "io"
"strings" "strings"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// Tree represents a flat directory listing. // Tree represents a flat directory listing.
@ -70,7 +72,7 @@ func (t *Tree) ListEntries() (Entries, error) {
} }
} }
stdout, _, runErr := NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(t.repo.Ctx, &RunOpts{Dir: t.repo.Path}) stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(t.repo.Ctx, &gitcmd.RunOpts{Dir: t.repo.Path})
if runErr != nil { if runErr != nil {
if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") {
return nil, ErrNotExist{ return nil, ErrNotExist{
@ -91,15 +93,15 @@ func (t *Tree) ListEntries() (Entries, error) {
// listEntriesRecursive returns all entries of current tree recursively including all subtrees // listEntriesRecursive returns all entries of current tree recursively including all subtrees
// extraArgs could be "-l" to get the size, which is slower // extraArgs could be "-l" to get the size, which is slower
func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, error) {
if t.entriesRecursiveParsed { if t.entriesRecursiveParsed {
return t.entriesRecursive, nil return t.entriesRecursive, nil
} }
stdout, _, runErr := NewCommand("ls-tree", "-t", "-r"). stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-t", "-r").
AddArguments(extraArgs...). AddArguments(extraArgs...).
AddDynamicArguments(t.ID.String()). AddDynamicArguments(t.ID.String()).
RunStdBytes(t.repo.Ctx, &RunOpts{Dir: t.repo.Path}) RunStdBytes(t.repo.Ctx, &gitcmd.RunOpts{Dir: t.repo.Path})
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
@ -120,5 +122,5 @@ func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size
func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
return t.listEntriesRecursive(TrustedCmdArgs{"--long"}) return t.listEntriesRecursive(gitcmd.TrustedCmdArgs{"--long"})
} }

View File

@ -6,7 +6,6 @@ package git
import ( import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"fmt"
"io" "io"
"strconv" "strconv"
"strings" "strings"
@ -42,14 +41,6 @@ func (oc *ObjectCache[T]) Get(id string) (T, bool) {
return obj, has return obj, has
} }
// ConcatenateError concatenats an error with stderr string
func ConcatenateError(err error, stderr string) error {
if len(stderr) == 0 {
return err
}
return fmt.Errorf("%w - %s", err, stderr)
}
// ParseBool returns the boolean value represented by the string as per git's git_config_bool // ParseBool returns the boolean value represented by the string as per git's git_config_bool
// true will be returned for the result if the string is empty, but valid will be false. // true will be returned for the result if the string is empty, but valid will be false.
// "true", "yes", "on" are all true, true // "true", "yes", "on" are all true, true

View File

@ -7,6 +7,7 @@ import (
"context" "context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
) )
// GetBranchesByPath returns a branch by its path // GetBranchesByPath returns a branch by its path
@ -33,9 +34,9 @@ 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 := git.NewCommand("symbolic-ref", "HEAD"). _, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD").
AddDynamicArguments(git.BranchPrefix+name). AddDynamicArguments(git.BranchPrefix+name).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)}) RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
} }

View File

@ -7,14 +7,14 @@ import (
"context" "context"
"strings" "strings"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/globallock"
) )
func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) { func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) {
result, _, err := git.NewCommand("config", "--get"). result, _, err := gitcmd.NewCommand("config", "--get").
AddDynamicArguments(key). AddDynamicArguments(key).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)}) RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -28,9 +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 := git.NewCommand("config", "--add"). _, _, err := gitcmd.NewCommand("config", "--add").
AddDynamicArguments(key, value). AddDynamicArguments(key, value).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)}) RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }
@ -40,9 +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 := git.NewCommand("config"). _, _, err := gitcmd.NewCommand("config").
AddDynamicArguments(key, value). AddDynamicArguments(key, value).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)}) RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }

View File

@ -7,10 +7,10 @@ import (
"context" "context"
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd"
) )
// Fsck verifies the connectivity and validity of the objects in the database // Fsck verifies the connectivity and validity of the objects in the database
func Fsck(ctx context.Context, repo Repository, timeout time.Duration, args git.TrustedCmdArgs) error { func Fsck(ctx context.Context, repo Repository, timeout time.Duration, args gitcmd.TrustedCmdArgs) error {
return git.NewCommand("fsck").AddArguments(args...).Run(ctx, &git.RunOpts{Timeout: timeout, Dir: repoPath(repo)}) return gitcmd.NewCommand("fsck").AddArguments(args...).Run(ctx, &gitcmd.RunOpts{Timeout: timeout, Dir: repoPath(repo)})
} }

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
giturl "code.gitea.io/gitea/modules/git/url" giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -24,7 +25,7 @@ const (
func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL string, options ...RemoteOption) error { 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 { return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
cmd := git.NewCommand("remote", "add") cmd := gitcmd.NewCommand("remote", "add")
if len(options) > 0 { if len(options) > 0 {
switch options[0] { switch options[0] {
case RemoteOptionMirrorPush: case RemoteOptionMirrorPush:
@ -37,15 +38,15 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st
} }
_, _, err := cmd. _, _, err := cmd.
AddDynamicArguments(remoteName, remoteURL). AddDynamicArguments(remoteName, remoteURL).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)}) RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }
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 := git.NewCommand("remote", "rm").AddDynamicArguments(remoteName) cmd := gitcmd.NewCommand("remote", "rm").AddDynamicArguments(remoteName)
_, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)}) _, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
return err return err
}) })
} }
@ -64,8 +65,8 @@ func GitRemoteGetURL(ctx context.Context, repo Repository, remoteName string) (*
// GitRemotePrune prunes the remote branches that no longer exist in the remote repository. // 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 { func GitRemotePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error {
return git.NewCommand("remote", "prune").AddDynamicArguments(remoteName). return gitcmd.NewCommand("remote", "prune").AddDynamicArguments(remoteName).
Run(ctx, &git.RunOpts{ Run(ctx, &gitcmd.RunOpts{
Timeout: timeout, Timeout: timeout,
Dir: repoPath(repo), Dir: repoPath(repo),
Stdout: stdout, Stdout: stdout,
@ -75,8 +76,8 @@ func GitRemotePrune(ctx context.Context, repo Repository, remoteName string, tim
// GitRemoteUpdatePrune updates the remote branches and prunes the ones that no longer exist in the remote repository. // 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 { func GitRemoteUpdatePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error {
return git.NewCommand("remote", "update", "--prune").AddDynamicArguments(remoteName). return gitcmd.NewCommand("remote", "update", "--prune").AddDynamicArguments(remoteName).
Run(ctx, &git.RunOpts{ Run(ctx, &gitcmd.RunOpts{
Timeout: timeout, Timeout: timeout,
Dir: repoPath(repo), Dir: repoPath(repo),
Stdout: stdout, Stdout: stdout,

184
modules/glob/glob.go Normal file
View File

@ -0,0 +1,184 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package glob
import (
"errors"
"fmt"
"regexp"
"code.gitea.io/gitea/modules/util"
)
// Reference: https://github.com/gobwas/glob/blob/master/glob.go
type Glob interface {
Match(string) bool
}
type globCompiler struct {
nonSeparatorChars string
globPattern []rune
regexpPattern string
regexp *regexp.Regexp
pos int
}
// compileChars compiles character class patterns like [abc] or [!abc]
func (g *globCompiler) compileChars() (string, error) {
result := ""
if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '!' {
g.pos++
result += "^"
}
for g.pos < len(g.globPattern) {
c := g.globPattern[g.pos]
g.pos++
if c == ']' {
return "[" + result + "]", nil
}
if c == '\\' {
if g.pos >= len(g.globPattern) {
return "", errors.New("unterminated character class escape")
}
result += "\\" + string(g.globPattern[g.pos])
g.pos++
} else {
result += string(c)
}
}
return "", errors.New("unterminated character class")
}
// compile compiles the glob pattern into a regular expression
func (g *globCompiler) compile(subPattern bool) (string, error) {
result := ""
for g.pos < len(g.globPattern) {
c := g.globPattern[g.pos]
g.pos++
if subPattern && c == '}' {
return "(" + result + ")", nil
}
switch c {
case '*':
if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '*' {
g.pos++
result += ".*" // match any sequence of characters
} else {
result += g.nonSeparatorChars + "*" // match any sequence of non-separator characters
}
case '?':
result += g.nonSeparatorChars // match any single non-separator character
case '[':
chars, err := g.compileChars()
if err != nil {
return "", err
}
result += chars
case '{':
subResult, err := g.compile(true)
if err != nil {
return "", err
}
result += subResult
case ',':
if subPattern {
result += "|"
} else {
result += ","
}
case '\\':
if g.pos >= len(g.globPattern) {
return "", errors.New("no character to escape")
}
result += "\\" + string(g.globPattern[g.pos])
g.pos++
case '.', '+', '^', '$', '(', ')', '|':
result += "\\" + string(c) // escape regexp special characters
default:
result += string(c)
}
}
return result, nil
}
func newGlobCompiler(pattern string, separators ...rune) (Glob, error) {
g := &globCompiler{globPattern: []rune(pattern)}
// Escape separators for use in character class
escapedSeparators := regexp.QuoteMeta(string(separators))
if escapedSeparators != "" {
g.nonSeparatorChars = "[^" + escapedSeparators + "]"
} else {
g.nonSeparatorChars = "."
}
compiled, err := g.compile(false)
if err != nil {
return nil, err
}
g.regexpPattern = "^" + compiled + "$"
regex, err := regexp.Compile(g.regexpPattern)
if err != nil {
return nil, fmt.Errorf("failed to compile regexp: %w", err)
}
g.regexp = regex
return g, nil
}
func (g *globCompiler) Match(s string) bool {
return g.regexp.MatchString(s)
}
func Compile(pattern string, separators ...rune) (Glob, error) {
return newGlobCompiler(pattern, separators...)
}
func MustCompile(pattern string, separators ...rune) Glob {
g, err := Compile(pattern, separators...)
if err != nil {
panic(err)
}
return g
}
func IsSpecialByte(c byte) bool {
return c == '*' || c == '?' || c == '\\' || c == '[' || c == ']' || c == '{' || c == '}'
}
// QuoteMeta returns a string that quotes all glob pattern meta characters
// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`.
// Reference: https://github.com/gobwas/glob/blob/master/glob.go
func QuoteMeta(s string) string {
pos := 0
for pos < len(s) && !IsSpecialByte(s[pos]) {
pos++
}
if pos == len(s) {
return s
}
b := make([]byte, pos+2*(len(s)-pos))
copy(b, s[0:pos])
to := pos
for ; pos < len(s); pos++ {
if IsSpecialByte(s[pos]) {
b[to] = '\\'
to++
}
b[to] = s[pos]
to++
}
return util.UnsafeBytesToString(b[0:to])
}

208
modules/glob/glob_test.go Normal file
View File

@ -0,0 +1,208 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// Copyright (c) 2016 Sergey Kamardin
// SPDX-License-Identifier: MIT
//
//nolint:revive // the code is from gobwas/glob
package glob
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Reference: https://github.com/gobwas/glob/blob/master/glob_test.go
const (
pattern_all = "[a-z][!a-x]*cat*[h][!b]*eyes*"
regexp_all = `^[a-z][^a-x].*cat.*[h][^b].*eyes.*$`
fixture_all_match = "my cat has very bright eyes"
fixture_all_mismatch = "my dog has very bright eyes"
pattern_plain = "google.com"
regexp_plain = `^google\.com$`
fixture_plain_match = "google.com"
fixture_plain_mismatch = "gobwas.com"
pattern_multiple = "https://*.google.*"
regexp_multiple = `^https:\/\/.*\.google\..*$`
fixture_multiple_match = "https://account.google.com"
fixture_multiple_mismatch = "https://google.com"
pattern_alternatives = "{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}"
regexp_alternatives = `^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$`
fixture_alternatives_match = "http://yahoo.com"
fixture_alternatives_mismatch = "http://google.com"
pattern_alternatives_suffix = "{https://*gobwas.com,http://exclude.gobwas.com}"
regexp_alternatives_suffix = `^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$`
fixture_alternatives_suffix_first_match = "https://safe.gobwas.com"
fixture_alternatives_suffix_first_mismatch = "http://safe.gobwas.com"
fixture_alternatives_suffix_second = "http://exclude.gobwas.com"
pattern_prefix = "abc*"
regexp_prefix = `^abc.*$`
pattern_suffix = "*def"
regexp_suffix = `^.*def$`
pattern_prefix_suffix = "ab*ef"
regexp_prefix_suffix = `^ab.*ef$`
fixture_prefix_suffix_match = "abcdef"
fixture_prefix_suffix_mismatch = "af"
pattern_alternatives_combine_lite = "{abc*def,abc?def,abc[zte]def}"
regexp_alternatives_combine_lite = `^(abc.*def|abc.def|abc[zte]def)$`
fixture_alternatives_combine_lite = "abczdef"
pattern_alternatives_combine_hard = "{abc*[a-c]def,abc?[d-g]def,abc[zte]?def}"
regexp_alternatives_combine_hard = `^(abc.*[a-c]def|abc.[d-g]def|abc[zte].def)$`
fixture_alternatives_combine_hard = "abczqdef"
)
type test struct {
pattern, match string
should bool
delimiters []rune
}
func glob(s bool, p, m string, d ...rune) test {
return test{p, m, s, d}
}
func TestGlob(t *testing.T) {
for _, test := range []test{
glob(true, "* ?at * eyes", "my cat has very bright eyes"),
glob(true, "", ""),
glob(false, "", "b"),
glob(true, "*ä", "åä"),
glob(true, "abc", "abc"),
glob(true, "a*c", "abc"),
glob(true, "a*c", "a12345c"),
glob(true, "a?c", "a1c"),
glob(true, "a.b", "a.b", '.'),
glob(true, "a.*", "a.b", '.'),
glob(true, "a.**", "a.b.c", '.'),
glob(true, "a.?.c", "a.b.c", '.'),
glob(true, "a.?.?", "a.b.c", '.'),
glob(true, "?at", "cat"),
glob(true, "?at", "fat"),
glob(true, "*", "abc"),
glob(true, `\*`, "*"),
glob(true, "**", "a.b.c", '.'),
glob(false, "?at", "at"),
glob(false, "?at", "fat", 'f'),
glob(false, "a.*", "a.b.c", '.'),
glob(false, "a.?.c", "a.bb.c", '.'),
glob(false, "*", "a.b.c", '.'),
glob(true, "*test", "this is a test"),
glob(true, "this*", "this is a test"),
glob(true, "*is *", "this is a test"),
glob(true, "*is*a*", "this is a test"),
glob(true, "**test**", "this is a test"),
glob(true, "**is**a***test*", "this is a test"),
glob(false, "*is", "this is a test"),
glob(false, "*no*", "this is a test"),
glob(true, "[!a]*", "this is a test3"),
glob(true, "*abc", "abcabc"),
glob(true, "**abc", "abcabc"),
glob(true, "???", "abc"),
glob(true, "?*?", "abc"),
glob(true, "?*?", "ac"),
glob(false, "sta", "stagnation"),
glob(true, "sta*", "stagnation"),
glob(false, "sta?", "stagnation"),
glob(false, "sta?n", "stagnation"),
glob(true, "{abc,def}ghi", "defghi"),
glob(true, "{abc,abcd}a", "abcda"),
glob(true, "{a,ab}{bc,f}", "abc"),
glob(true, "{*,**}{a,b}", "ab"),
glob(false, "{*,**}{a,b}", "ac"),
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/rate"),
glob(true, "/{rate,[0-9][0-9][0-9]}*", "/rate"),
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/usd"),
glob(true, "{*.google.*,*.yandex.*}", "www.google.com", '.'),
glob(true, "{*.google.*,*.yandex.*}", "www.yandex.com", '.'),
glob(false, "{*.google.*,*.yandex.*}", "yandex.com", '.'),
glob(false, "{*.google.*,*.yandex.*}", "google.com", '.'),
glob(true, "{*.google.*,yandex.*}", "www.google.com", '.'),
glob(true, "{*.google.*,yandex.*}", "yandex.com", '.'),
glob(false, "{*.google.*,yandex.*}", "www.yandex.com", '.'),
glob(false, "{*.google.*,yandex.*}", "google.com", '.'),
glob(true, "*//{,*.}example.com", "https://www.example.com"),
glob(true, "*//{,*.}example.com", "http://example.com"),
glob(false, "*//{,*.}example.com", "http://example.com.net"),
glob(true, pattern_all, fixture_all_match),
glob(false, pattern_all, fixture_all_mismatch),
glob(true, pattern_plain, fixture_plain_match),
glob(false, pattern_plain, fixture_plain_mismatch),
glob(true, pattern_multiple, fixture_multiple_match),
glob(false, pattern_multiple, fixture_multiple_mismatch),
glob(true, pattern_alternatives, fixture_alternatives_match),
glob(false, pattern_alternatives, fixture_alternatives_mismatch),
glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_first_match),
glob(false, pattern_alternatives_suffix, fixture_alternatives_suffix_first_mismatch),
glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_second),
glob(true, pattern_alternatives_combine_hard, fixture_alternatives_combine_hard),
glob(true, pattern_alternatives_combine_lite, fixture_alternatives_combine_lite),
glob(true, pattern_prefix, fixture_prefix_suffix_match),
glob(false, pattern_prefix, fixture_prefix_suffix_mismatch),
glob(true, pattern_suffix, fixture_prefix_suffix_match),
glob(false, pattern_suffix, fixture_prefix_suffix_mismatch),
glob(true, pattern_prefix_suffix, fixture_prefix_suffix_match),
glob(false, pattern_prefix_suffix, fixture_prefix_suffix_mismatch),
} {
g, err := Compile(test.pattern, test.delimiters...)
require.NoError(t, err)
result := g.Match(test.match)
assert.Equal(t, test.should, result, "pattern %q matching %q should be %v but got %v, compiled=%s", test.pattern, test.match, test.should, result, g.(*globCompiler).regexpPattern)
}
}
func TestQuoteMeta(t *testing.T) {
for id, test := range []struct {
in, out string
}{
{
in: `[foo*]`,
out: `\[foo\*\]`,
},
{
in: `{foo*}`,
out: `\{foo\*\}`,
},
{
in: `*?\[]{}`,
out: `\*\?\\\[\]\{\}`,
},
{
in: `some text and *?\[]{}`,
out: `some text and \*\?\\\[\]\{\}`,
},
} {
act := QuoteMeta(test.in)
assert.Equal(t, test.out, act, "QuoteMeta(%q)", test.in)
_, err := Compile(act)
assert.NoError(t, err, "#%d _, err := Compile(QuoteMeta(%q) = %q); err = %q", id, test.in, act, err)
}
}

View File

@ -16,6 +16,7 @@ import (
"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/gitcmd"
"code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/indexer"
path_filter "code.gitea.io/gitea/modules/indexer/code/bleve/token/path" path_filter "code.gitea.io/gitea/modules/indexer/code/bleve/token/path"
"code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/indexer/code/internal"
@ -162,7 +163,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
var err error var err error
if !update.Sized { if !update.Sized {
var stdout string var stdout string
stdout, _, err = git.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) stdout, _, err = gitcmd.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,6 +15,7 @@ import (
"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/gitcmd"
"code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/indexer"
"code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/indexer/code/internal"
indexer_internal "code.gitea.io/gitea/modules/indexer/internal" indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
@ -147,7 +148,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
var err error var err error
if !update.Sized { if !update.Sized {
var stdout string var stdout string
stdout, _, err = git.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) stdout, _, err = gitcmd.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -250,7 +251,7 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st
func (b *Indexer) Delete(ctx context.Context, repoID int64) error { func (b *Indexer) Delete(ctx context.Context, repoID int64) error {
if err := b.doDelete(ctx, repoID); err != nil { if err := b.doDelete(ctx, repoID); err != nil {
// Maybe there is a conflict during the delete operation, so we should retry after a refresh // Maybe there is a conflict during the delete operation, so we should retry after a refresh
log.Warn("Deletion of entries of repo %v within index %v was erroneus. Trying to refresh index before trying again", repoID, b.inner.VersionedIndexName(), err) log.Warn("Deletion of entries of repo %v within index %v was erroneous. Trying to refresh index before trying again", repoID, b.inner.VersionedIndexName(), err)
if err := b.refreshIndex(ctx); err != nil { if err := b.refreshIndex(ctx); err != nil {
return err return err
} }

View File

@ -10,13 +10,14 @@ import (
repo_model "code.gitea.io/gitea/models/repo" 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/git/gitcmd"
"code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/indexer/code/internal"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
func getDefaultBranchSha(ctx context.Context, repo *repo_model.Repository) (string, error) { func getDefaultBranchSha(ctx context.Context, repo *repo_model.Repository) (string, error) {
stdout, _, err := git.NewCommand("show-ref", "-s").AddDynamicArguments(git.BranchPrefix+repo.DefaultBranch).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) stdout, _, err := gitcmd.NewCommand("show-ref", "-s").AddDynamicArguments(git.BranchPrefix+repo.DefaultBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -32,8 +33,8 @@ func getRepoChanges(ctx context.Context, repo *repo_model.Repository, revision s
needGenesis := len(status.CommitSha) == 0 needGenesis := len(status.CommitSha) == 0
if !needGenesis { if !needGenesis {
hasAncestorCmd := git.NewCommand("merge-base").AddDynamicArguments(status.CommitSha, revision) hasAncestorCmd := gitcmd.NewCommand("merge-base").AddDynamicArguments(status.CommitSha, revision)
stdout, _, _ := hasAncestorCmd.RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) stdout, _, _ := hasAncestorCmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
needGenesis = len(stdout) == 0 needGenesis = len(stdout) == 0
} }
@ -86,7 +87,7 @@ func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) {
// genesisChanges get changes to add repo to the indexer for the first time // genesisChanges get changes to add repo to the indexer for the first time
func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) { func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) {
var changes internal.RepoChanges var changes internal.RepoChanges
stdout, _, runErr := git.NewCommand("ls-tree", "--full-tree", "-l", "-r").AddDynamicArguments(revision).RunStdBytes(ctx, &git.RunOpts{Dir: repo.RepoPath()}) stdout, _, runErr := gitcmd.NewCommand("ls-tree", "--full-tree", "-l", "-r").AddDynamicArguments(revision).RunStdBytes(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
@ -98,8 +99,8 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s
// nonGenesisChanges get changes since the previous indexer update // nonGenesisChanges get changes since the previous indexer update
func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) { func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) {
diffCmd := git.NewCommand("diff", "--name-status").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision) diffCmd := gitcmd.NewCommand("diff", "--name-status").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision)
stdout, _, runErr := diffCmd.RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) stdout, _, runErr := diffCmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
if runErr != nil { if runErr != nil {
// previous commit sha may have been removed by a force push, so // previous commit sha may have been removed by a force push, so
// try rebuilding from scratch // try rebuilding from scratch
@ -115,9 +116,9 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
updatedFilenames := make([]string, 0, 10) updatedFilenames := make([]string, 0, 10)
updateChanges := func() error { updateChanges := func() error {
cmd := git.NewCommand("ls-tree", "--full-tree", "-l").AddDynamicArguments(revision). cmd := gitcmd.NewCommand("ls-tree", "--full-tree", "-l").AddDynamicArguments(revision).
AddDashesAndList(updatedFilenames...) AddDashesAndList(updatedFilenames...)
lsTreeStdout, _, err := cmd.RunStdBytes(ctx, &git.RunOpts{Dir: repo.RepoPath()}) lsTreeStdout, _, err := cmd.RunStdBytes(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
if err != nil { if err != nil {
return err return err
} }

View File

@ -134,7 +134,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
}, },
}, },
// Search for matches on both the contents and the filenames within the repo '62'. // Search for matches on both the contents and the filenames within the repo '62'.
// This scenario yields two results: the first result is baed on the file (cucumber.md) while the second is based on the contents // This scenario yields two results: the first result is based on the file (cucumber.md) while the second is based on the contents
{ {
RepoIDs: []int64{62}, RepoIDs: []int64{62},
Keyword: "cucumber", Keyword: "cucumber",

View File

@ -28,7 +28,7 @@ import (
) )
// IndexerMetadata is used to send data to the queue, so it contains only the ids. // IndexerMetadata is used to send data to the queue, so it contains only the ids.
// It may look weired, because it has to be compatible with the old queue data format. // It may look weird, because it has to be compatible with the old queue data format.
// If the IsDelete flag is true, the IDs specify the issues to delete from the index without querying the database. // If the IsDelete flag is true, the IDs specify the issues to delete from the index without querying the database.
// If the IsDelete flag is false, the ID specify the issue to index, so Indexer will query the database to get the issue data. // If the IsDelete flag is false, the ID specify the issue to index, so Indexer will query the database to get the issue data.
// It should be noted that if the id is not existing in the database, it's index will be deleted too even if IsDelete is false. // It should be noted that if the id is not existing in the database, it's index will be deleted too even if IsDelete is false.

View File

@ -18,7 +18,7 @@ func resolveLinkRelative(ctx context.Context, base, cur, link string, absolute b
} }
if strings.HasPrefix(link, "/") { if strings.HasPrefix(link, "/") {
if strings.HasPrefix(link, base) && strings.Count(base, "/") >= 4 { if strings.HasPrefix(link, base) && strings.Count(base, "/") >= 4 {
// a trick to tolerate that some users were using absolut paths (the old gitea's behavior) // a trick to tolerate that some users were using absolute paths (the old gitea's behavior)
finalLink = link finalLink = link
} else { } else {
finalLink = util.URLJoin(base, "./", link) finalLink = util.URLJoin(base, "./", link)

View File

@ -10,10 +10,9 @@ import (
"strings" "strings"
"sync" "sync"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/gobwas/glob"
) )
var ( var (

View File

@ -22,7 +22,7 @@
// //
// 4. Handler (represented by HandlerFuncT type): // 4. Handler (represented by HandlerFuncT type):
// - It's the function responsible for processing items. Each active worker will call it. // - It's the function responsible for processing items. Each active worker will call it.
// - If an item or some items are not psuccessfully rocessed, the handler could return them as "unhandled items". // - If an item or some items are not successfully processed, the handler could return them as "unhandled items".
// In such scenarios, the queue system ensures these unhandled items are returned to the base queue after a brief delay. // In such scenarios, the queue system ensures these unhandled items are returned to the base queue after a brief delay.
// This mechanism is particularly beneficial in cases where the processing entity (like a document indexer) is // This mechanism is particularly beneficial in cases where the processing entity (like a document indexer) is
// temporarily unavailable. It ensures that no item is skipped or lost due to transient failures in the processing // temporarily unavailable. It ensures that no item is skipped or lost due to transient failures in the processing

View File

@ -3,7 +3,7 @@
package setting package setting
import "github.com/gobwas/glob" import "code.gitea.io/gitea/modules/glob"
type GlobMatcher struct { type GlobMatcher struct {
compiledGlob glob.Glob compiledGlob glob.Glob

View File

@ -9,10 +9,9 @@ import (
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"github.com/gobwas/glob"
) )
// enumerates all the types of captchas // enumerates all the types of captchas

View File

@ -6,10 +6,10 @@ package setting
import ( import (
"testing" "testing"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/gobwas/glob"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -17,7 +17,7 @@ import (
type StateType string type StateType string
const ( const (
// StateOpen pr is opend // StateOpen pr is opened
StateOpen StateType = "open" StateOpen StateType = "open"
// StateClosed pr is closed // StateClosed pr is closed
StateClosed StateType = "closed" StateClosed StateType = "closed"
@ -76,6 +76,8 @@ type Issue struct {
// swagger:strfmt date-time // swagger:strfmt date-time
Deadline *time.Time `json:"due_date"` Deadline *time.Time `json:"due_date"`
TimeEstimate int64 `json:"time_estimate"`
PullRequest *PullRequestMeta `json:"pull_request"` PullRequest *PullRequestMeta `json:"pull_request"`
Repo *RepositoryMeta `json:"repository"` Repo *RepositoryMeta `json:"repository"`

View File

@ -319,7 +319,7 @@ const (
) )
// Name represents the service type's name // Name represents the service type's name
// WARNNING: the name have to be equal to that on goth's library // WARNING: the name has to be equal to that on goth's library
func (gt GitServiceType) Name() string { func (gt GitServiceType) Name() string {
return strings.ToLower(gt.Title()) return strings.ToLower(gt.Title())
} }

View File

@ -61,7 +61,7 @@ type User struct {
// MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility // MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility
func (u User) MarshalJSON() ([]byte, error) { func (u User) MarshalJSON() ([]byte, error) {
// Re-declaring User to avoid recursion // Redeclaring User to avoid recursion
type shadow User type shadow User
return json.Marshal(struct { return json.Marshal(struct {
shadow shadow

View File

@ -48,12 +48,12 @@ func TestIsSvgImage(t *testing.T) {
<!-- Comments --> <!-- Comments -->
<svg></svg>`)).IsSvgImage()) <svg></svg>`)).IsSvgImage())
assert.True(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?> assert.True(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?>
<!-- Multline <!-- Multiline
Comment --> Comment -->
<svg></svg>`)).IsSvgImage()) <svg></svg>`)).IsSvgImage())
assert.True(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?> assert.True(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Multline <!-- Multiline
Comment --> Comment -->
<svg></svg>`)).IsSvgImage()) <svg></svg>`)).IsSvgImage())

View File

@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/go-chi/binding" "gitea.com/go-chi/binding"
"github.com/gobwas/glob"
) )
const ( const (

View File

@ -6,8 +6,9 @@ package validation
import ( import (
"testing" "testing"
"code.gitea.io/gitea/modules/glob"
"gitea.com/go-chi/binding" "gitea.com/go-chi/binding"
"github.com/gobwas/glob"
) )
func getGlobPatternErrorString(pattern string) string { func getGlobPatternErrorString(pattern string) string {

View File

@ -11,9 +11,8 @@ import (
"strings" "strings"
"sync" "sync"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/gobwas/glob"
) )
type globalVarsStruct struct { type globalVarsStruct struct {

View File

@ -8,7 +8,7 @@ gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar !gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties # Avoid ignore Gradle wrapper properties
!gradle-wrapper.properties !gradle-wrapper.properties
# Cache of project # Cache of project

View File

@ -3425,7 +3425,6 @@ swift.install=Přidejte balíček do svého <code>Package.swift</code> souboru:
swift.install2=a spustit následující příkaz: swift.install2=a spustit následující příkaz:
vagrant.install=Pro přidání Vagrant box spusťte následující příkaz: vagrant.install=Pro přidání Vagrant box spusťte následující příkaz:
settings.link=Propojit tento balíček s repozitářem settings.link=Propojit tento balíček s repozitářem
settings.link.description=Pokud propojíte balíček s repozitářem, je tento balíček uveden v seznamu balíčků repozitáře.
settings.link.select=Vybrat repozitář settings.link.select=Vybrat repozitář
settings.link.button=Aktualizovat odkaz na repozitář settings.link.button=Aktualizovat odkaz na repozitář
settings.link.success=Odkaz na repozitář byl úspěšně aktualizován. settings.link.success=Odkaz na repozitář byl úspěšně aktualizován.

View File

@ -3475,7 +3475,6 @@ swift.install=Füge das Paket deiner <code>Package.swift</code> Datei hinzu:
swift.install2=und führe den folgenden Befehl aus: swift.install2=und führe den folgenden Befehl aus:
vagrant.install=Um eine Vagrant-Box hinzuzufügen, führe den folgenden Befehl aus: vagrant.install=Um eine Vagrant-Box hinzuzufügen, führe den folgenden Befehl aus:
settings.link=Dieses Paket einem Repository zuweisen settings.link=Dieses Paket einem Repository zuweisen
settings.link.description=Wenn du ein Paket mit einem Repository verknüpfst, wird es in der Paketliste des Repositories angezeigt.
settings.link.select=Repository auswählen settings.link.select=Repository auswählen
settings.link.button=Repository-Link aktualisieren settings.link.button=Repository-Link aktualisieren
settings.link.success=Repository-Link wurde erfolgreich aktualisiert. settings.link.success=Repository-Link wurde erfolgreich aktualisiert.

View File

@ -3133,7 +3133,6 @@ swift.install=Προσθέστε το πακέτο στο αρχείο <code>Pac
swift.install2=και εκτελέστε την ακόλουθη εντολή: swift.install2=και εκτελέστε την ακόλουθη εντολή:
vagrant.install=Για προσθήκη ενός κυτίου Vagrant, εκτελέστε την ακόλουθη εντολή: vagrant.install=Για προσθήκη ενός κυτίου Vagrant, εκτελέστε την ακόλουθη εντολή:
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=Ο σύνδεσμος αποθετηρίου ενημερώθηκε επιτυχώς.

Some files were not shown because too many files have changed in this diff Show More